home *** CD-ROM | disk | FTP | other *** search
- page 60, 132
- ;
- ; atclock.asm
- ;
- ; An MS-DOS clock device driver that uses the AT real-time
- ; clock rather than the BIOS time-of-day.
- ;
- ; Placed in the public domain.
- ; D. Rifkind 13 Jan 90
- ;
-
- ;
- ; Request header structure
- ;
- ; This is the structure of a device driver request header
- ; for read and write requests, the only ones (other than
- ; initialization) supported.
- ;
-
- reqhdr_t struc
- rh_length db ? ; Length of request header
- rh_unit db ? ; Unit number (ignored)
- rh_command db ? ; Command code
- rh_status dw ? ; Return status location
- db 8 dup (?) ; Reserved
- rh_media db ? ; Media descriptor (ignored)
- rh_address dd ? ; Transfer address (far pointer)
- rh_count dw ? ; Transfer length
- rh_sector dw ? ; Starting sector number (ignored)
- reqhdr_t ends
-
- ;
- ; This is the structure of the request header for the
- ; initialization call. The first 13 bytes are the same as
- ; for all other requests, and not duplicated here.
- ;
-
- reqinit_t struc
- db 13 dup (?) ; Duplicates other requests
- ri_nunits db ? ; Number of units (not supported)
- ri_endaddr dd ? ; Address of end of driver
- ri_bpbptr dd ? ; BPB pointer (not supported)
- reqinit_t ends
-
- ;
- ; Start of driver
- ;
-
- clock segment
- assume cs:clock
-
- ;
- ; Device driver header
- ;
-
- dd_link dw -1, -1 ; Link to next driver: none
- dd_attrib dw 8008h ; Device attribute word:
- ; 8000h - is a character device
- ; 0008h - is the clock device
- dd_stratptr dw strategy ; Offset of strategy entry point
- dd_intptr dw interrupt ; Offset of interrupt entry point
- dd_name db "CLOCK$ " ; Device name (8 characters)
-
- ;
- ; Driver data area
- ;
-
- reqptr label dword ; Pointer to I/O request header
- reqptr_off dw ?
- reqptr_seg dw ?
-
- ;
- ; Commands table
- ;
- ; Array of pointers to individual command handlers. A
- ; zero entry is an unimplemented command.
- ;
-
- commands label word
- dw clk_initialize ; 0 - initialize
- dw 0 ; 1 - media check
- dw 0 ; 2 - build BPB
- dw 0 ; 3 - IOCTL input
- dw clk_input ; 4 - input
- dw clk_input ; 5 - nondestructive input
- dw clk_noop ; 6 - input status
- dw clk_noop ; 7 - input flush
- dw clk_output ; 8 - output
- dw clk_output ; 9 - output with verify
- dw clk_noop ; 10 - output status
- dw clk_noop ; 11 - output flush
- NCOMMANDS equ ($-commands)/2
-
- ;
- ; Strategy entry point
- ;
- ; Like most DOS device drivers, this strategy entry does
- ; nothing but save a pointer to the request header for use
- ; by the interrupt routine.
- ;
-
- strategy proc far
- assume ds:nothing
-
- mov cs:reqptr_off, bx
- mov cs:reqptr_seg, es
- ret
-
- strategy endp
-
- ;
- ; Interrupt entry point
- ;
- ; Sorely misnamed, this routine is called by DOS after
- ; passing the address of the request header to the
- ; strategy routine. This driver handles only a few
- ; requests: read (and nondestructive read), write (and
- ; write with verify), and initialize.
- ;
-
- interrupt proc far
- assume ds:nothing
-
- push ax ; Save the world
- push bx
- push cx
- push dx
- push bp
- push si
- push di
- push ds
- push es
-
- mov ax, cs ; Point DS to driver segment
- mov ds, ax
- assume ds:clock
-
- les di, reqptr ; ES:DI points to request header
-
- mov es:rh_status[di], 8003h
- ; Assume invalid command
- mov bl, es:rh_command[di]
- cmp bl, NCOMMANDS ; Check command in range
- jae interrupt_return
- xor bh, bh
- shl bx, 1
- cmp commands[bx], 0 ; Check for unimplemented command
- jz interrupt_return
-
- call commands[bx] ; Execute command
- les di, reqptr ; Assume the command stepped on this
- mov es:rh_status[di], ax
- ; Set return status
-
- interrupt_return:
- pop es ; Restore old state
- pop ds
- assume ds:nothing
- pop di
- pop si
- pop bp
- pop dx
- pop cx
- pop bx
- pop ax
- ret
-
- interrupt endp
-
- assume ds:clock ; For remainder of driver
-
- ;
- ; month_date
- ;
- ; Used for date conversions. A table of the number of
- ; days from the start of the year to the start of a given
- ; month, for non-leap years.
- ;
-
- month_date label word
- dw 0
- dw 31
- dw 59
- dw 90
- dw 120
- dw 151
- dw 181
- dw 212
- dw 243
- dw 273
- dw 304
- dw 334
- dw 365
-
- ;
- ; clk_noop
- ;
- ; Called for do-nothing requests that return "done" status
- ; and nothing else.
- ;
-
- clk_noop proc near
-
- mov ax, 100h ; "Done"
- ret
-
- clk_noop endp
-
- ;
- ; clk_input
- ;
- ; Reads the time-of-day clock using INT 1Ah services.
- ;
-
- clk_input proc near
-
- cmp es:rh_count[di], 6
- je clk_input_1 ; Only valid requests supported
-
- mov ax, 800Bh ; Call it a read fault
- ret
-
- clk_input_1:
- les di, es:rh_address[di]
- ; Point to transfer address
-
- clk_input_again:
- mov ah, 4h
- int 1Ah ; Get current date...
- push cx ; ...and save it
- push dx
-
- mov ah, 2h
- int 1Ah ; Get current time
-
- mov al, dh
- call frombcd ; Convert to binary
- mov es:[di+5], al ; Current seconds
-
- mov byte ptr es:[di+4], 0
- ; Clock has only 1-sec. resolution
-
- mov al, cl
- call frombcd
- mov es:[di+2], al ; Current minutes
-
- mov al, ch
- call frombcd
- mov es:[di+3], al ; Current hours
-
- mov ah, 4h
- int 1Ah ; Get date again
- pop bx ; Restore saved date
- pop ax
- cmp bx, dx ; Check whether date has changed,
- jne clk_input_again ; and go try again if it has
- cmp ax, cx
- jne clk_input_again
-
- ; Now we have the INT 1Ah date in CX and DX and need to
- ; convert it to days since 1 Jan 80. Start with the
- ; number of days to the start of this month.
-
- mov al, dh
- call frombcd ; Month of year
- dec ax
- mov bx, ax
- shl bx, 1
- mov ax, month_date[bx]
- ; Number of days to start of month
- mov es:[di+0], ax
-
- ; If the current year is a leap year and the current month
- ; is March or later, bump the date to make up for the leap
- ; day.
-
- mov al, cl
- call frombcd
- test al, 3h ; Leap year?
- jnz clk_input_2 ; No - skip it
- cmp dh, 3 ; March or later?
- jb clk_input_2 ; No - skip it
- inc word ptr es:[di+0]
- clk_input_2:
-
- ; Add in the current day of the month.
-
- mov al, dl
- call frombcd ; Day of month
- dec ax
- add word ptr es:[di+0], ax
-
- ; Now we need the current year, 1980-based. I hope we can
- ; do better than MS-DOS by 2000, but I'll check anyhow.
-
- mov al, cl
- call frombcd ; Year within century
- cmp ch, 20h ; Is it 2000 AD yet?
- jb clk_input_3
- add ax, 100 ; Yer puttin' me on...
- clk_input_3:
- sub ax, 80 ; Base on 1980
- mov cx, ax ; Save for later
- mov bx, 365
- mul bx
- add word ptr es:[di+0], ax
-
- ; Now fix up for leap years before the current year.
-
- add cx, 3
- shr cx, 1
- shr cx, 1 ; Leap years before this year
- add word ptr es:[di+0], cx
-
- ; That's it!
-
- mov ax, 100h
- ret
-
- clk_input endp
-
- ;
- ; clk_output
- ;
- ; Sets the real-time clock date and time.
- ;
- ; For those who want to do date conversions in the worst
- ; possible way, this is it: the worst possible way. I
- ; assume that setting the clock is a rare operation, so I
- ; don't care if it takes ten times as long as necessary,
- ; and just want to keep the code size down.
- ;
-
- clk_output proc near
-
- cmp es:rh_count[di], 6
- je clk_output_1 ; Only valid requests supported
-
- mov ax, 800Ah ; Call it a write fault
- ret
-
- clk_output_1:
- les di, es:rh_address[di]
-
- ; The hard part is converting the number-of-days-since-1-
- ; Jan-80 field to year, month, and day. We start by
- ; counting years.
-
- mov ax, es:[di+0] ; Number of days...
- xor cx, cx ; CX = year past 1980
-
- clk_output_2:
- mov bl, cl
- and bl, 3h
- cmp bl, 1 ; Carry set if leap year
- mov bx, 0 ; Don't use XOR 'cause we need CF
- adc bx, 365 ; Number of days this year
- cmp ax, bx
- jb clk_output_3
- sub ax, bx
- inc cx
- jmp clk_output_2
- clk_output_3:
-
- ; Now we need to know the month. Scan through the months
- ; table looking for the first one starting after this
- ; date. The months table has an extra entry to make sure
- ; this ends properly. Some incredibly abstruse fiddling
- ; with carries handles leap years.
-
- mov bx, 2 ; No sense worrying about January
- xor si, si ; Holds previous month's start
- clk_output_4:
- mov dl, cl
- and dl, 3h
- cmp bx, 4 ; Index for March in table
- adc dl, 0
- cmp dl, 1 ; Carry set if leap year and month
- ; March or later
- mov dx, month_date[bx]
- adc dx, 0 ; Fix up leap year dates
- cmp ax, dx
- jb clk_output_5
- mov si, dx
- inc bx
- inc bx
- jmp clk_output_4
- clk_output_5:
- sub ax, si ; Day of month (0-based)
- mov dl, al
- shr bx, 1 ; Month (1-based)
- mov dh, bl
-
- ; Now put that all aside for the moment. We're going to
- ; set the time of day (to nothing useful) to make sure it
- ; doesn't flip the date over while we're updating things.
-
- push cx
- push dx
-
- mov ah, 2h
- int 1Ah ; Just to get the DST flag
- xor cx, cx
- xor dh, dh
- mov ah, 3h
- int 1Ah
-
- ; Date format needs a little adjusting. Convert year-1980
- ; to century/year, day to 1-based, and everything to BCD.
-
- pop dx
- mov al, dl
- inc al
- call tobcd ; Day of month
- mov dl, al
- mov al, dh
- call tobcd ; Month
- mov dh, al
-
- pop ax ; Year - 1980
- add ax, 80
- mov ch, 19h
- cmp ax, 100
- jb clk_output_6
- mov ch, 20h
- sub ax, 100
- clk_output_6:
- call tobcd ; Year within century
- mov cl, al
-
- ; Phew. Now we can set the date.
-
- mov ah, 5h
- int 1Ah
-
- ; Setting the time is a breeze. Just convert parts of it
- ; to BCD and go.
-
- mov ah, 2h
- int 1Ah ; To get DST flag
-
- mov al, es:[di+5]
- call tobcd ; Seconds
- mov dh, al
- mov al, es:[di+2]
- call tobcd ; Minutes
- mov cl, al
- mov al, es:[di+3]
- call tobcd ; Hours
- mov ch, al
-
- mov ah, 3h
- int 1Ah ; Set time
-
- ; Done.
-
- mov ax, 100h
- ret
-
- clk_output endp
-
- ;
- ; frombcd
- ;
- ; Converts a two-digit BCD number in AL to its binary
- ; equivalent in AX. Uses AX and BX but preserves all
- ; other registers.
- ;
-
- frombcd proc near
-
- mov bl, al
- and al, 0Fh ; One's digit
- and bl, 0F0h ; Ten's digit
- shr bl, 1
- add al, bl ; One's + (ten's * 8)...
- shr bl, 1
- shr bl, 1
- add al, bl ; ...+ (ten's * 2)
- xor ah, ah
- ret
-
- frombcd endp
-
- ;
- ; tobcd
- ;
- ; Converts a binary number in AL to its BCD equivalent.
- ; This is a rotten way to do a conversion, but I don't
- ; care. Used only when setting the clock, so it's not
- ; time-critical. AL on entry must be less than 100.
- ;
-
- tobcd proc near
-
- xor ah, ah
- tobcd_loop:
- cmp al, 10
- jb tobcd_return
- sub al, 10
- add ah, 10h
- jmp tobcd_loop
- tobcd_return:
- or al, ah
- ret
-
- tobcd endp
-
- ;
- ; End of resident part of driver
- ;
-
- clock_end equ $
-
- ;
- ; clk_initialize
- ;
- ; Driver initialization. Very little to do here except
- ; set the driver end address.
- ;
-
- clk_initialize proc near
-
- ; Check whether we really have a CMOS clock.
-
- mov ah, 2h
- stc
- int 1Ah
- jnc clk_initialize_1
-
- ; No clock. Some silly person must be trying to install
- ; us on a PC or XT. We'll show them!
-
- mov dd_attrib, 0
- mov es:ri_nunits[di], 0
- mov word ptr es:ri_endaddr+0[di], 0
- mov word ptr es:ri_endaddr+2[di], cs
-
- mov ax, 100h
- ret
-
- clk_initialize_1:
- mov word ptr es:ri_endaddr+0[di], offset clock_end
- mov word ptr es:ri_endaddr+2[di], cs
-
- mov ax, 100h
- ret
-
- clk_initialize endp
-
- clock ends
- end
-