;=============================================================================
; boot.asm - Protected mode example boot program.
;
; Copyright (C) 1992-93, Dennis Marer               File created: 12/21/92
;                                                   Last modified: 1/27/93
;
; Description:
;  This program sets up the system, the hardware, and the 386 for use in
;  its mysterious and tricky Protected Mode.  This initializes the GDT, the
;  IDT, the kernel TSS, and an LDT for use by the kernel.  Hardware which
;  is initialized includes the 8259 interrupt controller (PIC), the 8253
;  timer (PIT), and the 8042 slave processor (SP) for A20 control.  The
;  final long jump into protected mode begins the execution of the kernel.
;
; Portability:
;  Requires Turbo Assembler 1.0 (TASM) or better to be assembled.
;  Dependent on the IBM PC 386 or better platform and its standard devices.
;
; Modifications:
;  DPM, 1/27/93 Finished the first revision of this file.
;=============================================================================

                .386p

                include 386p.inc                ; 386 Protected mode
                include ibmpc.inc               ; IBM PC hardware
                include kernel.inc              ; Kernel definitions



;============================ Boot stack segment =============================

BootStack       segment para stack use32 'BootStack'
                dw      64 dup (?)
BootStack       ends



;============================= Boot data segment =============================

BootData        segment use16 'BootData'



;-----------------------------------------------------------------------------
; Miscellaneous data, used only during initialization.
;-----------------------------------------------------------------------------

SPFailure       db      'Failure de-gating the A20 line.$'

segBase         dd      0                       ; Linear segment base
PIC1Mask        db      0                       ; Saved interrupt masks
PIC2Mask        db      0

GDTInfo         dw      GDTEnd-GDTStart-1       ; GDT limit
GDTAddr         dd      0                       ; GDT linear start address

IDTInfo         dw      IDTEnd-IDTStart-1       ; IDT limit
IDTAddr         dd      0                       ; IDT linear start address



;-----------------------------------------------------------------------------
; List of interrupt handlers used to initialize the vectors.
;-----------------------------------------------------------------------------

intrTable:      dw      IntrDivide              ; 0  - Divide error
                dw      IntrDebug               ; 1  - Debug exception
                dw      IntrNMI                 ; 2  - NMI
                dw      IntrBreakpoint          ; 3  - One byte interrupt
                dw      IntrOverflow            ; 4  - Interrupt on overflow
                dw      IntrBounds              ; 5  - Array bounds error
                dw      IntrOpcode              ; 6  - Invalid opcode
                dw      IntrMathGone            ; 7  - Math not available
                dw      IntrDouble              ; 8  - Double fault
                dw      IntrMathOver            ; 9  - Math segment overflow
                dw      IntrTSS                 ; 10 - Invalid TSS
                dw      IntrSegment             ; 11 - Segment not present
                dw      IntrStack               ; 12 - Stack fault
                dw      IntrGeneral             ; 13 - General protection
                dw      IntrPage                ; 14 - Page fault
                dw      Reserved                ; 15
                dw      IntrMathErr             ; 16 - Math error
                dw      Reserved                ; 17
                dw      Reserved                ; 18
                dw      Reserved                ; 19
                dw      Reserved                ; 20
                dw      Reserved                ; 21
                dw      Reserved                ; 22
                dw      Reserved                ; 23
                dw      Reserved                ; 24
                dw      Reserved                ; 25
                dw      Reserved                ; 26
                dw      Reserved                ; 27
                dw      Reserved                ; 28
                dw      Reserved                ; 29
                dw      Reserved                ; 30
                dw      Reserved                ; 31
                dw      KernelClock             ; 32 - IRQ 0
                dw      Unused                  ; 33 - IRQ 1
                dw      Unused                  ; 34 - IRQ 2
                dw      Unused                  ; 35 - IRQ 3
                dw      Unused                  ; 36 - IRQ 4
                dw      Unused                  ; 37 - IRQ 5
                dw      Unused                  ; 38 - IRQ 6
                dw      Unused                  ; 39 - IRQ 7
                dw      Unused                  ; 40 - IRQ 8
                dw      Unused                  ; 41 - IRQ 9
                dw      Unused                  ; 42 - IRQ 10
                dw      Unused                  ; 43 - IRQ 11
                dw      Unused                  ; 44 - IRQ 12
                dw      Unused                  ; 45 - IRQ 13
                dw      Unused                  ; 46 - IRQ 14
                dw      Unused                  ; 47 - IRQ 15

intrTableSize  equ     ($ - intrTable) / 2     ; Size (number of entries)



;-----------------------------------------------------------------------------
; Global Descriptor Table (GDT) - Set up the GDT for use in Protected Mode,
;  including a descriptor for the kernel code and data segments, the kernel
;  TSS, the necessary stack fault handler TSS and double handler TSS, and
;  a descriptor to access the screen memory.
;-----------------------------------------------------------------------------

GDTStart        seg_descr <0,0,0,0,0,0>                 ; NULL descriptor

_access         = AC_MAPPED+ACT_A386                    ; PM TSS descriptor
kernelDescr     seg_descr <TSSLimit,0,0,_access,0,0>

_access         = AC_MAPPED+AC_USER+AC_CODE+ACC_READ    ; Kernel code segment
descrKCS        seg_descr <0FFFFh,0,0,_access,LIM_32BIT,0>

_access         = AC_MAPPED+AC_USER+ACD_WRITE           ; Kernel data segment
descrKDS        seg_descr <0FFFFh,0,0,_access,0,0>

_access         = AC_MAPPED+ACT_A386                    ; Stack fault handler
descrStack      seg_descr <TSSLimit,0,0,_access,0,0>

_access         = AC_MAPPED+ACT_A386                    ; Double fault handler
descrDouble     seg_descr <TSSLimit,0,0,_access,0,0>

_access         = AC_MAPPED+ACT_LDT                     ; LDT descriptor
descrLDT        seg_descr <LDTLimit,0,0,_access,0,0>

_access         = AC_MAPPED+AC_USER+ACD_WRITE           ; Screen memory
descrScreen     seg_descr <0ffffh,0,0,_access,0,0>

_access         = AC_MAPPED+AC_USER+ACD_WRITE           ; Memory over 1 meg!
descr1Meg       seg_descr <0ffffh,0,0,_access,0,0>

GDTEnd          equ     $



;-----------------------------------------------------------------------------
; Interrupt descriptor Table, which will be filled in later.
;-----------------------------------------------------------------------------

IDTStart        db      (32+16)*8 dup (?)
IDTEnd          equ     $



;-----------------------------------------------------------------------------
; Empty LDT table, required by Protected Mode for the task.
;-----------------------------------------------------------------------------

LDTStart        seg_descr   <0,0,0,0,0,0>       ; NULL descriptor
LDTLimit        equ     $ - LDTStart



;-----------------------------------------------------------------------------
; Task State Segments.
;-----------------------------------------------------------------------------

kernelTSS       tss386  <>
TSSLimit        equ     $ - kernelTSS - 1       ; Limit of a TSS

TSSStack        tss386  <>                      ; Stack fault handler
TSSDouble       tss386  <>                      ; Double fault handler



;-----------------------------------------------------------------------------
; Define each of the selectors for the useful segments.
;-----------------------------------------------------------------------------

selKernel       equ     kernelDescr - GDTStart
selKCS          equ     descrKCS - GDTStart
selKDS          equ     descrKDS - GDTStart
selStack        equ     descrStack - GDTStart
selDouble       equ     descrDouble - GDTStart
selLDT          equ     descrLDT - GDTStart
selScreen       equ     descrScreen - GDTStart
sel1Meg         equ     descr1Meg - GDTStart



BootData        ends



;=============================================================================

Boot            segment para use16 'Boot'
                assume  CS:Boot,DS:BootData



;-----------------------------------------------------------------------------
; LINEAR - Create a 32-bit linear address of the specified variable and
;  store the result in EAX.  This relies on the segment base in segBase.
;  This is destructive to the EBX register.
;-----------------------------------------------------------------------------

LINEAR          macro   variable
                mov     EAX,dword ptr segBase
                mov     EBX,offset variable
                add     EAX,EBX
                endm



;-----------------------------------------------------------------------------
; DBASE - Macro to store the base address held in EAX into the specified
;  descriptor.  This is destructive to the EAX register.
;-----------------------------------------------------------------------------

DBASE           macro   dest
                mov     dest.base_0,AX          ; Store the bits 0-15
                shr     EAX,16
                mov     dest.base_16,AL         ; Store the bits 16-19
                endm



;-----------------------------------------------------------------------------
; DiskOff - Force the floppy drive to shut its motor off by calling the
;  interrupt 8 (IRQ 0) handler a good number of times.  This also speeds
;  up the system clock a strange amount.  It's a hack, but it works.
;-----------------------------------------------------------------------------

DiskOff         proc    near

                mov     CX,1000                 ; Loop 1000 times

DiskOffLoop:    int     8                       ; Call IRQ 0 handler
                loop    DiskOffLoop

                ret
DiskOff         endp



;-----------------------------------------------------------------------------
; SetupGDT - Setup the Global Descriptor Table (GDT) for use within the
;  386 Protected Mode, initializing each of the descriptors and the Task
;  State Segments (TSS) found there.
;-----------------------------------------------------------------------------

SetupGDT        proc    near

                LINEAR  GDTStart                ; Save the linear address of
                mov     GDTAddr,EAX             ;   the GDT for later

                xor     EAX,EAX                 ; Create a 32-bit linear
                mov     AX,seg KernelStart      ;   address for the kernel
                shl     EAX,4                   ;   code segment and place
                DBASE   descrKCS                ;   this in its descriptor

                xor     EAX,EAX                 ; Create a 32-bit linear
                mov     AX,seg initStack        ;   address for the kernel
                shl     EAX,4                   ;   data segment and place
                DBASE   descrKDS                ;   this in its descriptor

                LINEAR  kernelTSS               ; Create the linear addresses
                DBASE   kernelDescr             ;   for each of the TSS
                LINEAR  TSSStack                ;   which reside in this
                DBASE   descrStack              ;   program's data segment
                LINEAR  TSSDouble
                DBASE   descrDouble
                LINEAR  LDTStart                ; Also the LDT
                DBASE   descrLDT

                mov     EAX,0B8000h             ; Screen fixed in memory
                DBASE   descrScreen

                mov     EAX,100000h             ; Memory over 1 megabyte!
                DBASE   descr1Meg

                lea     DI,kernelTSS            ; Set up the kernel TSS
                mov     AX,selKDS
                mov     [DI].t3SS0,AX
                mov     [DI].t3SS1,AX
                mov     [DI].t3SS2,AX
                mov     [DI].t3DS,AX
                mov     [DI].t3ES,AX
                mov     [DI].t3FS,AX
                mov     [DI].t3GS,AX
                mov     [DI].t3SS,AX
                mov     [DI].t3CS,selKCS
                mov     [DI].t3ESP0,offset stack0
                mov     [DI].t3ESP1,offset stack1
                mov     [DI].t3ESP2,offset stack2
                mov     [DI].t3LDT,selLDT
                mov     [DI].t3EFlags,000000h   ; Interrupts off

                cld                             ; Set direction to forward

                lea     SI,kernelTSS            ; Duplicate the PM TSS into
                lea     DI,TSSStack             ;   the stack fault TSS
                mov     CX,TSSLimit+1
                rep movsb
                mov     TSSStack.t3EIP,offset IntrStack

                lea     SI,kernelTSS            ; Duplicate the PM TSS into
                lea     DI,TSSDouble            ;   the double fault TSS
                mov     CX,TSSLimit+1
                rep movsb
                mov     TSSDouble.t3EIP,offset IntrDouble

                ret

SetupGDT        endp



;-----------------------------------------------------------------------------
; SetupIDT - Setup the Interrupt Descriptor Table (IDT) for use within the
;  386 Protected Mode, initializing each of the descriptors with values
;  from the existing interrupt vectors.
;-----------------------------------------------------------------------------

SetupIDT        proc    near

                LINEAR  IDTStart                ; Save the linear address of
                mov     IDTAddr,EAX             ;   the IDT for later

                mov     CX,intrTableSize        ; Set things up to loop
                lea     BX,intrTable            ;   through the whole
                lea     DI,IDTStart             ;   interrupt table

FillIDT:        mov     [DI].selector,selKCS    ; Selector value to use
                mov     [DI].words,00h          ; No words to be pushed
                mov     [DI].access,08Eh        ; Descriptor type (int gate)
                mov     AX,[BX]                 ; Set the interrupt handler
                mov     [DI].offset_0,AX
                mov     [DI].offset_16,0

                add     DI,8                    ; Move on to the next vector
                add     BX,2                    ;   and the next table entry
                loop FillIDT

                lea     DI,IDTStart+(8*8)       ; Vector 8, double fault
                mov     [DI].selector,selDouble ; Selector value to use
                mov     [DI].words,00h          ; No words to be pushed
                mov     [DI].access,0E5h        ; Task Gate with DPL=3
                mov     [DI].offset_0,0         ; (unused)
                mov     [DI].offset_16,0        ; (unused)

                lea     DI,IDTStart+(12*8)      ; Vector 12, stack fault
                mov     [DI].selector,selStack  ; Selector value to use
                mov     [DI].words,00h          ; No words to be pushed
                mov     [DI].access,0E5h        ; Task Gate with DPL=3
                mov     [DI].offset_0,0         ; (unused)
                mov     [DI].offset_16,0        ; (unused)

                ret

SetupIDT        endp



;-----------------------------------------------------------------------------
; EmptySP - Wait for the 8042 Slave Processor (SP) to empty its buffer.  This
;  is used exclusively by the SetupSP procedure.
;
; Returns AL=0 if the buffer is empty, timeout occurred otherwise.
;-----------------------------------------------------------------------------

EmptySP         proc    near

                push    CX                      ; Save used registers
                xor     CX,CX                   ; CX=65536 for timeout value

EmptySPLoop:    in      AL,SP_STATUS            ; Read the status port, find
                and     AL,SP_BUFFER_FULL       ;   out if the buffer is full
                loopnz  EmptySPLoop             ; Loop until buffer empty

                pop     CX                      ; Restore used registers
                ret

EmptySP         endp



;-----------------------------------------------------------------------------
; SetupSP - Setup the 8042 Slave Processor (SP), most notably enabling its
;  A20 output to allow access to memory above 1 megabyte.  If this function
;  fails, a message is displayed and the program terminates.
;-----------------------------------------------------------------------------

SetupSP         proc    near

                call    EmptySP                 ; Empty the 8042 SP buffer,
                jnz     SetupSPExit             ;   exit on failure

                mov     AL,SP_WRITE_OUTPUT      ; Send command to write
                out     SP_STATUS,AL            ;   output port, wait until
                call    EmptySP                 ;   the 8042 buffer is empty,
                jnz     SetupSPExit             ;   exit on failure

                mov     AL,SP_ENABLE_A20        ; Send command to enable the
                out     SP_PORT_A,AL            ;   A20 line, wait until the
                call    EmptySP                 ;   8042 buffer is empty,
                jnz     SetupSPExit             ;   exit on failure
                ret

SetupSPExit:    sti                             ; Re-enable interrupts
                mov     AH,009h                 ; Output failure message
                mov     DX,offset SPFailure
                int     21h
                int     20h                     ; Terminate program

SetupSP         endp



;-----------------------------------------------------------------------------
; SetupPIC - Setup the dual 8259 Programmable Interrupt Controllers (PIC)
;  to relocate the hardware interrupts past the 386 reserved vectors.
;-----------------------------------------------------------------------------

PIC1_VECTOR     equ     020h                    ; New PIC interrupt vectors
PIC2_VECTOR     equ     028h

SetupPIC        proc    near

                in      AL,PIC1_DATA            ; Save the current mask
                mov     PIC1Mask,AL             ;   state of both of the
                in      AL,PIC2_DATA            ;   8259 PICs
                mov     PIC2Mask,AL

                mov     AL,ICW1_INIT+ICW1_ICW4  ; Send the initialization
                out     PIC1_CMND,AL            ;   sequence to both the
                DELAY                           ;   8259 PICs
                out     PIC2_CMND,AL
                DELAY

                mov     AL,PIC1_VECTOR          ; Set the new interrupt
                out     PIC1_DATA,AL            ;   vectors to avoid
                DELAY                           ;   conflicts with the
                mov     AL,PIC2_VECTOR          ;   386 reserved vectors
                out     PIC2_DATA,AL
                DELAY

                mov     AL,4                    ; PIC1 is the master, which
                out     PIC1_DATA,AL            ;   has channel 2 cascaded
                DELAY
                mov     AL,2                    ; PIC2 is slave, with ID #2
                out     PIC2_DATA,AL
                DELAY

                mov     AL,ICW4_8086            ; Put both PICs into 8086
                out     PIC1_DATA,AL            ;   mode, to use vectors
                DELAY
                out     PIC2_DATA,AL
                DELAY

                mov     AL,PIC1Mask             ; Restore the interrupt masks
                out     PIC1_DATA,AL            ;   to their original states
                DELAY
                mov     AL,PIC2Mask
                out     PIC2_DATA,AL
                DELAY

                ret

SetupPIC        endp



;-----------------------------------------------------------------------------
; SetupPIT - Setup the dual 8253 Programmable Interval Timer (PIT) to speed
;  up the system clock (timer 0) to roughly 100 ticks/second.  The other
;  two timers are still used for DMA (timer 1) and speaker (timer 2).
;-----------------------------------------------------------------------------

SetupPIT        proc    near

                mov     AL,PIT_SC0+PIT_LOAD+PIT_SQUARE
                out     PIT_CMND,AL

                mov     AL,09Bh                 ; Load the system clock timer
                out     PIT_CNT0,AL             ;   with a value to generate
                mov     AL,02Eh                 ;   roughly 100 ticks/second
                out     PIT_CNT0,AL

                ret

SetupPIT        endp
        


;-----------------------------------------------------------------------------
; Main - Main program start point.
;-----------------------------------------------------------------------------

Main            proc    far

                call    DiskOff                 ; Turn floppy drive off

                mov     AX,BootData             ; Load the data segment
                mov     DS,AX

                mov     AX,seg initStack        ; Load the kernel data
                mov     ES,AX                   ;   segment in order to
                lss     SP,ES:initStack         ;   load the new stack

                mov     EAX,0                   ; Convert the data segment
                mov     AX,BootData             ;   into a 32-bit linear
                shl     EAX,4                   ;   address and store for
                mov     segBase,EAX             ;   use by the LINEAR macro

                pushfd                          ; Make sure no surprises are
                pop     EAX                     ;   found in the flags, clear
                and     EAX,00FFFh              ;   VM, IOPL, NT, and so on
                push    EAX
                popfd

                cli                             ; Ensure interrupts disabled

                call    SetupGDT                ; Setup the GDT table
                call    SetupIDT                ; Setup the IDT table
                call    SetupSP                 ; Setup the SP, A20 hardware
                call    SetupPIC                ; Setup the PIC hardware
                call    SetupPIT                ; Setup the PIT hardware

                lgdt    fword ptr GDTInfo       ; Load the GDT
                lidt    fword ptr IDTInfo       ; Load the IDT
                mov     EAX,CR0                 ; Switch to Protected Mode
                or      AL,1                    ;   by setting the PM bit
                mov     CR0,EAX

                db      0EAh                    ; Perform a far jump to clear
                dw      offset KernelStart      ;   the prefetch queue and
                dw      selKCS                  ;   load the kernel segment

Main            endp


Boot            ends

                end     main
