Code: Select all
''=============================================================================
'' This source implements a 1/1024 second-resolution timer for DOS,
'' along with a sleep procedure that provides a similar resolution.
''
'' To avoid problems with the Interrupt 8/IRQ0 handler that fbgfx
'' appears to install under some conditions, and various problems
'' with non-standard PIT timer 0 configurations, this timer is
'' based on the RTC timer and Interrupt 70h/IRQ8.
''=============================================================================
''-------------------------------------------------------------
'' Use a 64-bit counter to avoid having to deal with overflow.
''-------------------------------------------------------------
dim shared as longint g_dos_timer_count
dim shared as integer g_prev_handler_offset
dim shared as short g_prev_handler_selector
''=============================================================================
function DosTimer() as double
return g_dos_timer_count / 1024
end function
''=============================================================================
sub DosSleep( byval ms as integer )
dim as double t
t = DosTimer + ms / 1024
do
loop until DosTimer >= t
end sub
''=============================================================================
sub timer_constructor constructor
asm
''-------------------------------------------------------
'' This jump is necessary to prevent inline execution of
'' our interrupt handler and code-segment data.
''-------------------------------------------------------
jmp 1f
''------------------------------------------------------------------
'' This is our protected-mode interrupt handler. Since, in general,
'' we cannot depend on any segment register other than CS in the
'' handler, the safe approach for any data that the handler needs
'' to access directly is to store it in the code segment. Since the
'' the handler also needs to access data in the data segment, it
'' must have access to the data-segment selector.
''------------------------------------------------------------------
.balign 4
INT70H_HANDLER:
cli
''-------------------------------------------------------------
'' A hardware interrupt handler should preserve all registers.
''-------------------------------------------------------------
push ds
push eax
''----------------------------------------------------------
'' Set up access to the data segment through the DS segment
'' register. Most instructions that access data use DS as
'' the default. The CS override causes the instruction to
'' use CS instead of DS.
''----------------------------------------------------------
mov ax, cs:DS_SEL
mov ds, ax
''----------------------------
'' Increment the timer count.
''----------------------------
add DWORD PTR G_DOS_TIMER_COUNT, 1
adc DWORD PTR G_DOS_TIMER_COUNT+4, 0
''----------------------------------------------------
'' Do not pass the interrupt to the previous handler,
'' just do a dummy read of status register C.
''----------------------------------------------------
mov al, 0xc '' address of status register C
or al, 0x80 '' set bit 7 to disable NMI
out 0x70, al '' write to address port
in al, 0x71 '' do dummy read
mov al, 0xd '' set the address port to status
out 0x70, al '' register D and enable NMI
in al, 0x71 '' do dummy read
''-----------------------------------------------------------
'' For a hardware interrupt the last handler in the handler
'' chain must issue an EOI to the interrupt controller to
'' prepare it for the next interrupt, and since in this case
'' IRQ 8 is from the slave interrupt controller we must
'' issue an EOI to both interrupt controllers starting with
'' the slave.
''-----------------------------------------------------------
mov al, 0x20 '' non-specific EOI
out 0xa0, al '' PIC 2 (slave)
mov al, 0x20 '' non-specific EOI
out 0x20, al '' PIC 1 (master)
''----------------------------
'' Return from the interrupt.
''----------------------------
pop eax
pop ds
sti
iret
''--------------------------------
'' This is our code-segment data.
''--------------------------------
.balign 4
DS_SEL: .short 0
1:
''-------------------------------------------------------------------
'' Since code segments are limited to execute-only or execute/read,
'' to write to the code segment we need to use an alias descriptor,
'' created with the DPMI Create Alias Descriptor function. The alias
'' descriptor is identical to the original CS descriptor, except for
'' the 4-bit Type field of the access byte. Where for the original
'' CS descriptor the Type value 1011b specifies an execute/read code
'' segment, for the alias descriptor the Type value 0010b specifies
'' a read/write data segment. The function returns a selector in AX
'' that we temporarily load into the ES segment register to store
'' the data-segment selector to our code-segment data.
''-------------------------------------------------------------------
push es
mov ax, 0xa
mov bx, cs
int 0x31
mov es, ax
mov ax, ds
mov es:DS_SEL, ax
pop es
''------------------------------------------------------
'' Set the rate selection divider control in RTC status
'' register A for 1024 interrupts per second.
''------------------------------------------------------
cli
mov al, 0xa '' address of status register A
or al, 0x80 '' set bit 7 to disable NMI
out 0x70, al '' write to address port
in al, 0x71 '' get current value
and al, 0xF0 '' clear lower nibble
or al, 6 '' set to 6
out 0x71, al '' write it back
''------------------------------------------
'' Enable the periodic interrupt by setting
'' bit 6 of status register B.
''------------------------------------------
mov al, 0xb '' address of status register B
or al, 0x80 '' set bit 7 to disable NMI
out 0x70, al '' write to address port
in al, 0x71 '' get current value
or al, 0x40 '' set bit 6
out 0x71, al '' write it back
mov al, 0xd '' set the address port to status
out 0x70, al '' register D and enable NMI
in al, 0x71 '' do dummy read
''----------------------------------------------------
'' Unmask (allow) IRQ 8 (necessary for some systems).
''----------------------------------------------------
in al, 0xa1 '' get current mask for PIC 2
and al, NOT 1 '' clear mask for IRQ 8
out 0xa1, al '' write it back
''--------------------------------------------------
'' Get and save the selector and offset address for
'' the previous Interrupt 70h (IRO8) handler.
''--------------------------------------------------
mov ax, 0x204
mov bl, 0x70
int 0x31
mov G_PREV_HANDLER_OFFSET, edx
mov G_PREV_HANDLER_SELECTOR, cx
''----------------------------------------------------------------
'' Lock the interrupt handler and code-segment data, so they will
'' remain fixed in memory even under a DPMI implementation that
'' supports virtual memory.
''----------------------------------------------------------------
mov ax, 0x600
mov ecx, OFFSET 1b
sub ecx, OFFSET INT70H_HANDLER
inc ecx
mov di, cx
shr ecx, 16
mov si, cx
mov ecx, OFFSET INT70H_HANDLER
mov ebx, ecx
shr ebx, 16
int 0x31
''------------------------------------------------
'' Point the Interrupt 70h vector to our handler.
''------------------------------------------------
mov ax, 0x205
mov bl, 0x70
mov cx, cs
mov edx, OFFSET INT70H_HANDLER
int 0x31
end asm
end sub
sub timer_destructor destructor
asm
''------------------------------------------
'' Restore RTC status register B to normal.
''------------------------------------------
cli
mov al, 0xb '' address of status register B
or al, 0x80 '' set bit 7 to disable NMI
out 0x70, al '' write to address port
in al, 0x71 '' get current value
and al, NOT 0x40 '' clear bit 6
out 0x71, al '' write it back
mov al, 0xd '' set the address port to status
out 0x70, al '' register D and enable NMI
in al, 0x71 '' do dummy read
sti
''-----------------------------------------------------------
'' Restore the Interrupt 70h vector to the previous handler.
''-----------------------------------------------------------
mov ax, 0x205
mov bl, 0x70
mov cx, G_PREV_HANDLER_SELECTOR
mov edx, G_PREV_HANDLER_OFFSET
int 0x31
''-----------------------------------------------------
'' Unlock the interrupt handler and code-segment data.
''-----------------------------------------------------
mov ax, 0x601
mov ecx, OFFSET 1b
sub ecx, OFFSET INT70H_HANDLER
inc ecx
mov di, cx
shr ecx, 16
mov si, cx
mov ecx, OFFSET INT70H_HANDLER
mov ebx, ecx
shr ebx, 16
int 0x31
end asm
end sub
''=============================================================================
Code: Select all
#include "dostimer.bas"
'screen 12
'screenres 640,480,32
'width 80,30
dim as double t1,t2,accum
sleep 3000
for i as integer = 1 to 1000
t1 = DosTimer
do
t2 = DosTimer
loop until t2 > t1
accum += t2 - t1
next
print using "DosTimer resolution: ##.### ms";accum
for i as integer = 1 to 1000
t1 = Timer
do
t2 = Timer
loop until t2 > t1
accum += t2 - t1
next
print using "Timer resolution: ##.### ms";accum
sleep
Code: Select all
#include "dostimer.bas"
dim as double t1,t2
''----------------------------------------------------------
'' This code is intended to time against an external clock.
''----------------------------------------------------------
sleep
t1 = DosTimer
sleep
t2 = DosTimer
print t2-t1
sleep
t1 = Timer
sleep
t2 = Timer
print t2-t1
sleep
Code: Select all
#include "dostimer.bas"
dim as double t1,t2
'screen 12
sleep 3000
t1 = DosTimer
for i as integer = 1 to 1000
sleep 1
next
t2 = DosTimer
print using "##.### ms";t2-t1
t1 = DosTimer
for i as integer = 1 to 1000
sleep 10
next
t2 = DosTimer
print using "##.### ms";t2-t1
t1 = DosTimer
for i as integer = 1 to 1000
sleep 20
next
t2 = DosTimer
print using "##.### ms";t2-t1
t1 = DosTimer
for i as integer = 1 to 1000
DosSleep 1
next
t2 = DosTimer
print using "##.### ms";t2-t1
t1 = DosTimer
for i as integer = 1 to 1000
DosSleep 10
next
t2 = DosTimer
print using "##.### ms";t2-t1
t1 = DosTimer
for i as integer = 1 to 1000
DosSleep 20
next
t2 = DosTimer
print using "##.### ms";t2-t1
sleep
Code: Select all
#include "dostimer.bas"
dim as longint i
dim as double t
t = timer
do
i += 1
loop until timer - t > 10
print i
sleep
Code: Select all
3 8192
4 4096
5 2048
6 1024
7 512
8 256
9 128
10 64
11 32
12 16
I still have not added the STI ahead of the IRET, as recommended here, because with the handler as it currently is, not chaining to the previous handler, I cannot see how the IRET could fail to restore EFLAGS to what to what it was before the interrupt, overwriting the effects of the STI.
After more thought, and considering the two higher-priority hardware interrupts (Interrupt 8/IRQ0 and Interrupt 9/IRQ1), I decided the conservative approach would be to specifically clear the interrupt flag on entry to the handler and set it immediately before returning from the interrupt.