www.xbdev.net
xbdev - software development
Friday February 14, 2025
Home | Contact | Support | Assembly Language What every pc speaks..1010...
     
 

Assembly Language

What every pc speaks..1010...

 

Protected Mode (PIC, Timing, IRQ).....

by bkenwright@xbdev.net

 

 

 

I suppose once you get into 32bit protected mode, and start playing around with simple interrupts, you decide you want more and more!  I mean you want to harness all the cool little things that your computer can harness...from probing the keyboard, to getting timing information from the 8549 timing chip.  It takes us the the heart and workings of the IBM PC, and teaches us the real meaning of how a computer works and of course how our operating system does its tasks.

 

 

Well what we have here, is simply, setting up the PIC (Programmable Interrupt Controller) so that its set and ready to go.  Then we disable all IRQ's and enable only IRQ0 for the timer (8253/8254) chip, so that it calls an interrupt each time it has counted down to zero.  Each time it counts down to zero it calls our interrupt, which simply increments our counter...and as its set to 100hz, every time the counter reaches 100, we modify the character on the screen, so we get an alternating character at 1 second per change.

Note that when you run the simulation on BOCHs, a pc 80x86 simulator you'll find that the timming isn't real time and the frequency you get from your timing chip isn't the same as what you would get if you ran the code on a real pc.

 

Download File (click) - asm_1.asm

 


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


;-------------------------------------------------------------------------

 

 

 

etc etc..

 

 

 

 

 

 

 

 

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.