Reputation: 7930
Still working on an x86 NASM assembly OS.
Followup question to my last one (CLI and STI are not working):
After I realized that the CLI and STI instructions were turning all of interrupts off correctly, I also realized shortly afterward that the System Timer & RTC clock are interrupts themselves (see Wikipedia - IRQ : x86 IRQs : Master PIC). This was why the clock would not function- it would wait and wait (and wait) forever, until the System timer got around to updating. This obviously never happened, because the Interrupt for the update was off!
Unfortunately, knowing this does not solve my atomicity problem: the system clock cannot be read without interrupts on. However, having interrupts on will not ensure atomicity.
I have heard that there are ways to mask some interrupts, but not all of them. I would like to know how to mask all of the interrupts besides 0 and 8 (see wikipedia link). I want to implement this in the Wait_Clk_Ticks
function.
Which brings me to this code- this code, when loaded onto the boot device (floppy disk in my case) will display a "falling raindrop" effect. A red, 2 pixel long section will move slowly down the screen, then restart at the top. Which is what it is supposed to do.
However, there are 3 problems, all of which are related to the clock (I believe):
When a key is pressed, the computer will beep obnoxiously, and the raindrop will go slowly. I believe that is because pressign a key is causing an interrupt, and that is delaying the timer function.
There is no atomicity for the timer.
After a little while, the raindrop will stop completely (possibly this is an interrupt that is causing an issue)
How can I fix these issues?
EDIT: I have added the Set_IRQ_Mask function. However now my os hangs on startup.
EDIT 2: Fixed, see answer.
[BITS 16] ; 16 bit code
[ORG 0x7C00] ; Start to load at 0x7c00
; OS to create 'falling raindrop' effect
jmp Code_Start
Set_IRQ_Mask: ; see http://wiki.osdev.org/8259_PIC and http://cs.smith.edu/~thiebaut/ArtOfAssembly/CH17/CH17-3.html
in al, 21h ; Read existing bits.
or al, 00010000b ; Disable IRQ4 (serial port com1)
out 21h, al
mov al, 0x20 ; Send end of interrupt signal
out 0x20, al
ret
Wait_Clk_Ticks:
; MASK CERTAIN INTERRUPTS
push ax ; Store current values.
push bx
mov ax, 0 ; Reset 'ds' (destination pointer).
mov ds, ax
mov bx, [46Ch] ; Tick status is at 0:46Ch.
; Store into 'bx'.
_WCT_Get_Tick: ; Gets new tick.
mov ax, [46Ch] ; Update current time into 'ax'.
cmp ax, bx ; If 'current' == 'older' ticks,
je _WCT_Get_Tick
; Then clock tick isn't over: reset.
mov bx, ax ; If the clock tick is over,
; put 'current' into 'older'.
sub cx, 1 ; Decrement number of ticks till exit.
jnz _WCT_Get_Tick;
; If 'cx' (ticks till exit) is zero,
pop bx ; Restore current values.
pop ax
; UNMASK CERTAIN INTERRUPTS
ret ; Return.
Set_Video_Mode:
mov ax, 13h ; VGA mode (320x200) 256 color
int 10h ; sets vga mode SEE: http://www.wagemakers.be/english/doc/vga
ret
Write_Pixel:
push ax ; push variables used here onto the stack
push bx
push cx
mov cx, 320 ; puts 320 (how many rows there are) into a register
mul cx ; multiplies 320 by AX (# of rows) and stores into AX
add ax, bx ; adds rows and cols to make the location, puts into ax
mov si, ax ; move ax into si
mov bx, 0a000h ; this puts 0a000h (starting register) as the beginning
mov es, bx ; move bx into es
pop cx
mov word [es:si], cx ; move the color attribute (cx) into the memory location es+si
pop bx ; restore variables from stack
pop ax
ret
Code_Start:
mov bx, 40 ; work on COLUMN 40 solely
call Set_Video_Mode
call Set_IRQ_Mask
Reset:
mov ax, 0 ; Reset row to 0
Loop:
mov cx, 40 ; Write head in in RED (40)
call Write_Pixel
cmp ax, 0 ; Are we at row 1?
je Next_Zero ; Go to special conditions
cmp ax, 1 ; Are we at row 2?
je Next_One ; Go to special conditions
jmp Next_Norm ; Otherwise, no special conditions
Next_Zero: ; Make the cover spot 197 if the current dot is 0
push ax
mov ax, 197
jmp Cover_Black
Next_One: ; Make the cover spot 198 if the current dot is 1
push ax
mov ax, 198
jmp Cover_Black
Next_Norm: ; Otherwise, make the cover spot 'ax - 2'
push ax
sub ax, 2
Cover_Black: ; Set color to black
mov cx, 0
call Write_Pixel
pop ax ; Restore AX to the RED location.
mov cx, 1 ; Set to wait for a clock tick
call Wait_Clk_Ticks
inc ax ; Increment the row
cmp ax, 199 ; If pixel has reached the bottom of the screen, reset AX
je Reset
jmp Loop ; Otherwise, continue downwards.
End:
jmp $ ; Run this line over and over again- stops excecution.
times 510-($-$$) db 0 ; Fill the rest of the 512 byte sector with zeros
dw 0xAA55 ; Boot magic number
Upvotes: 0
Views: 1687
Reputation: 39651
You don't need to disable interrupts at all for what you're doing. I'm not sure what you think needs to be atomic, but the only thing that does need to be done atomically is the 16-bit read of the tick counter in memory. Since aligned 16-bit reads are guaranteed to be atomic, and since the tick counter is suitably aligned you don't need to disable interrupts. Even if it wasn't guaranteed (eg. the counter was located at an odd address) all you would need to do is disable interrupts for the instructions that perform the reads.
Here's how I would implement your Wait_Clk_Ticks
function:
Wait_Clk_Tics:
push ds
push ax
push bx
xor ax, ax
mov ds, ax
; cli
mov ax, [0x46c]
; sti
.loop:
hlt
; cli
mov bx, [0x46c]
; sti
sub bx, ax
cmp bx, cx
jb .loop
pop bx
pop ax
pop ds
ret
I've shown where the CLI/STI instructions would go if timer counter wasn't suitably aligned. I've also simplified the code so it doesn't count counter changes, it just subtracts the current value of the counter from its original value. Finally I've inserted a HLT instruction. This will cause the CPU to wait for an interrupt. On a relatively modern CPU this will also cause the processor to enter a lower power state and should have noticeable effect power usage and the noise generated by the CPU fan.
Upvotes: 0
Reputation: 1
or al, 00010000b ; Disable IRQ4 (serial port com1)
The previous line is not as correct as yours or will NOT mask off the serial port com1!!!
and al, 11101111b
Upvotes: -1
Reputation: 7930
...Well, it seems that the fix was to add ret
after the Set_IRQ_Mask
function...
That was 5 hours of debugging for that one line... :) Thanks for the help though!
Upvotes: 1
Reputation: 11716
Assuming you're not trying to deal with any multiprocessing stuff yet, you can program the 8259 Programmable Interrupt Controller to mask certain IRQs.
Upvotes: 1