; ============================================================================
;
; LT-DOS - Clock device
;
; ============================================================================
; PC Real-time clock:
; Hardware clock generator: 1.193180 MHz (correct value is 1.193182 MHz)
; Timer divisor: 65536
; Interrupt frequency: 18.20648193 Hz (correct value 18.20651245 Hz)
; Interrupt interval: 54,92549322 ms (correct value 54,92540115 ms)
; Ticks per day: 1'573'040, 1800B0h (correct value 1'573'043, 1800B3h)
; Hundredths second per day: 8'640'000
; Recalc counter to hundredth second: 100*65536/1193180 = 5*65536/59659
; ------------- Strukture of data block of CLOCK$ device
struc CLOCKData
clock_date resw 1 ; 0: current date in days from 1/1/1980
clock_min resb 1 ; 2: minute (0 to 59)
clock_hour resb 1 ; 3: hour (0 to 23)
clock_hund resb 1 ; 4: hundredth second (0 to 99)
clock_sec resb 1 ; 5: second (0 to 59)
endstruc
CLOCKDATA_SIZE EQU CLOCKData_size ; 6 bytes
; ----------------------------------------------------------------------------
; Device service tables
; ----------------------------------------------------------------------------
; ------------- CLOCK$ service table
FNCCLK: db 10 ; number of functions
dw DevIntOK ; 0 init
dw DevIntOK ; 1 media check
dw DevIntOK ; 2 build BPB
dw DevIntInv ; 3 IOCTL input
dw CLKRData ; 4 read data
dw DevIntBusy ; 5 test read
dw DevIntOK ; 6 input status
dw DevIntOK ; 7 flush input
dw CLKWData ; 8 write data
dw CLKWData ; 9 write with verify
; ----------------------------------------------------------------------------
; Device interrupt service - clock read data
; ----------------------------------------------------------------------------
; INPUT: ES:DI = data buffer
; DS = data segment
; OUTPUT: AX = status word
; DESTROYS: BX, CX, DX, DI
; ----------------------------------------------------------------------------
; ------------- Read system timer (-> CX:DX, max. 1573039)
CLKRData: call GetTimer ; read system timer
; ------------- Store current date in days from 1/1/1980
cld ; direction up
mov ax,[Date] ; AX <- current date
stosw ; store current date
; ------------- Recalculate system timer to time (-> AX, CX)
call Clock2Time ; recalc timer to time
; ------------- Store time
stosw ; store minute AL and hour AH
xchg ax,cx ; AL <- hudredth, AH <- second
stosw ; store hundredth AL and seconds AH
; ------------- Status OK
mov ah,S_DONE ; status OK
ret
; ----------------------------------------------------------------------------
; Device interrupt service - clock write data
; ----------------------------------------------------------------------------
; INPUT: ES:DI = data buffer
; DS = data segment
; OUTPUT: AX = status word
; DESTROYS: BX, CX, DX, SI
; ----------------------------------------------------------------------------
; ------------- Read system timer and flush date-overflow flag
CLKWData: call GetTimer ; read system timer
; ------------- Set current date from 1/1/1980
cld ; direction up
mov si,di ; SI <- buffer
es lodsw ; AX <- current date
mov [Date],ax ; store current date
; ------------- Set new time to RTC clock
es lodsw ; AL <- minute, AH <- hour
push ax ; push AX
mov dx,[es:si] ; DL <- hundredth, DH <- second
push dx ; push DX
call BinBCDD ; pack to BCD
xchg ax,cx ; CL <- minute, CH <- hour
mov dl,0 ; no daylight savings
mov ah,3 ; AH <- function code
int 1ah ; set current time
pop cx ; pop CX (CL=hundredth, CH=second)
pop bx ; pop BX (BL=minute, BH=hour)
; ------------- Recalculate time to system timer (-> CX:DX)
call Time2Clock ; recalc time to system timer
; ------------- Set new time to system timer
mov ah,1 ; AH <- function code
int 1ah ; set new time
; ------------- Recalc DOS date to date (-> CX, DX)
call DOS2Date ; recalc DOS date to date
; ------------- Set new date to RTC clock
xchg ax,cx ; AX <- date LOW
call BinBCDD ; pack to BCD
xchg ax,cx ; CX <- date LOW
mov ah,5 ; AH <- function code
int 1ah ; set new date
; ------------- Status OK
mov ah,S_DONE ; status OK
ret
; ----------------------------------------------------------------------------
; Recalculate system timer to time
; ----------------------------------------------------------------------------
; INPUT: CX:DX = timer
; OUTPUT: CL = hundredth
; CH = second
; AL = minute
; AH = hour
; DESTROYS: BX, DX
; ----------------------------------------------------------------------------
; ------------- Calculate CX:DX * 5 (-> DX:AX, max. 7865195)
Clock2Time: mov bx,5 ; BX <- 5
xchg ax,dx ; AX <- timer LOW
xchg ax,cx ; AX <- timer HIGH, CX <- timer LOW
mul bx ; AX <- timer HIGH*5
xchg ax,cx ; AX <- timer LOW, CX <- timer HIGH*5
mul bx ; DX:AX <- timer LOW*5
add dx,cx ; add timer HIGH
; ------------- Calculate DX:AX / 59659 * 65536 (-> CX:AX, max. 8639994)
mov bx,59659 ; BX <- 59659
div bx ; AX <- quotient, DX <- remainder
xchg cx,ax ; CX <- hundredths HIGH
xor ax,ax ; AX <- 0, DX:AX = remainder * 65536
div bx ; AX <- hundredths LOW
; ------------- Calculate seconds (->DX:AX, max.86399)+hundredths (->CL, 0-99)
xchg ax,cx ; AX <- hundredths HIGH, CX <- LOW
xor dx,dx ; DX <- 0
mov bx,100 ; BX <- divisor
div bx ; AX <- seconds HIGH, DX <- reaminder
xchg ax,cx ; AX <- hundredths LOW, CX <- sec. HIGH
div bx ; AX <- seconds LOW, DX <- hundredths
xchg dx,cx ; DX <- seconds HIGH, CL <- hundredths
; ------------- Calculate second (-> CH, 0 to 59)+minutes (-> AX, max. 1439)
mov bl,60 ; BX <- divisor
div bx ; calculate minute and second
mov ch,dl ; CH <- second
; ------------- Calculate hour (0 to 23) and minute (0 to 59)
div bl ; AL <- hour, AH <- minute
xchg ah,al ; AL <- minute, AH <- hour
ret
; ----------------------------------------------------------------------------
; Recalculate time to system timer
; ----------------------------------------------------------------------------
; INPUT: BL = minute
; BH = hour
; CL = hundredth
; CH = second
; OUTPUT: CX:DX = timer
; DESTROYS: AX, BX
; ----------------------------------------------------------------------------
; ------------- Merge second with hundredth (-> CX, max. 5999 hundredths)
Time2Clock: mov al,100 ; AL <- 100 hundredths in second
mul ch ; AX <- second in hundredths
mov ch,0 ; CX = hundredth
add cx,ax ; CX <- second*100 + hundredth
; ------------- Merge minute with hour (-> AX, max. 1439 minutes)
mov al,60 ; AL <- 60 minutes in hour
mul bh ; AX <- recalc hour to minutes
mov bh,0 ; BX = minute
add ax,bx ; AX = hour*60 + minute
; ------------- Merge all items (-> DX:CX, max. 8639999 hundredths)
mov dx,6000 ; DX <- 6000 hundredths in minute
mul dx ; DX:AX <- hour & minute in hundredths
add cx,ax ; CX <- hundredths LOW
adc dx,byte 0 ; DX <- hundredths HIGH
; ------------- Calculate DX:CX * 59659 / 65536 (-> DX:AX, max. 7865199)
xchg ax,dx ; AX <- hundredths HIGH
xchg ax,cx ; AX <- hundredths LOW, CX <- HIGH
mov bx,59659 ; BX <- 59659
mul bx ; DX <- result LOW (e.g. / 65536)
xchg ax,dx ; AX <- result LOW
xchg ax,cx ; CX <- result LOW, AX <- hundr. HIGH
mul bx ; DX:AX <- result
add ax,cx ; AX <- result LOW
adc dx,byte 0 ; DX <- result HIGH
; ------------- Calculate DX:AX / 5 (-> CX:DX, max. 1573039)
mov bx,5 ; BX <- divisor
xchg ax,dx ; AX <- HIGH (= max. 24)
div bl ; AL <- result HIGH
xchg ax,cx ; CL <- result HIGH
mov al,ch ; AL <- remainder
mov ch,0 ; CX = result HIGH
cbw ; AX = remainder
xchg ax,dx ; AX <- LOW, DX <- HIGH
div bx ; calculate result LOW
xchg ax,dx ; DX <- result LOW
ret
; ----------------------------------------------------------------------------
; Pack 4 bytes to BCD
; ----------------------------------------------------------------------------
; INPUT: DH,DL,AH,AL = binary numbers to pack
; OUTPUT: DH,DL,AH,AL = BCD numbers
; ----------------------------------------------------------------------------
; ------------- Pack DX
BinBCDD: xchg ax,dx ; prepare words
call BinBCDW ; pack word in AX to BCD
xchg ax,dx ; return words
; ------------- Pack AH
BinBCDW: xchg al,ah ; prepare bytes
call BinBCDB ; pak byte in AL to BCD
xchg al,ah ; return bytes
; ------------- Push registers
BinBCDB: push cx ; push CX
mov cx,ax ; CH <- push registers AH
; ------------- Pack number in AL
aam ; unpack digits
mov cl,4 ; CL <- number of rotations
shl ah,cl ; shift AH 4 bits left
or al,ah ; AL <- BCD number
; ------------- Pop registers
mov ah,ch ; pop register AH
pop cx ; pop CX
ret
; ----------------------------------------------------------------------------
; Unpack 4 BCD bytes
; ----------------------------------------------------------------------------
; INPUT: DH,DL,AH,AL = BCD numbers to unpack
; OUTPUT: DH,DL,AH,AL = binary numbers
; ----------------------------------------------------------------------------
; ------------- Unpack DX
BCDBinD: xchg ax,dx ; prepare words
call BCDBinW ; unpack BCD word in AX
xchg ax,dx ; return words
; ------------- Unpack AH
BCDBinW: xchg al,ah ; prepare bytes
call BCDBinB ; unpack BCD byte in AL
xchg al,ah ; return bytes
; ------------- Push registers
BCDBinB: push cx ; push CX
xchg ax,cx ; CH <- push register AH
; ------------- Unpack number in AL
mov cl,4 ; CL <- number of rotations
mov ah,al ; AH <- number to unpack
shr ah,cl ; AH = BCD digit HIGH
and al,0fh ; AL = BCD digit LOW
aad ; pack digits to binary
; ------------- Pop registers
mov ah,ch ; pop register AH
pop cx ; pop CX
ret
; ----------------------------------------------------------------------------
; Recalc DOS date to date
; ----------------------------------------------------------------------------
; INPUT: AX = DOS date (days from 1/1/1980)
; DS = data segment
; OUTPUT: CH = century (19 or 20)
; CL = year (0 to 99)
; DH = month (1 to 12)
; DL = day (1 to 31)
; DESTROYS: AX, BX, SI
; ----------------------------------------------------------------------------
; ------------- Add whole quadrenniums
DOS2Date: mov bx,3*365+366 ; BX <- days in one quadrennium
xor dx,dx ; DX <- 0
div bx ; AX <- calc quadrennium, DX <- rest
shl ax,1 ; quadrennium * 2
shl ax,1 ; quadrennium * 4
add ax,19*256+80 ; AX <- add year 1980
xchg ax,cx ; CX <- year
xchg ax,dx ; AX <- days in last quadrennium
; ------------- Add years in last quadrennium (first year is leap)
mov bx,366 ; BX <- days in leap year
DOS2Date2: cmp ax,bx ; is it whole year ?
jb DOS2Date4 ; it is not whole year
inc cx ; add one year to CL
sub ax,bx ; subtract one year from days
mov bx,365 ; BX <- days in non-leap year
jmp short DOS2Date2 ; next year
; ------------- Correct years over 2000
DOS2Date4: cmp cl,99 ; overflow year in century?
jbe DOS2Date5 ; year is OK
sub cl,100 ; CL <- correct year
mov ch,20 ; CH <- 20th century
; ------------- Prepare days in February
DOS2Date5: call PrepareFeb ; prepare days in February
; ------------- Add days in months
cld ; direction up
xchg ax,bx ; BX <- days
mov si,DayMonthTab ; days in months
xor dx,dx ; DH <- 0 month counter
DOS2Date6: inc dh ; DH <- add next month
lodsb ; AL <- load next days in month
cbw ; AX <- days in month
sub bx,ax ; decrease number of days
jnc DOS2Date6 ; next month
add bx,ax ; return last days
; ------------- Days in last month
inc bx ; day correction
mov dl,bl ; DL <- day
ret
; ----------------------------------------------------------------------------
; Recalc date to DOS date
; ----------------------------------------------------------------------------
; INPUT: CH = century (19 or 20)
; CL = year (0 to 99)
; DH = month (1 to 12)
; DL = day (1 to 31)
; DS = data segment
; OUTPUT: AX = DOS date (days from 1/1/1980)
; DESTROYS: BX, CX, DX, SI
; ----------------------------------------------------------------------------
; ------------- Prepare days in February
Date2DOS: call PrepareFeb ; prepare days in February
; ------------- Prepare year from 1980 (-> AX, value 0 to 119)
xchg ax,cx ; AX <- year
cmp ah,20 ; is it century 20xx?
jne Date2DOS2 ; it is not century 20xx
add al,100 ; add 100 years
Date2DOS2: sub al,80 ; offset from 1980
cbw ; AX = years from 1980
; ------------- Calculate quadrennium (-> AX) and year in quadrennium (-> CL)
mov bl,4 ; divisor
div bl ; AH <- quadrennium, AL <- year
mov cl,ah ; CL <- year in quadrennium
cbw ; AX <- quadrennium
; ------------- Days in all quadrenniums (-> CX)
mov bx,dx ; BL <- day, BH <- month
mov dx,3*365+366 ; DX <- days in one quadrennium
mul dx ; AX <- days in all quadrenniums
xchg ax,cx ; CX <- days, AL <- year
; ------------- Days in years in last quadrennium (-> CX)
cbw ; AX <- years in last quadrennium
or ax,ax ; is it first year?
jz Date2DOS3 ; it is first year
mov dx,365 ; DX <- days in one year
mul dx ; AX <- days in last years
inc ax ; correction for leap year
Date2DOS3: add cx,ax ; CX <- days in years
; ------------- Add days in last month
mov al,bl ; AL <- day
cbw ; AX <- day
dec ax ; date correction
add ax,cx ; AX <- days in year and day
xchg ax,bx ; BX <- days, AH <- month
; ------------- Add days in months
mov al,ah ; AL <- month
cbw ; AX <- month
xchg ax,cx ; CX <- month
dec cx ; correction
jz Date2DOS5 ; it is first month
cld ; direction up
mov si,DayMonthTab ; SI <- days in months
Date2DOS4: lodsb ; AL <- load next days in month
cbw ; AX <- days in month
add bx,ax ; increase number of days
loop Date2DOS4 ; next month
Date2DOS5: xchg ax,bx ; AX <- days from 1/1/1980
ret
; ----------------------------------------------------------------------------
; Read system timer
; ----------------------------------------------------------------------------
; OUTPUT: CX:DX = system timer (number of 1/18 sec ticks)
; NOTES: It shifts "current date" when timer overflows 24 hours.
; ----------------------------------------------------------------------------
GetTimer: push ax ; push AX
mov ah,0 ; AH <- function code
int 1ah ; read system timer
mov ah,0 ; AX = flag 24-hours overflow
add [cs:Date],ax ; shift current date
pop ax ; pop AX
ret
; ----------------------------------------------------------------------------
; Prepare days in February
; ----------------------------------------------------------------------------
; INPUT: CL = year (may be relative from 1980 or 1900)
; ----------------------------------------------------------------------------
PrepareFeb: push ax ; push AX
mov al,28 ; AL <- 28 days in non-leap year
test cl,3 ; is it leap year?
jnz PrepareFeb2 ; it is not leap year
inc ax ; AL <- 29 days in leap year
PrepareFeb2: mov [cs:DayMonthFeb],al; store days in February
pop ax ; pop AX
ret
; ----------------------------------------------------------------------------
; Data
; ----------------------------------------------------------------------------
Date: dw 0 ; current date (days from 1/1/1980)
; ------------- Days in months
DayMonthTab: db 31 ; January
DayMonthFeb: db 28 ; February
db 31 ; March
db 30 ; April
db 31 ; May
db 30 ; June
db 31 ; July
db 31 ; August
db 30 ; September
db 31 ; October
db 30 ; November
db 31 ; December
|