; 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.
;-------------------------------------------------------------------------
|