Objective Summary: I need to write a sleep()
function to be used in 8088 (PC/XT ISA) ROM code with 1 ms resolution. Though the question can be more generalized to chaining INTs with data in different segments.
I'm writing procedure that will create delay with 1 ms resolution for the purpose of waiting for I/O device settling. The plan is:
- Save the running
INT 08H
(0000:0020
) vector, and keep it in a safe place. - Speed up PIT timer 0 to 1 kHz, where
1,193,180 / 1000
= divisor of1193
. - Install new handler at
0000:0020
that calls the originalINT 08H
once every 55 ms, to make sure not to muck things up with the standard timer. - Set up a counter in memory for the desired number of ms, decrementing each time the new 1 ms
INT 08H
completes, and wait for 0. Note: since this is BIOS ROM code this counter must be stored in temp space in BDA segment0040H
. - After ms timer has elapsed, put it all back the way you found it.
This all seems fairly straightforward, and the code I wrote for it works as expected. At the point in step #3, where the new interrupt is to jump/chain to the old one I am running into a little issue. That is, in order to make this FAR jump I would need DS
to be the memory in the BDA segment (0040H)
containing the vector of the original interrupt. However since this is jumping straight to that code for it to complete and then IRET
, I cannot change DS
to be anything other than how it was called, nor can I just expect that it will be called with DS
being set to BDA. Now, in practice that will probably be the cause since the "sleep" function is really just a loop checking a counter, where DS
is the BDA segment. However, this seems like a bug waiting to happen.
Other examples of doing something similar, mostly using a DOS INT 21H
/ TSR pattern do the far jump to CS:INT_08_OLD
since that data will live in the TSR's read/write memory space. In the case of ROM of course, CS
is read-only so that won't work.
Is there something I'm missing here or another way to look at it that I'm not seeing?
Below is the vector code:
;----------------------------------------------------------------------------;
; New INT 08 vector to be installed at 0000:0020H
;----------------------------------------------------------------------------;
NEW_INT_08:
STI
PUSH DS
PUSH AX
MOV AX, 40H
MOV DS, AX ; DS = BDA (0040H)
;----------------------------------------------------------------------------;
; Check if sleep counter is zero
;
CLI
DEC SLEEP_COUNTER ; counter at BDA (0040:00ACH)
JZ INT_DELAY_TIMER_DONE
;----------------------------------------------------------------------------;
; Check if 18.2 Hz INT 08 counter is zero
;
CLI
CMP TICK_COUNTER ; counter at BDA (0040:00AEH)
JZ INT_DELAY_PIT_INT8
;----------------------------------------------------------------------------;
; Not elapsed, send EOI and exit
;
INT_DELAY_TIMER_DONE:
STI
MOV AL, 20H ; End of Interrupt OCW
OUT 20H, AL ; write EOI to port 0
POP AX
POP DS
IRET
;----------------------------------------------------------------------------;
; Counter is done. Reset counter and call previous INT 08 interrupt.
;
INT_DELAY_PIT_INT8:
MOV TICK_COUNTER, 55 ; restart counter
;
; Original Code
;
; POP AX
; POP DS ; must restore DS from caller
; JMP DWORD PTR DS:INT_08_OLD ; jump to original INT 8, let it EOI and IRET
;
; Suggestion from @justme:
;
PUSHF ; save flags to simulate INT/IRET
CALL DWORD PTR DS:INT_08_OLD ; jump to original INT 8, let it EOI
POP AX ; call preserve registers
POP DS
IRET
Edit 1: Added suggestion from @justme above
Edit 2: I had left out the sleep/re-vectoring/un-vectoring code from the original post for the sake of keeping the post shorter, but I realize that might have left it a little unclear as to the rest of it. For the sake of completeness here is the "sleep" portion of it.
SEGMENT AT 40H
ORG 0ACH
SLEEP_COUNTER DW ? ; BDA counter for sleep (ms)
TICK_COUNTER DB ? ; BDA counter for 18.2 Hz ticks
;----------------------------------------------------------------------------;
; "Sleep" using PIT channel 0 for ~1 ms delay
;----------------------------------------------------------------------------;
; Input:
; AX = number of ms to delay
;----------------------------------------------------------------------------;
PIT_SLEEP:
TEST AX, AX ; do nothing if 0 delay
JZ IO_DELAY_PIT_EXIT
PUSH AX
PUSH SI
PUSH DI
PUSH DS
PUSH ES
MOV SI, SEG _BDA
MOV ES, SI ; ES = BDA segment (0040H)
XOR SI, SI
MOV DS, SI ; DS = IVT segment (0000H)
;----------------------------------------------------------------------------;
; Set up counters
;
MOV DI, OFFSET SLEEP_COUNTER ; address in BDA memory for counters
STOSW ; set SLEEP counter (in ms)
MOV AL, 55 ; reset 18.2Hz INT 08 counter
STOSB
;----------------------------------------------------------------------------;
; Save old INT 08 vector
;
CLI
MOV SI, OFFSET _INT_08H ; SI = INT 08 offset in IVT (0067H)
MOV DI, OFFSET INT_08_OLD ; DI = temp location in BDA
PUSH DI ; save temp location
PUSH SI ; save INT 08 offset in IVT
MOVSW ; move old INT 08 offset
MOVSW ; move old INT 08 segment
POP DI ; DI = INT 08 offset in IVT
PUSH DI
;----------------------------------------------------------------------------;
; Write new INT 08 handler to IVT
;
PUSH ES ; save BDA segment (0040)
PUSH DS
POP ES ; ES = IVT segment (0000)
MOV AX, OFFSET NEW_INT_08 ; set up IVT for new INT 08
STOSW ; write offset to IVT
MOV AX, CS ; AX = CS (F000H)
STOSW ; write segment to IVT
;----------------------------------------------------------------------------;
; Set up timer 0 for 1ms (1 kHz)
;
MOV AL, 00110110B ; Send CW to Counter 0
OUT 43H, AL
MOV AL, LOW 1193 ; 1,193,180 / 1000 (1 ms) = ~1193
OUT 40H, AL ; send low byte
MOV AL, HIGH 1193
OUT 40H, AL ; send high byte
POP DS ; DS = BDA segment (0040)
;----------------------------------------------------------------------------;
; Wait until sleep counter has elapsed
;
STI
IO_DELAY_PIT_WAIT:
HLT ; wait for next (any) interrupt
CMP SLEEP_COUNTER, 0 ; check if sleep counter has elapsed
JNZ IO_DELAY_PIT_WAIT
;----------------------------------------------------------------------------;
; Uninstall new INT 08 vector
;
IO_DELAY_PIT_ELAPSED:
POP DI ; DI = INT 08 offset in IVT
POP SI ; SI = pointer to saved old INT 08
CLI
MOVSW ; copy BDA DS:[SI] to IVT ES:[DI]
MOVSW
CALL INIT_TIMER_0 ; reset to standard 18.2Hz timer
STI
POP ES
POP DS
POP DI
POP SI
POP AX
IO_DELAY_PIT_EXIT:
RET
Note: this hasn't yet been fully optimized, commented or "golfed" yet, and sorry for the Dino-MASM syntax. :)
push ax
and anotherpush ax
. Thenpush ds
andpush bp
andmov bp, sp
. Retrieve your downlink address and store it in egdword [bp + 4]
. At the end justpop bp
thenpop ds
thenretf
(if you want to chain this particular interrupt call).