
; Run in dos (not under windows) and it will take us to 32 bit protected mode

[ORG 0x100]         ; Reserve 256 bytes for dos

; 8253 timer - generating interrupts at 100hz

[BITS 16]           ; Dos is 16 bits


; assemble using 'nasm' assemblaer

; C:>nasm asm_1.asm -o test.exe

jmp entry           ; Jump to the start of our code

msg1 db 'Where good to go..$';

jumpOffset:
    dd go_pm
    dw 0x08

entry:
 
; Display a message showing where alive!

mov dx, msg1        ; register dx=msg1
mov ah, 9           ; register ah=9 -- the print string function
int 21h             ; dos service interrupt .. looks at register ah to figure out what to do

; Thanks from Brendan, as we have to make sure our GDTR points to the actual
; memory address, add code location and dos 0x100 onto our loaded offset 

    mov eax,0
    mov ax,cs
    shl eax,4
    add [gdtr+2],eax
    add [idtr+2],eax         ; set idtr and gdtr so it points to the 'real' address in memory
    add [jumpOffset],eax     ; do the same for our 32 pm addr

    add [int_start], eax     ; Set the value of int_start (our simple interrupt function) so
                             ; it has the "physical" address
; we have 4 different interrupt functions at the bottom of the code:
;isr_00, irq0, int_test, exc
;Our main one will be exc, and its address is saved in memory location int_start
    
    mov eax, [int_start]
    mov word [idt_start], ax
    shr eax,16
    mov word [idt_start+6], ax


;---------------------------------------------------------------------------
; Whats all this about?...
;---------------------------------------------------------------------------
; We loop through all 46 of the interrupt function addresses in our IDT and
; set them all to point to the same interupt function, which we need to
; correct because dos loads our code at some unknown address in memory and
; our IDT address values need to be the real memory address, not the offsets.

;mov ecx, 46                                 ; Number of IDT Interupt Functions
mov ecx, 32
mov ebx, 0                                  ; Start value for our loop counter
zloop:

    mov eax, [int_start]
    add ebx, 8
    mov word [idt_start + ebx], ax
    shr eax,16
    mov word [idt_start+6 + ebx], ax

      dec ecx     
      jne zloop                             ; if ecx is 0, end of loop
;---------------------------------------------------------------------------


  cli		    ; Clear or disable interrupts
  
  mov     al, 0x70
  mov     dx, 0x80
  out     dx, al      ; outb(0x80, 0x70) - disable NMI
          

  lgdt[gdtr]	    ; Load GDT
  
  lidt[idtr]       ; Load IDT
     
  mov eax,cr0	    ; The lsb of cr0 is the protected mode bit
  or al,0x01	    ; Set protected mode bit
  mov cr0,eax	    ; Mov modified word to the control register

jmp far dword [jumpOffset]  ;can't just use "jmp go_pm" as where in dos!


nop                 ; ignore - no operation opcodes :)
nop

;---------------------------------------------------------------------------
;                                 32 BIT
;---------------------------------------------------------------------------
; Once we reach here where in protected mode!  32 Bit!  Where not in
; the real world (mode) anymore :)
[BITS 32]
go_pm :

mov ax, 0x10        ; use our datasel selector ( alternatively mov ax, datasel )
mov ds, ax,
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax

mov esp, 0afffh    ; we need a simple stack if where calling functions!
                   ; Such as interupt functions etc, as the return address is
                   ; put on the stack remember!

mov word [es: 0xb8000],0x740 ; put a char to the screen!...yeahh!
                             ; '@' in the top left of the screen if we made it
                             ; here okay.

; Force a call to interrupt 4!
; Int 0x4           ; We call our interrupt 4 subroutine

;-------------------------------------------------------------------------
; Divide by Zero Warning
;-------------------------------------------------------------------------
; Just remember, when we do a divide by zero, and the interupt is called,
; the return address passed to the interrupt, is in fact the address of the
; line that caused the interrupt!  So if we just return from the interrupt
; it would just keep causing the interrupt over and over again.
;-------------------------------------------------------------------------
; Do a divide by 0 error, so we force a call to our interrupt 0
;  mov eax, 0
;  mov ebx, 0
;  div ebx            ; eax divided by ebx, and stored back in eax

mov byte [es: 0xb8008], "B" ; poke a character onto our screen buffer

nop
nop

;-------------------------------------------------------------------------
; Where not in basics anymore!
;-------------------------------------------------------------------------
; This is the line where we go from simple settups, to using the computers
; interal hardward, fiddling around with the irqs and pic (Programmable
; Interupt Controller) etc.
;-------------------------------------------------------------------------

; Disable all IRQs.
;-------------------------------------------------------------------------
disable_irqs:
          mov     al, 0xFF
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, 0xFF)

          mov     al, 0xFF
          mov     dx, 0xA1
          out     dx, al      ; outb(0x21, 0xFF)
          
;-------------------------------------------------------------------------          
          mov byte [es: 0xb8020], "Q" ; poke a char onto screen (debug)
          

          mov     cl, 0x20                            ; PIC 1.
          mov     ch, 0x28                            ; PIC 2.
;-------------------------------------------------------------------------
; Remap PICs to:  CL = pic1  CH = pic2
;-------------------------------------------------------------------------
remap_pics:

     ; IWC1
     ;------
          mov     al, 0x11
          mov     dx, 0x20
          out     dx, al      ; outb(0x20, 0x11)

          mov     al, 0x11
          mov     dx, 0xA0
          out     dx, al      ; outb(0xA0, 0x11)
 
     ; IWC2
     ;------
          mov     al, cl
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, pic1)

          mov     al, ch
          mov     dx, 0xA1
          out     dx, al      ; outb(0xA1, pic2)

     ; IWC3
     ;------
          mov     al, 0x04
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, 4)

          mov     al, 0x02
          mov     dx, 0xA1
          out     dx, al      ; outb(0xA1, 2)

     ; IWC4
     ;------
          mov     al, 0x01
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, 0x01)

          mov     al, 0x01
          mov     dx, 0xA1
          out     dx, al      ; outb(0xA1, 0x01)
;-------------------------------------------------------------------------

          mov byte [es: 0xb8022], "R" ; poke a char onto screen (debug)
         
;-------------------------------------------------------------------------

; Enable IRQ0 (timer) at the 8259 PIC chips, disable others:
;(Port 0x21 bit 0 is timer)
          mov     al, 0xFE
          mov     dx, 0x21
          out     dx, al      ; outb(0x21, ~0x01)

          mov     al, 0xFF
          mov     dx, 0xA1
          out     dx, al      ; outb(0xA1, ~0x00)

          mov byte [es: 0xb8024], "S" ; poke a char onto screen (debug)
;-------------------------------------------------------------------------

; Every time an irq interupt occurs, we must clear it before another irq
; is sent.  Else another interupt wont' be sent till its been cleared.
; We usually call this at the end of our interupt routine.

;EOI for IRQ 0-7
mov     al, 0x20
        mov dx, 0x20
        out dx, al      ; outb(0x20, 0x20)
        
;-------------------------------------------------------------------------

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

       ;16bit value - Valid range is (0-65535)  
       ;zero (0) indicates the maximum value of 65535
       ;Counts down to 0 at 1.19318Mhz
       ;Now there are 3 counters, but where only interested in Counter 0,
       ;the other 2 are for ram refresh and audio etc.
       ;The clock freq, with which the counter is decremented is 1.19318MHz
       ;So to refresh all the people out there, 1/freq is the time.  So
       ; 1.19318MHz/65535 ~ 18.1 times a second or (55ms between etc interupt)
       
       ;Bascially this is as slow as we can make our clock go, but we could 
       ;put a counter in our timer interrupt to keep track of how many times
       ;its been called. For example, every 1000 times the interupt is called
       ;this is about 1000x0.055 or 55 seconds.
       
       ;Remember this!
       ;Counter 0 is the time of day interrupt and is generated
	   ;approximately 18.2 times per sec.  The value 18.2 is derived from
	   ;the frequency 1.19318/65536 (the normal default count).
       
;       countdown  equ	8000h ; approx 36 interrupts per second
;                             ; e.g. 1.19318/8000h = 1.10318/32768 ~ 36
;
;	   mov	al,00110110b  ; bit 7,6 = (00) timer counter 0              \
;			              ; bit 5,4 = (11) write LSB then MSB           |
;			              ; bit 3-1 = (011) generate square wave        |- outb(0x43, 0x36)
;			              ; bit 0 = (0) binary counter                  |
;	   out	43h,al	      ; prep PIT, counter 0, square wave&init count /
;	   
;	   mov	cx,countdown  ; default is 0x0000 (65536) (18.2 per sec)    \
;			              ; interrupts when counter decrements to 0     |
;	   mov	al,cl	      ; send LSB of timer count                     |- out(0x40,countdown_lsb)
;	   out	40h,al        ;                                             |
;	                      ;                                             /

;	   mov	al,ch	      ; send MSB of timer count                     \
;	   out	40h,al        ;                                             |- out(0x40 countdown_msb)
;	                      ;                                             /

       ;So for example, seting the counter to a value of 1234, and
       ;haveing it countdown, is done like this:
       ;   outportb(0x43, 0x36);
       ;   outportb(0x40, 0x12);
       ;   outportb(0x40, 0x34);


;------------------------------------------------------------------------
;0x2E95 gives an interrupt rate of 100Hz.

	   mov	al,36         ; outb(0x43, 0x36)
	   out	43h,al	      
	  
	   mov	al,0x2e	      ; default is 0x0000 (65536) (18.2 per sec) 
	   out	40h,al        ; out(0x40,countdown_lsb)			            

	   mov	al,0x95	      ; msb
	   out	40h,al        ; out(0x40 countdown_msb)        

;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

mov word[tick_count], 0

;-------------------------------------------------------------------------

sti              ; Interrupts back..

mov byte [es: 0xb8026], "T" ; poke a char onto screen (debug)

;int 0

lp: jmp lp  ; loops here forever and ever...


  ; loops here forever and ever...

; We use 16 bits here - as you'll notice we use dw and dd only,
; and out data will be packed together nice and tight.

[BITS 16]
align 4

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our GDTR register value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

gdtr :
   dw gdt_end-gdt-1    ; Length of the gdt
   dd gdt	       ; physical address of gdt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is the start of our gdt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align 4

gdt:    
gdt0 		       
   dd 0		      
   dd 0                
codesel:          
   dw 0x0ffff	      
   dw 0x0000	       
   db 0x00             	
   db 0x09a	      
   db 0x0cf	       
   db 0x00	       
datasel:        
   dw 0x0ffff	       
   dw 0x0000	      
   db 0x00	       
   db 0x092
   db 0x0cf
   db 0x00
gdt_end:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

align 4
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our IDTR register value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

idtr :
   dw idt_end - idt_start - 1  ; Length of the idt
   dd idt_start        ; physical address of idt

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This is the start of our idt - its actual value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 

; Nothing special, just a very basic interrupt function so we know something
; has happened
;-------------------------------------------------------------------------
[BITS 16]

;-------------------------------------------------------------------------

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

align 4

idt_start:
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0 ;  5
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0 ; 10
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      exc, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0 ; 15
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0 ; 20
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0 ; 25
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0 ; 29
                dw      unexp, 8, 08e00h, 0
                dw      unexp, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0   ; 32
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0
                dw      irq, 8, 08e00h, 0 ; 47   
idt_end:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
align 4
[bits 32]

int_start :
   dd exc
   dw 0x08
   
   align 4

;-------------------

vval:
  dd 0
tick_count:
  dd 0
  

exc:
unexp:
irq:
pushad
push es


;We use a variable 'tick_count' which is incremented each time our interupt
;is called.  Since in this demo only our timer is calling it, at 100 times
;a second, when the tick_count variable is 100, this is 1 seconds.

inc word [tick_count]     ; increment our tick_count variable
cmp word [tick_count], 100 ; is it 100?
jl eend                   ;  if less than 100 goto eend

mov word [tick_count], 0  ; if its more than 100 where here, and hence reset our counter
                           ; value to 0


nop
mov ax,0x10
mov es,ax


cmp word [vval], 1
jz bbb

abb:
inc word [vval]
mov byte [es: 0xb8002], "X" ; poke a character into the graphics output screen
jmp eend

bbb:
mov dword [vval], 0
mov byte [es: 0xb8002], "Y" ; poke a character into the graphics output screen

eend:


;EOI for IRQ 0-7
mov    al, 0x20
        mov dx, 0x20
        out dx, al      ; outb(0x20, 0x20)


;To end interupts of both chips 0-15, you can use.
;For IRQ 8-15, both 8259 chips must get EOI: 
;mov     al, 0x20
;        mov dx, 0xA0
;        out dx, al      ; outb(0xA0, 0x20)
;
;mov     al, 0x20
;        mov dx, 0x20
;        out dx, al      ; outb(0x20, 0x20)          

pop es
popad
iret


; Small delay loop
; on a p4 2ghz its about 2 seconds :)
;-------------------------------------------------------------------
delay_loop:

mov eax, 700
xdloop:

mov ebx, 100
ydloop:

mov ecx, 100 
zdloop:
      dec ecx     
      jne zdloop  

      dec ebx     
      jne ydloop  

      dec eax     
      jne xdloop  
;------------------------------------------------------------------


;-------------------------------------------------------------------------

TIMES 0x500-($-$$) DB 0x90    ; And of course, this will make our file size
                             ; equal to 0x500 a nice round number -
                             ; 0x500 ... so if you assemble the file
                             ; you should find its that size exactly.


;-------------------------------------------------------------------------





