home *** CD-ROM | disk | FTP | other *** search
- ;**********************************************************************
- ;* Copyright (C) 1988, 1989, 1990, 1991, Alan P. Barrett, Durban. *
- ;* *
- ;* Permission is granted to use, copy or distribute this software in *
- ;* any way, except for profit. Modified versions of this software *
- ;* may be distributed, provided that this notice is retained and the *
- ;* modifications are clearly indicated. *
- ;* *
- ;* No liability is accepted for any loss or damage whatsoever caused *
- ;* or allegedly caused by the use or misuse of this software. *
- ;**********************************************************************
-
- ;***********************************************************************
- ;* File: CLOCKDEV.ASM
- ;* Author: A.P. Barrett
- ;* Date created: 06 May 1988
- ;* Date edited: 24 Jul 1991
- ;* Purpose: MS-DOS installable device driver, for CLOCK$ device.
- ;* On an AT: Makes some DOS date and time accesses use the AT BIOS
- ;* real time clock. (The AT clock has a 1 second granularity,
- ;* which is too poor to be used for all time accesses.)
- ;* On a PC with a National Semiconductors MM58167AN clock chip:
- ;* Makes all DOS date and time accesses use the chip.
- ;* On a PC with an MSM6242 clock chip: Makes some DOS date and
- ;* time accesses use the chip. (The chip has a 1 second
- ;* granularity.)
- ;* On a PC with no timer chip, fixes the date change bug present in
- ;* MSDOS 3.2 (and possibly other DOS versions).
- ;* Instal this device driver by placing
- ;* DEVICE=CLOCK.DEV
- ;* in the CONFIG.SYS file. (Some additional options might be required;
- ;* see the help message that is printed on startup.)
- ;* Compile as follows:
- ;* MASM CLOCKDEV ;
- ;* LINK CLOCKDEV ;
- ;* EXE2BIN CLOCKDEV.EXE CLOCK.DEV
- ;* DEL CLOCKDEV.EXE
- ;* DEL CLOCKDEV.OBJ
- ;*
- ;* Please send comments, bug fixes, complaints etc. to the author:
- ;* Alan Barrett, Department of Electronic Engineering, University of
- ;* Natal, Durban, 4001, South Africa.
- ;* RFC822 email address: barrett@ee.und.ac.za
- ;***********************************************************************
-
- ; Reasons for using CLOCK.DEV:
- ;
- ; If you have an AT, you probably have to run the SETUP program
- ; whenever the date or time needs to be changed. The SETUP
- ; program's insistance on rebooting the computer has led to the
- ; availability of several small utilities that set the real time
- ; clock with DOS's idea of the date and time, so instead of running
- ; SETUP you can first use the DOS date and time commands, and then
- ; run some special utility to update the real time clock. Using
- ; CLOCK.DEV will make the DOS date and time commands set the real
- ; time clock, without running any other programs.
- ;
- ; If you have a PC (or XT) with a clock chip, then you probably
- ; have a special program to read and set the time. The program may
- ; be called TIMER, or there may be two programs called SETCLOCK and
- ; GETCLOCK (or some other combination). You have to use a special
- ; command to update the time maintained by the card, because the
- ; DOS date and time commands do not do that. Another problem with
- ; some of these clock cards is that the year does not change
- ; correctly. Using CLOCK.DEV will make the DOS date and time
- ; commands set the real time clock, without running any other
- ; programs, and will ensure that the year changes correctly at the
- ; beginning of January.
- ;
- ; If you use MS-DOS version 3.2, the DOS date will not change at
- ; midnight. Using CLOCK.DEV will fix this problem, even on a PC
- ; without a clock chip. (Some other DOS versions might also have
- ; this problem.)
- ;
- ; If you leave your PC turned on but idle for more than one day (so
- ; that the time passes midnight more than once), then the date will
- ; not be correct; the date will move forward by only one day,
- ; instead of by more than one day. If your PC has a clock chip,
- ; using CLOCK.DEV will correct this problem. If you do not have a
- ; clock chip, there is no way of correcting the problem without
- ; modifying the BIOS.
- ;
- ; How to use CLOCK.DEV:
- ;
- ; Simply add the line
- ; DEVICE=\CLOCK.DEV
- ; to your CONFIG.SYS file. (Include a drive and directory name if
- ; CLOCK.DEV is not stored in the root directory.) When your
- ; computer is booted up, it will determine whether you have an AT
- ; or an add-on clock chip or neither, and will act appropriately.
- ; Although the built in default action will usually be suitable,
- ; there are some options that can be specified in the DEVICE=...
- ; line; try using "DEVICE=CLOCK.DEV -H" to get a list of options,
- ; or just look for the message in the source code.
-
- ;***********************************************************************
-
- ; Modification history:
- ;
- ; Ver 1.2 28 May 1988
- ; First release. Supports AT BIOS clock, supports add-on
- ; National Semiconductor MM58167AN clock chip for XTs,
- ; fixes MS-DOS version 3.2 date change bug.
- ; 15 Jun 1988
- ; Fixed bug in conversion from date (year, month and day)
- ; to day number (since 01-Jan-1980). Comment said "BH is
- ; still zero", but of course it wasn't.
- ; 07 Oct 1988
- ; Improved error reporting when there is no clock chip.
- ; There are now two separate messages, depending on
- ; whether user said "-C+" or"-C=xxxx".
- ; 20 Jun 1989
- ; When time is first established at bootup, and later when
- ; time is changed, be sure to set plain BIOS time,
- ; even if a clock chip is used. This is desirable because
- ; some software uses the plain BIOS time instead of the
- ; DOS time. (Plain BIOS time is INT 1Ah function 0 and 1.
- ; It is not to be confused with AT BIOS time, which is
- ; INT 1Ah function 2, 3, 4 and 5.)
- ; 20 Mar 1990
- ; When the date changes, the BIOS will report the date change
- ; to the first program that asks for the plain BIOS time,
- ; but that might be another program, other than this one.
- ; In such a case, the fact that the date has changed will be
- ; hidden from us. But we can partially fix the problem by
- ; checking if the BIOS time ever seems to run backwards,
- ; and assuming a date change if that does happen.
- ;
- ; Ver 1.3 04 Sep 1990
- ; Added support for MSM6242 clock chip. Changed command line
- ; options slightly. Restructured some code to make
- ; support for other clock chips a little easier to add.
- ; (Memory usage increased from 1488 to 1680 bytes.)
- ; 05 Sep 1990
- ; MSM6242 returns junk in high 4 bits of each port, so ignore
- ; that.
- ;
- ; Ver 1.4 24 Jul 1991
- ; Slight cleanup. No new functionality.
-
- ;***********************************************************************
-
- ; Discussion:
- ;
- ; On the IBM PC, the 8253 timer divides a 1.19318 Mhz clock by 65536 to
- ; generate a time-base interrupt on IRQ0, which corresponds to 8088
- ; hardware interrupt number 8. The BIOS uses this interrupt to increment
- ; a 32-bit counter, in order to keep track of the time-of-day. The BIOS
- ; also uses the time-base interrupt to handle diskette motor timeouts,
- ; and performs an INT 1Ch to allow user written routines to be invoked
- ; once for each time-base interrupt.
- ;
- ; When the BIOS time-of-day count reaches 1573040, it is reset to zero
- ; and an overflow flag is set. The next time the BIOS time-of-day
- ; service (interrupt 1Ah function 0) is called, the overflow flag is
- ; reset, and is returned to the caller. This is a signal to the caller
- ; of the BIOS time-of-day service that the date has changed.
- ;
- ; If the date changes more than once before the BIOS time-of-day service
- ; is called, there is no way for the caller to know this. Thus if a
- ; machine is left idle for several days, the date will change only once.
- ; This behaviour is unpleasant, but cannot be changed without modifying
- ; the BIOS or intercepting either the time-base interrupt or the user
- ; tick interrupt.
- ;
- ; MS-DOS interrupt 21h functions 2Ah, 2Bh, 2Ch and 2Dh are used to get
- ; and set the date and time. MS-DOS implements these functions via a
- ; device driver whose attributes indicate that it is a clock device.
- ; This device is usually named CLOCK$. Reading and writing the clock
- ; device gets and sets the date and time. The clock device presumably
- ; relies on the BIOS time-of-day service for the time, and maintains its
- ; own record of the current date. When the BIOS indicates that the date
- ; has changed, the DOS updates its idea of the current date.
- ;
- ; Versions of MS-DOS up to 3.1 behaved as just described, but MS-DOS
- ; version 3.2 does not ever increment the current date. This behaviour
- ; is entirely unacceptable, because at midnight the time changes but the
- ; date does not. If the date stamps on files are used to keep track of
- ; which files are newer than others, then files modified just after
- ; midnight will appear to be OLDER than files modified just before
- ; midnight.
- ;
- ; The IBM-PC AT (and compatibles) contains a built-in real time clock,
- ; which is automatically read when the machine is booted up. The AT BIOS
- ; provides functions to read and set the real time clock. (BIOS interrupt
- ; 1Ah, functions 2, 3, 4 and 5.) Changing the time in the real time clock
- ; is usually done only by the SETUP program. The clock has a resolution
- ; of 1 second.
- ;
- ; Many users instal peripheral cards containing real time clock chips
- ; that continue to function when the power is turned off. These cards
- ; are usually supplied together with programs to change their idea of
- ; the date and time, and to make the DOS idea of the date and time
- ; match the card's idea of the date and time. The clock chip is usually
- ; consulted only when the computer is booted up. A problem with many
- ; clock chips (or rather, with the programs supplied to control the
- ; chips) is that the year may not change correctly.
- ;
- ; This installable device driver is a replacement for the DOS clock
- ; device, and is an attempt to solve the following problems:
- ;
- ; The DOS date and time must never go backwards. When the BIOS
- ; indicates that the date has changed, THE DATE MUST CHANGE.
- ; (The MS-DOS 3.2 date change bug is fixed by this program.)
- ;
- ; The real time clock maintained by the BIOS on an AT computer
- ; (and usually accessible only via the SETUP program) should
- ; be used for all DOS date and time functions.
- ;
- ; The AT BIOS clock has a one-second resolution. This is
- ; improved by usually reading the ordinary BIOS timer, and only
- ; using the AT BIOS clock when the date changes or after a long
- ; idle period, or when the user changes the date or time.
- ;
- ; If there is a real time clock on a peripheral card, the DOS
- ; date and time should match that on the peripheral card.
- ;
- ; If there is a real time clock on a peripheral card, the year
- ; should change correctly. The software provided with some
- ; such add-on clock chips doesn't work properly.
- ;
- ; The date should change the correct number of times if the
- ; computer is left unattended for several days.
- ; (Not fixed yet, for machines without clock chips.)
- ;
-
- ;***********************************************************************
-
- ;
- ; These definitions control the conditional assembly
- ;
-
- False = 0
- True = not False
-
- ;
- ; Make do_debug_writes True to get lots of additional output that
- ; may assist in debugging. The macros that actually perform the
- ; debugging output are disabled if do_debug_writes is False.
- ;
-
- do_debug_writes = False
-
- ;
- ; Make add_test_code True to include additional code for testing.
- ;
-
- add_test_code = False
-
- ;
- ; It may be necessary to switch to a local stack. This is because
- ; DOS does not guarantee to have more than 40 free words of stack
- ; space when the device driver is called. Even the 40 words mentioned
- ; in the Programmer's Reference is probably not guaranteed.
- ; This driver uses about 30 words of stack space if the debug output
- ; is disabled, so use_local_stack can be set to False. ????
- ; When debugging output is performed, much more stack space is used,
- ; so use_local_stack should be True, with a reasonably large
- ; local_stack_size.
- ; Note that the size of the local stack is specified in BYTES.
- ; Remember that any interrupts occuring while this program is busy
- ; will need space on the stack.
- ;
-
- if do_debug_writes
- use_local_stack = True ; Strongly recommend this be set to TRUE
- local_stack_size = 300
- else
- use_local_stack = True ; Try TRUE if FALSE gives problems
- local_stack_size = 160
- endif
-
- ;***********************************************************************
-
- ;
- ; Define the order of segments in memory
- ;
- ; All the segments go together in a GROUP, so the whole thing will
- ; actually be just one 64k segment.
- ;
- ; The reason for defining multiple segments here is so that the source code
- ; can be written in any order, and the linker will place everything in the
- ; correct order in the executable file. We particularly want to ensure
- ; that non-resident code and data appears after the resident code and data,
- ; so that its space can be freed after initialisation. We must also
- ; ensure that the header comes first in memory.
- ;
-
- header_segment segment byte
- header_segment ends
- cgroup group header_segment
-
- resident_data segment byte
- resident_data ends
- cgroup group resident_data
-
- resident_code segment byte
- resident_code ends
- cgroup group resident_code
-
- if use_local_stack
- local_stack_seg segment word
- ;;; db '-->'
- db (local_stack_size+7)/8 dup (' STACK ')
- even ; Ensure word alignment
- local_stack_top label word
- ;;; db '<--'
- local_stack_seg ends
- cgroup group local_stack_seg
- endif ; use_local_stack
-
- startup_data segment byte
- end_resident_memory label byte ; This must be the start of the section
- ; that is used only at startup time
- startup_data ends
- cgroup group startup_data
-
- startup_code segment byte
- startup_code ends
- cgroup group startup_code
-
- identity_seg segment byte
- identity_seg ends
- cgroup group identity_seg
-
- ;
- ; CS will point to the CGROUP segment while the code is running.
- ;
- assume cs:cgroup
-
- ;***********************************************************************
-
- ;
- ; This is the message printed at initialisation. We arrange for the
- ; message to be the last thing in the executable file so that it
- ; is easy to find.
- ;
-
- identity_seg segment
- start_message label byte
- db 'CLOCKDEV version 1.4 [24 Jul 1991]. Copyright (C) A.P. Barrett',13,10
- start_message_len equ $-start_message
- identity_seg ends
-
- ;***********************************************************************
-
- ;
- ; Some useful macro definitions
- ;
-
- ;
- ; Push all registers except CS, IP, SS and SP.
- ;
- save_regs macro
- pushf
- push ax
- push bx
- push cx
- push dx
- push es
- push ds
- push si
- push di
- push bp
- endm
-
- ;
- ; Pop all registers saved by the save_regs macro
- ;
- restore_regs macro
- pop bp
- pop di
- pop si
- pop ds
- pop es
- pop dx
- pop cx
- pop bx
- pop ax
- popf
- endm
-
- ;
- ; JCXNZ: jump if CX is non zero.
- ;
- jcxnz macro dest
- local l1
- jcxz l1
- jmp short dest
- l1:
- endm
-
- ;
- ; Conditional jump, where the target is too far away.
- ; Use, for example, <jcond je, dest> instead of <je dest>.
- ;
- ; There must be a better way of doing this ???
- ;
- jcond macro type,dest
- local next, l1
- ifidni <type>, <jcxz> ; CX register zero
- jcxz l1
- jmp short next
- endif
- ifidni <type>, <jcxnz> ; CX register non zero (really a macro)
- jcxz next
- endif
- ifidni <type>, <jc> ; carry flag set
- jnc next
- endif
- ifidni <type>, <jnc> ; carry flag clear
- jc next
- endif
- ifidni <type>, <jz> ; zero flag set
- jnz next
- endif
- ifidni <type>, <jnz> ; zero flag clear
- jz next
- endif
- ifidni <type>, <jo> ; overflow flag set
- jno next
- endif
- ifidni <type>, <jno> ; overflow flag clear
- jo next
- endif
- ifidni <type>, <js> ; sign flag set (negative)
- jns next
- endif
- ifidni <type>, <jns> ; sign flag clear (positive)
- js next
- endif
- ifidni <type>, <jp> ; parity flag set (even parity)
- jnp next
- endif
- ifidni <type>, <jnp> ; parity flag clear (odd parity)
- jp next
- endif
- ifidni <type>, <jpe> ; even parity
- jpo next
- endif
- ifidni <type>, <jpo> ; odd parity
- jpe next
- endif
- ifidni <type>, <je> ; equal
- jne next
- endif
- ifidni <type>, <jl> ; less (signed)
- jnl next
- endif
- ifidni <type>, <jg> ; greater (signed)
- jng next
- endif
- ifidni <type>, <jb> ; below (unsigned)
- jnb next
- endif
- ifidni <type>, <ja> ; above (unsigned)
- jna next
- endif
- ifidni <type>, <jle> ; less or equal (signed)
- jnle next
- endif
- ifidni <type>, <jge> ; greater or equal (signed)
- jnge next
- endif
- ifidni <type>, <jbe> ; below or equal (unsigned)
- jnbe next
- endif
- ifidni <type>, <jae> ; above or equal (unsigned)
- jnae next
- endif
- ifidni <type>, <jne> ; not equal
- je next
- endif
- ifidni <type>, <jnl> ; not less (signed)
- jl next
- endif
- ifidni <type>, <jng> ; not greater (signed)
- jg next
- endif
- ifidni <type>, <jnb> ; not below (unsigned)
- jb next
- endif
- ifidni <type>, <jna> ; not above (unsigned)
- ja next
- endif
- ifidni <type>, <jnle> ; not less or equal (signed)
- jle next
- endif
- ifidni <type>, <jnge> ; not greater or equal (signed)
- jge next
- endif
- ifidni <type>, <jnbe> ; not below or equal (unsigned)
- jbe next
- endif
- ifidni <type>, <jnae> ; not above or equal (unsigned)
- jae next
- endif
- l1: jmp dest
- next:
- endm
- ;
- ; If debugging is disabled, the following macros are defined as do-nothing.
- ; If debugging is enabled, they are defined in the normal way.
- ;
- if do_debug_writes
-
- ;
- ; Display a message for debugging
- ;
- debug_message macro msg
- local m, m_len
- ; The message must appear in memory somewhere
- resident_data segment
- m db msg
- m_len equ $-m
- resident_data ends
- ; Save registers
- push cx
- push si
- push ds
- ; Print the message
- push cs
- pop ds
- mov si,offset cgroup:m
- mov cx,m_len
- call display_message
- ; Restore registers
- pop ds
- pop si
- pop cx
- endm
-
- ;
- ; Display a character (for debugging)
- ;
- debug_show_char macro chr
- ; Save regs
- pushf
- push ax
- push bx
- ; Display
- mov ah,0Eh
- mov al,chr
- mov bx,1
- int 10h
- ; Restore regs
- pop bx
- pop ax
- popf
- endm
-
- ;
- ; Display a byte in hexadecimal (for debugging)
- ;
- debug_hex_byte macro byt
- push ax
- mov al,byt
- call display_hex_byte
- pop ax
- endm
-
- ;
- ; Display a string of hex bytes, starting at seg:addr (for debugging)
- ;
- debug_hex_string macro seg,addr,count,count_size
- push cx
- push si
- push ds
- push seg
- pop ds
- lea si,addr
- ifidni <count_size>, <byte>
- mov cl,count
- xor ch,ch
- else
- mov cx,count
- endif
- call display_hex_bytes
- pop ds
- pop si
- pop cx
- endm
- ;
- ; Display a word in hexadecimal (for debugging)
- ;
- debug_hex_word macro wrd
- push ax
- mov ax,wrd
- xchg al,ah
- call display_hex_byte
- mov al,ah
- call display_hex_byte
- pop ax
- endm
-
- ;
- ; Display a DWORD in hexadecimal. THE VALUE MUST BE IN CX_DX. For debugging.
- ;
- debug_hex_cx_dx macro
- push ax
- mov al,ch
- call display_hex_byte
- mov al,cl
- call display_hex_byte
- mov al,dh
- call display_hex_byte
- mov al,dl
- call display_hex_byte
- pop ax
- endm
-
- else ; do_debug_writes
- ;
- ; Empty definitions for the debug macros, if debugging is turned off.
- ;
- debug_message macro msg
- endm
- debug_show_char macro chr
- endm
- debug_hex_byte macro byt
- endm
- debug_hex_string macro seg,addr,count,count_size
- endm
- debug_hex_word macro wrd
- endm
- debug_hex_cx_dx macro
- endm
- endif ; do_debug_writes
-
- ;***********************************************************************
-
- ;
- ; Equates for DOS device driver attributes
- ;
-
- D_ATT_chr equ 1000000000000000b ; Character device
- D_ATT_blk equ 0000000000000000b ; Block device (chr bit is off)
- D_ATT_ioc equ 0100000000000000b ; Supports IOCTL
- D_ATT_oub equ 0010000000000000b ; Output until busy (char only)
- D_ATT_fat equ 0010000000000000b ; Uses FATID byte (block only)
- D_ATT_opn equ 0000100000000000b ; Understands Open/Close
- D_ATT_3_2 equ 0000000001000000b ; Supports DOS 3.2 functions
- D_ATT_clk equ 0000000000001000b ; This is the clock device
- D_ATT_nul equ 0000000000000100b ; This is the NUL device
- D_ATT_sto equ 0000000000000010b ; This is the std. output device
- D_ATT_sti equ 0000000000000001b ; This is the std. input device
-
- ;
- ; Equates for offsets of various items within driver requests
- ;
-
- ; Items in the request header
- D_REQ_len equ 0 ; Length of the request block
- D_REQ_unit equ 1 ; Device subunit number
- D_REQ_command equ 2 ; Command code number
- D_REQ_status equ 3 ; Returned status code
-
- ; Additional items in the INIT request
- D_INIT_end equ 14 ; Points to end of resident part
- D_INIT_args equ 18 ; Points to command line arguments
-
- ; Additional items in read, write and IOCTL requests
- D_RDWR_media equ 13 ; Media byte from BPB
- D_RDWR_buffer equ 14 ; Address of transfer buffer
- D_RDWR_length equ 18 ; Number of chars or sectors
- D_RDWR_startsec equ 20 ; Start sector num. (block device)
-
- ; Additional item in the non-destructive read request
- D_PEEK_char equ 13 ; Returned character
-
- ;
- ; Equates for request completion status codes.
- ; These are all byte values, to be stored in the high byte of the
- ; status word.
- ;
-
- D_STAT_error equ 10000000b ; Error
- D_STAT_busy equ 00000010b ; Device busy
- D_STAT_done equ 00000001b ; Request complete
-
- ;
- ; Equates for request error codes.
- ; These are to be stored in the low byte of the status word when the
- ; ERROR bit is set in the high byte.
- ;
-
- D_ERR_write_prot equ 0 ; Write protect violation
- D_ERR_bad_unit equ 1 ; Unknown unit number
- D_ERR_not_ready equ 2 ; Device not ready
- D_ERR_bad_command equ 3 ; Unknown command
- D_ERR_CRC equ 4 ; CRC error
- D_ERR_bad_length equ 5 ; Bad request structure length
- D_ERR_seek equ 6 ; Seek error
- D_ERR_unknown_media equ 7 ; Unknown media
- D_ERR_sector equ 8 ; Sector not found
- D_ERR_paper equ 9 ; Printer out of paper
- D_ERR_write equ 0Ah ; Write fault
- D_ERR_read equ 0Bh ; Read fault
- D_ERR_general equ 0Ch ; General failure
- D_ERR_bad_change equ 0Fh ; Invalid disk change
-
- ;***********************************************************************
-
- ;
- ; CODE STARTS HERE
- ;
-
- header_segment segment
- assume ds:nothing, es:nothing
-
- ;
- ; First, we need a special header so that DOS recognises the driver correctly.
- ;
-
- CLK_header label byte
- dw -1,-1 ; List linkage filled in when DOS
- ; installs the device driver
- dw D_ATT_chr+D_ATT_clk ; Attribute word
- dw offset cgroup:CLK_strategy ; STRATEGY entry point
- dw offset cgroup:CLK_interrupt ; "INTERRUPT" entry point
- db 'CLOCK$ ' ; Name, must be 8 chars
-
- ;
- ; For debugging only: Something to search for in memory to help find
- ; where the driver has been loaded.
- ; When not debugging, comment this out to save a few bytes of memory.
- ;
- ;;; db 'APB'
-
- header_segment ends
-
- ;***********************************************************************
-
- ;
- ; Data used by the resident portion of the CLOCK$ device driver.
- ;
-
- resident_data segment
-
- ;
- ; Pointer to the header of the current device request.
- ;
- request_pointer dd ?
-
- if use_local_stack
- ;
- ; Saved values needed for switching to a local stack
- ;
- saved_ax dw ?
- saved_ss dw ?
- saved_sp dw ?
- endif ; use_local_stack
-
- ;
- ; The current date and time.
- ; This is stored in the format required by the DOS, so a READ or WRITE
- ; is accomplished by copying the six-byte data block either to or from here.
- ;
- ; The order of the following data definitions is important.
- ;
- CLK_date_time_len equ 6 ; Length of the date and time data
- CLK_date_time label byte
- CLK_date_days dw 0 ; Days since 01-Jan-1980
- CLK_time_min db 0 ; Current time: minutes
- CLK_time_hour db 0 ; Current time: hour
- CLK_time_hsec db 0 ; Current time: hundredths of secs
- CLK_time_sec db 0 ; Current time: seconds
-
- ;
- ; Some more date values, used internally by various procedures.
- ;
- CLK_date_century db 0 ; First two digits of year
- CLK_date_year db 0 ; Last two digits of year
- CLK_date_month db 0 ; Current month
- CLK_date_day db 0 ; Day of month
- CLK_date_weekday db 0 ; Day of week (0 = Sunday)
-
- ;
- ; This command table is used by the CLOCK$ device "interrupt" handler.
- ; Each word is a pointer to a routine that handles a particular function.
- ;
- CLK_command_table label byte
- dw offset cgroup:CLK_init ; 0: Instal the device driver
- dw offset cgroup:bad_command ; 1: Media check (block dev.)
- dw offset cgroup:bad_command ; 2: Build BPB (block dev.)
- dw offset cgroup:bad_command ; 3: IOCTL input
- dw offset cgroup:CLK_input ; 4: Input (get date, time)
- dw offset cgroup:CLK_peek ; 5: Non destr. read no wait
- dw offset cgroup:exit_ok ; 6: Input status query
- dw offset cgroup:exit_ok ; 7: Input flush
- dw offset cgroup:CLK_output ; 8: Output (set date, time)
- dw offset cgroup:CLK_output ; 9: Output with verify
- dw offset cgroup:exit_ok ;10: Output status query
- dw offset cgroup:exit_ok ;11: Output flush
-
- CLK_last_command equ 11
- ; all other function codes are treated as bad commands
-
- ;
- ; The timer chip base address is determined during initialisation.
- ;
- CLK_chip_IO_base dw 0FFFFh ; I/O base address for clock chip.
- ; Default value FFFF means look in all usual places.
- ; Value zero means do not use chip.
- ; Other value is place to look.
-
- ;
- ; This pointer specifies a subroutine to be called by the
- ; CLK_fix_our_time procedure. The pointer is set by the
- ; startup code.
- ;
- CLK_get_procedure_ptr dw ?
-
- ;
- ; This pointer specifies a subroutine to be called by the
- ; CLK_set_other_timers procedure. The pointer is set by the
- ; startup code.
- ;
- CLK_set_procedure_ptr dw ?
-
- ;
- ; When a coarse grain clock chip is used, CLK_get_procedure_ptr
- ; will point to CLK_get_BIOS_or_other_time, and this pointer
- ; will point to a subroutine that will get the time from the
- ; 'other' source. This is set by the startup code.
- ;
- CLK_get_other_procedure_ptr dw ?
-
- ;
- ; These words are used for temporary storage of intermediate results.
- ;
- t1 dw ?
- t2 dw ?
- t3 dw ?
- t4 dw ?
- t5 dw ?
-
- resident_data ends
-
- ;***********************************************************************
-
- ;
- ; A null procedure. May be pointed to by CLK_set_procedure_ptr.
- ;
-
- resident_code segment
- null_proc proc near
- ret
- null_proc endp
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; The strategy entry point is called by DOS to pass the request block.
- ; The driver simply saves the address of the request block and returns.
- ; On multi-tasking systems, the driver should add the request to a queue.
- ;
-
- resident_code segment
-
- CLK_strategy proc far
- ;
- ; ES:BX points to the request block. Just save the address and return
- ;
- mov word ptr cs:[request_pointer],bx
- mov word ptr cs:[request_pointer+2],es
- ret ; FAR return
-
- CLK_strategy endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; The interrupt entry point handles the request that was saved by the
- ; strategy entry point. DOS calls it immediately after the strategy
- ; routine returns.
- ;
-
- resident_code segment
-
- CLK_interrupt proc far
- if use_local_stack
- ;
- ; Switch to a local stack, in case the stack is too small.
- ;
- mov cs:[saved_ax],ax
- mov ax,sp
- mov cs:[saved_sp],ax
- mov ax,ss
- mov cs:[saved_ss],ax
- mov ax,cs
- mov ss,ax
- mov sp,offset cgroup:local_stack_top
- mov ax,cs:[saved_ax]
- endif ; use_local_stack
- ;
- ; Save registers
- ;
- save_regs
- ;
- ; Make DS:BX point to the request header
- ;
- lds bx,cs:[request_pointer]
- debug_message '{ request header is at '
- debug_hex_word ds
- debug_show_char ':'
- debug_hex_word bx
- debug_message ' data is '
- debug_hex_string ds,[bx],<byte ptr ds:[bx]>,byte
- debug_message <'}',13,10>
- ;
- ; Get the command code, jump to error handler for bad command,
- ; look up the command in the table
- ;
- mov al,ds:[bx].D_REQ_command ; Get the command code
- cmp al,CLK_last_command ; See if it is too large
- jcond jg, bad_command ; Error if bad code
- cbw ; Sign extend AL to AX
- mov si,offset cgroup:CLK_command_table
- add si,ax
- add si,ax ; Now CS:SI points to the entry
- ; in the command table
- ;
- ; Now jump to the appropriate handler subroutine.
- ;
- jmp word ptr cs:[si] ; jump to the appropriate routine
-
- CLK_interrupt endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Routines for various operations required by the CLOCK$ "interrupt"
- ; handler. One of these routines is called with DS:BX pointing to the
- ; request header, and with the command code in AL.
- ;
-
- resident_code segment
-
- ;
- ; Request code 4: Input
- ;
- CLK_input proc near
- debug_message '{ CLOCK$ input '
- ;
- ; Make sure our time is correct.
- ;
- push bx
- call CLK_fix_our_time ; Get the new time value
- pop bx
- ;
- ; Get the requested transfer length from the request header.
- ; This is the size of the callers buffer, so make it smaller
- ; if necessary, because we will never return more than CLK_date_time_len
- ; bytes. If count is changed from the requested value, inform the caller.
- ;
- mov cx,word ptr ds:[bx].D_RDWR_length ; Requested length
- debug_message '( request length '
- debug_hex_word cx
- debug_message ' ) '
- cmp cx,CLK_date_time_len ; Check if CX is too large
- jbe CLK_inp_len_ok ; Skip ahead if count OK
- mov cx,CLK_date_time_len ; Set CX to max size
- mov word ptr ds:[bx].D_RDWR_length,cx ; Return true length
- CLK_inp_len_ok:
- jcond jcxz, exit_ok ; Exit if there is nothing to do
- ;
- ; Get the address of the caller's buffer, from the request header.
- ; Copy data from the current date and time buffer to the caller's buffer.
- ;
- les di,dword ptr ds:[bx].D_RDWR_buffer ; Destination address
- mov si,offset cgroup:CLK_date_time ; Make DS:SI point to source
- push cs
- pop ds
- cld ; Make sure the move goes forwards
- debug_message '( move from DS:SI = '
- debug_hex_word ds
- debug_show_char ':'
- debug_hex_word si
- debug_message ' to ES:DI = '
- debug_hex_word es
- debug_show_char ':'
- debug_hex_word di
- debug_message ' length '
- debug_hex_word cx
- debug_message ' ) '
- debug_message '{ data: '
- debug_hex_string ds,[si],cx,word
- debug_message '} '
- rep movsb ; Do the data transfer
- jmp exit_ok
- CLK_input endp
-
- ;
- ; Request code 5: Non destructive read, no wait
- ;
- CLK_peek proc near
- debug_message '{ CLOCK$ peek '
- ;
- ; Make sure our time is correct.
- ;
- push bx
- call CLK_fix_our_time ; Get the new time value
- pop bx
- ;
- ; Return first byte from the current date buffer
- ;
- mov al,byte ptr cs:[CLK_date_time] ; First byte of day
- mov ds:[bx].D_PEEK_char,al ; Return the byte
- jmp exit_ok ; Finished successfully
- CLK_peek endp
-
- ;
- ; Request code 8: Output
- ; Request code 9: Output with verify
- ;
- CLK_output proc near
- debug_message '{ CLOCK$ output '
- ;
- ; Get the requested transfer length
- ;
- mov cx,word ptr ds:[bx].D_RDWR_length ; Length of write
- ;
- ; CX contains the size of the callers buffer. If it is too small, then
- ; ignore the write request. If it is too large, perform the write as
- ; usual, but ignore any extra data in the buffer. In either case, let the
- ; caller believe that the write was successful.
- ;
- debug_message '( request length '
- debug_hex_word cx
- debug_message ' ) '
- cmp cx,CLK_date_time_len ; Check if length is OK
- jcond jb, bad_length ; Error if length is too small
- mov cx,CLK_date_time_len ; Set CX to max size
- ;
- ; Get address of caller's data buffer from the request header.
- ; Make sure the caller's data is valid
- ;
- lds si,dword ptr ds:[bx].D_RDWR_buffer ; Source data address
- call CLK_check_data_validity ; Check for bad date or time
- jcond jc, general_failure ; Return error code
- ;
- ; Copy data from the caller's buffer to the current date and time buffer.
- ;
- push cs ; Make ES:DI point to destination
- pop es ;
- mov di,offset cgroup:CLK_date_time ;
- cld ; Make sure the move goes forwards
- debug_message '( move from DS:SI = '
- debug_hex_word ds
- debug_show_char ':'
- debug_hex_word si
- debug_message ' to ES:DI = '
- debug_hex_word es
- debug_show_char ':'
- debug_hex_word di
- debug_message ' length '
- debug_hex_word cx
- debug_message ' ) '
- debug_message '{ data: '
- debug_hex_string ds,[si],cx,word
- debug_message '} '
- rep movsb ; Do the data transfer
- ;
- ; Make sure other timers get told about the new time.
- ;
- call CLK_set_other_timers
- jmp short exit_ok
- CLK_output endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Device "interrupt" handlers jump to one of these labels when they have
- ; finished. If there is more than one device driver in the same source
- ; file, they can all share these routines.
- ;
-
- resident_code segment
-
- exit_proc proc far
-
- ;
- ; Here for successful completion
- ;
- exit_ok:
- debug_message '(ok) '
- mov ax,D_STAT_done shl 8 ; Request completed
- if do_debug_writes
- jmp exit_save_status
- else
- jmp short exit_save_status
- endif
-
- ;
- ; Here for bad command
- ;
- bad_command:
- debug_message '(bad command) '
- mov al,D_ERR_bad_command
- jmp short exit_error
-
- ;
- ; Here for bad length
- ;
- bad_length:
- debug_message '(bad length) '
- mov al,D_ERR_bad_length
- jmp short exit_error
-
- ;
- ; Here for general failure
- ;
- general_failure:
- debug_message '(invalid data) '
- mov al,D_ERR_general
-
- ;
- ; Here to return error code
- ;
- exit_error:
- mov ah,D_STAT_error+D_STAT_done ; Completed with error
-
- ;
- ; Here to save the status code that is already in AH (and error code in AL)
- ;
- exit_save_status:
- ;
- ; Make DS:BX point to the request header, then save the status word
- ;
- lds bx,cs:[request_pointer]
- mov word ptr [bx].D_REQ_status,ax
- ;
- ; Restore registers and exit
- ;
- debug_message <'}',13,10>
- restore_regs
- if use_local_stack
- ;
- ; Switch back to the normal stack.
- ;
- mov cs:[saved_ax],ax
- mov ax,cs:[saved_ss]
- mov ss,ax
- mov sp,cs:[saved_sp]
- mov ax,cs:[saved_ax]
- endif ; use_local_stack
- ret ; FAR return
-
- exit_proc endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Convert dates between various formats.
- ;
-
- resident_data segment
-
- ;
- ; This table contains the cumulative days per month,
- ; not allowing for leap years.
- ;
- CLK_month_length_table label word
- dw 0, 31, 59, 90, 120, 151, 181 ; 0, Jan, Jan+Feb, ...
- dw 212, 243, 273, 304, 334, 365 ; ..., Jan+Feb+...+Dec
-
- resident_data ends
-
- resident_code segment
-
- ;
- ; Convert from years, months, days
- ; to number of days since 1-Jan-1980
- ;
-
- CLK_conv_ccyymmdd_days proc near
- assume ds:cgroup
- debug_message '{ conv_ccyymmdd_days '
- ;
- ; Find number of years since 1980. Treat 1980 as year number 0.
- ; This should never be more than 100, if we assume that the
- ; IBM-PC will be obsolete by the year 2079.
- ;
- mov al,[CLK_date_century]
- debug_message '( century '
- debug_hex_byte al
- debug_message ' ) '
- mov bx,100
- mul bl
- mov bl,[CLK_date_year]
- debug_message '( year '
- debug_hex_byte bl
- debug_message ' ) '
- add ax,bx
- sub ax,1980
- debug_message '( years since 1980 '
- debug_hex_word ax
- debug_message ' ) '
- mov cx,ax ; Save number of years for later
- ;
- ; Convert number of years to number of days.
- ;
- call CLK_conv_years_days
- debug_message '( days from years '
- debug_hex_word ax
- debug_message ' ) '
- ;
- ; Add the number of days for completed months this year.
- ; This is found from a lookup table.
- ;
- mov bl,[CLK_date_month]
- debug_message '( month '
- debug_hex_byte bl
- debug_message ' ) '
- xor bh,bh
- shl bx,1 ; Each entry is two bytes
- mov bx,word ptr ds:[CLK_month_length_table][bx-2]
- debug_message '( days from months '
- debug_hex_word bx
- debug_message ' ) '
- add ax,bx
- ;
- ; If it March or later, and if this year is a leap year, add another day.
- ;
- test [CLK_date_year],3 ; Is the year a multiple of 4 ?
- jnz CLK_conv_ccyymmdd_not_leap
- cmp [CLK_date_month],3 ; Is it March or later ?
- jb CLK_conv_ccyymmdd_not_leap
- inc ax ; Add another day
- debug_message '( Add 1 for leap year March or later ) '
- CLK_conv_ccyymmdd_not_leap:
- ;
- ; Add the current day of the month, less 1.
- ;
- mov bl,[CLK_date_day]
- debug_message '( day of month '
- debug_hex_byte bl
- debug_message ' ) '
- dec bl
- xor bh,bh
- add ax,bx
- debug_message '( total days '
- debug_hex_word ax
- debug_message ' ) '
- ;
- ; Store the result.
- ;
- mov [CLK_date_days],ax
- debug_message '} '
- ret
- CLK_conv_ccyymmdd_days endp
-
- ;
- ; Convert from number of days since 1-Jan-1980
- ; to years, months, days.
- ;
-
- CLK_conv_days_ccyymmdd proc near
- debug_message '{ conv_days_ccyymmdd '
- ;
- ; Divide the day number by 365.25 to get number of years.
- ; There is no need to worry about the century years not being leap years,
- ; because this software was not running in 1900; 2000 will be a leap year;
- ; and it is unlikely that this software will be running in the year 2100.
- ;
- ; This division is done by multiplying by 4 and dividing by 1461. The
- ; intermediate result will be longer than 16 bits, but that poses no problems.
- ;
- mov ax,[CLK_date_days]
- debug_message '( days '
- debug_hex_word ax
- debug_message ' ) '
- mov bx,4
- mul bx
- mov bx,1461
- div bx
- debug_message '( divide by 365.25 gives '
- debug_hex_word ax
- debug_message ' years since 1980 ) '
- ;
- ; Now the number of years is in AX. The additional days (remainder)
- ; in DX may be incorrect.
- ;
- ; Add 1980 and convert to year and century values.
- ;
- mov cx,ax ; Save for later
- add ax,1980
- mov bl,100
- div bl
- mov [CLK_date_century],al
- mov [CLK_date_year],ah
- debug_message '( century '
- debug_hex_byte al
- debug_message ' ) ( year '
- debug_hex_byte ah
- debug_message ' ) '
- ;
- ; Find the number of days accounted for by the number of full years.
- ; Subtract that from the total days to get number of days this year.
- ; Add 1 to make the first of January day number 1.
- ;
- mov ax,cx
- call CLK_conv_years_days
- debug_message '( days from full years '
- debug_hex_word ax
- debug_message ' ) '
- mov bx,[CLK_date_days]
- xchg ax,bx
- sub ax,bx
- inc ax
- debug_message '( day within year '
- debug_hex_word ax
- debug_message ' ) '
- ;
- ; If it is a leap year and the day-within-year is 60, then it must be 29-Feb.
- ; If a leap year and the day is above 60, subtact one from the day number.
- ; This will allow the month to be found by searching through the cumulative
- ; month length table.
- ;
- mov cl,[CLK_date_year]
- test cl,3 ; Is it a multiple of 4 ?
- jnz CLK_conv_days_not_leap
- cmp ax,60 ; Is is 29-Feb ?
- jb CLK_conv_days_not_leap ; Actually, it is a leap year,
- ; but it is still Jan or Feb
- jne CLK_conv_days_leap
- debug_message '( leap year 29-Feb ) '
- mov [CLK_date_month],2 ; It is 29-Feb
- mov [CLK_date_day],29
- jmp CLK_conv_days_end
- CLK_conv_days_leap:
- debug_message '( subtract 1 for leap year March or later ) '
- dec ax ; Subtract a day, because it is
- ; March or later in a leap year
- CLK_conv_days_not_leap:
- ;
- ; Find the month by searching through the cumulative month length
- ; table for an entry larger than or equal to the day number in AX.
- ; (AX=1 for 1-Jan.)
- ;
- mov bx,2
- CLK_conv_days_month_loop:
- cmp ax,word ptr ds:[CLK_month_length_table][bx]
- jbe CLK_conv_days_end_month_loop
- add bx,2
- jmp CLK_conv_days_month_loop
- CLK_conv_days_end_month_loop:
- mov cx,bx
- shr cx,1
- debug_message '( month from lookup '
- debug_hex_word cx
- debug_message ' ) '
- mov [CLK_date_month],cl
- ;
- ; Find the day within the month.
- ;
- sub ax,word ptr ds:[CLK_month_length_table][bx-2]
- mov [CLK_date_day],al
- debug_message '( day within month '
- debug_hex_word ax
- debug_message ' ) '
- CLK_conv_days_end:
- debug_message '} '
- ret
- CLK_conv_days_ccyymmdd endp
-
- ;
- ; Convert a number of years since 1980 to a number of days.
- ; 1980 is year number 0.
- ; Input years passed in AL, resulting days in AX.
- ;
-
- CLK_conv_years_days proc near
- ;
- ; Multiply number of years by 365.
- ; This result will be easily representable in 16 bits.
- ;
- mov cx,ax ; Save years for later
- mov bx,365
- mul bx
- ;
- ; Add one day for every fourth year (leap year).
- ;
- ; Because we are dealing only with the period from 1980 to 2079,
- ; we do not have to adjust the number of leap years.
- ; The year 2000 is a leap year, although 1900, 2100, 2200 and 2300 are not.
- ;
- add cx,3 ; CX := (years + 3)/4
- shr cx,1 ;
- shr cx,1 ;
- add ax,cx ; Add to total days
- ret
- CLK_conv_years_days endp
-
- ;
- ; Convert from number of days since 1-Jan-1980
- ; to day of the week. (Sunday = 0.)
- ;
-
- CLK_conv_days_weekday proc near
- debug_message '{ conv_days_weekday '
- ;
- ; The first of Jan 1980 (day number 0) was a Tuesday (weekday number 2).
- ; To convert a day number to a day of the week, we first add 2,
- ; then find the remainder when divided by 7.
- ;
- mov ax,[CLK_date_days] ; Add two
- debug_message '( days '
- debug_hex_word ax
- debug_message ' ) '
- add ax,2
- xor dx,dx ; 32-bit divide by 7
- mov bx,7
- div bx
- debug_message '( weekday '
- debug_hex_word dx
- debug_message ' ) '
- mov [CLK_date_weekday],dl
- debug_message '} '
- ret
- CLK_conv_days_weekday endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Binary <--> BCD conversions.
- ;
-
- resident_code segment
-
- ;
- ; Convert binary number in AL to BCD (still in AL).
- ;
-
- conv_binary_BCD proc near
- ;
- ; The AAM instruction does most of the work.
- ; It places the high BCD digit in AH and the low BCD digit in AL.
- ;
- aam
- ;
- ; Shift everything into place.
- ; (The undocumented {AAD 16} instruction would do this in one step.)
- ;
- push cx
- mov cl,4 ; Shift AH value to high nybble
- shl ah,cl
- or al,ah ; Merge the nybbles into AL
- pop cx
- ret
- conv_binary_BCD endp
-
- ;
- ; Convert BCD number in AL to binary (still in AL).
- ;
-
- conv_BCD_binary proc near
- ;
- ; Shift the high BCD digit into AH.
- ; (The undocumented {AAM 16} would do this in one step.)
- ;
- push cx
- xor ah,ah ; Shift high nybble to AH
- mov cl,4
- shl ax,cl
- shr al,cl ; Low nybble back into position
- pop cx
- ;
- ; The AAD instruction does the rest of the work.
- ; It gets the high BCD digit from AH and the low BCD digit from AL,
- ; and places the binary result into AL.
- ;
- aad
- ret
- conv_BCD_binary endp
-
- resident_code ends
-
- ;
- ; Check that the value in AL is a valid BCD number.
- ; Destroys AH in the process, but returns with AL unmodified.
- ; Returns with carry flag set if not a valid BCD number.
- ;
- startup_code segment
-
- CLK_check_BCD proc near
- mov ah,al ; Keep AL unchanged
- and ah,0Fh ; Check low nybble
- cmp ah,0Ah ; Must be 9 or less
- jae CLK_check_BCD_end ; Carry flag will be off if
- ; jump is taken
- mov ah,al ; Check high nybble
- and ah,0F0h ;
- cmp ah,0A0h ; Must be 90h or less
- ; Carry flag will be on if OK
- CLK_check_BCD_end:
- ;
- ; Now complement the carry flag and return.
- ; The carry flag will then be set if either test failed.
- ;
- cmc
- ret
- CLK_check_BCD endp
-
- startup_code ends
-
- ;***********************************************************************
-
- ;
- ; Some subroutines to read and set timers external to this device driver.
- ; This includes the BIOS timer, the AT BIOS real time clock, and battery
- ; backed-up timers on peripheral cards.
- ; The routines here simply call other routines, choosing which others
- ; to call according to the setup options.
- ;
-
- resident_code segment
-
- ;
- ; Make sure that the time is correct. Do this by checking other time
- ; references.
- ;
- CLK_fix_our_time proc near
- debug_message '{ fix our time '
- ;
- ; Ensure that DS points to the correct segment
- ;
- push ds ; Save old DS
- push cs ; Make DS point to code segment
- pop ds ;
- ;
- ; The initialisation routine should have stored pointers to a suitable
- ; procedure in the CLK_get_procedure_ptr word. Now call the procedure.
- ;
- call word ptr ds:[CLK_get_procedure_ptr]
- ;
- ; Restore DS segment register and return
- ;
- pop ds
- debug_message '} '
- ret
- CLK_fix_our_time endp
-
- ;
- ; Make sure that the time used by other timers in the system is correct.
- ; One reason for doing this is so that the "other timers" can tell us
- ; the correct time next time we need to ask them.
- ;
- CLK_set_other_timers proc near
- debug_message '{ set others '
- ;
- ; Ensure that DS points to the correct segment
- ;
- push ds ; Save old DS
- push cs ; Make DS point to code segment
- pop ds ;
- ;
- ; The initialisation routine should have stored a pointer to a suitable
- ; procedure in the CLK_set_procedures array. Now call the procedure.
- ;
- call word ptr ds:[CLK_set_procedure_ptr]
- ;
- ; Also set the plain BIOS idea of the time.
- ; (CLK_set_procedure_ptr will point to a null procedure if we are
- ; using the plain BIOS without any other clock.)
- ;
- call CLK_set_BIOS_time
- ;
- ; Restore DS segment register and return
- ;
- pop ds
- debug_message '} '
- ret
- CLK_set_other_timers endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Check the validity of the date and time passed in a write request.
- ; On entry, DS:SI points to the caller's data.
- ; On exit, carry flag will be set if the data is invalid.
- ;
- resident_code segment
-
- CLK_check_data_validity proc near
- debug_message '{ validity check '
- debug_message ' ( data: '
- debug_hex_string ds,[si],6,word
- debug_message ') '
- ;
- ; Check hours, minutes, seconds and hundredths of secs.
- ; Note funny order in which things are stored.
- ;
- cmp byte ptr ds:[si+3], 24 ; Hours must be < 24
- jnc short CLK_check_end
- cmp byte ptr ds:[si+2], 60 ; Minutes must be < 60
- jnc short CLK_check_end
- cmp byte ptr ds:[si+5], 60 ; Seconds must be < 60
- jnc short CLK_check_end
- cmp byte ptr ds:[si+4], 60 ; Hundredths must be < 100
- cmp al, 100
- CLK_check_end:
- ;
- ; Toggle the carry flag, so this routine returns with carry set if
- ; there was a problem, and clear if there was no problem.
- ;
- cmc
- debug_message '} '
- ret
- CLK_check_data_validity endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; This procedure will read the normal BIOS timer, and check whether either
- ; the date has changed or the time has changed by more than a few minutes
- ; since the last call. If necessary, it then reads some other clock (which
- ; has coarser granularity but higher accuracy), and re-calibrates the
- ; normal BIOS timer.
- ;
-
- resident_code segment
-
- CLK_get_BIOS_or_other_time proc near
- ;
- ; Ask BIOS for the new date and time
- ;
- call CLK_get_BIOS_time
- ;
- ; See if the date has changed
- ;
- mov ax,[CLK_date_days] ; Current date
- cmp ax,[CLK_last_date] ; Previous date
- jne CLK_must_get_other_time
- ;
- ; See if the time has changed by more than five minutes since
- ; last time we got the time from the AT BIOS battery-backed clock.
- ;
- mov al,[CLK_time_hour] ; Current hour*60 + minute
- mov ah,60
- mul ah
- add al,[CLK_time_min]
- adc ah,0
- sub ax,[CLK_last_time] ; Subtract previous time
- cmp ax,5 ; Did time go forwards < 5 mins ?
- jng CLK_dont_get_other_time
- CLK_must_get_other_time:
- ;
- ; Read the other clock and set the normal BIOS timer to the current time.
- ;
- call word ptr ds:[CLK_get_other_procedure_ptr]
- call CLK_set_BIOS_time
- CLK_dont_get_other_time:
- ;
- ; Remember the current date and time. This will be the "old" date and time
- ; next time the clock is read.
- ;
- mov ax,[CLK_date_days] ; Date
- mov [CLK_last_date],ax
- mov al,[CLK_time_hour] ; Hours multiplied by 60
- mov ah,60
- mul ah
- add al,[CLK_time_min] ; Plus minutes
- adc ah,0
- mov [CLK_last_time],ax
- ret
- CLK_get_BIOS_or_other_time endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Handle a AT-type computer, with clock chip support in the BIOS.
- ;
- ; Note that some of these routines are in the startup_code and some
- ; are in the resident_code.
- ;
-
- startup_code segment
-
- ;
- ; Check for AT-type BIOS support of the clock chip.
- ;
- ; BIOS interrupt 1A function 4 should get the date from the real time clock
- ; if the computer has an AT-type BIOS. The call should return invalid data if
- ; the BIOS does not support that function.
- ;
- ; Return with CARRY set if no AT BIOS support for clock chip.
- ;
-
- CLK_find_AT_BIOS proc near
- assume ds:cgroup
- debug_message '{ find AT BIOS '
- ;
- ; Do an interrupt 1A function 4 call. (Get date from real time clock
- ; on an AT machine.)
- ;
- xor cx,cx ; This will help find errors
- xor dx,dx ;
- mov ah,04h ; Ask BIOS for the date
- int 1Ah ;
- ;
- ; Check that the results are reasonable. If not, then it cannot
- ; have an AT-compatible BIOS.
- ; The results are all in BCD.
- ;
- cmp ch,19h ; Is the century reasonable ?
- jb CLK_find_no_AT ; no
- cmp cl,99h ; Is the year reasonable ?
- ja CLK_find_no_AT ; no
- cmp dh,12h ; Is the month reasonable ?
- ja CLK_find_no_AT ; no
- cmp dl,31h ; Is the day reasonable ?
- ja CLK_find_no_AT ; no
- ;
- ; Return with no carry if all is correct
- ;
- clc
- debug_message '(ok) } '
- ret
- ;
- ; If there is no AT BIOS clock support, return with carry flag set.
- ;
- CLK_find_no_AT:
- stc
- debug_message '(failed) } '
- ret
- CLK_find_AT_BIOS endp
-
- startup_code ends
-
- resident_code segment
-
- ;
- ; Get the time from AT BIOS.
- ;
-
- CLK_get_AT_BIOS_time proc near
- assume ds:cgroup
- debug_message '{ get AT BIOS '
- ;
- ; BIOS interrupt 1Ah functions 2 and 4 get time and date from AT BIOS
- ;
- mov ah,02h ; Get time
- int 1Ah ;
- mov al,ch ; Save it
- call conv_BCD_binary ;
- mov [CLK_time_hour],al ;
- mov al,cl ;
- call conv_BCD_binary ;
- mov [CLK_time_min],al ;
- mov al,dh ;
- call conv_BCD_binary ;
- mov [CLK_time_sec],al ;
- mov [CLK_time_hsec],0 ; There are no hundredths ???
- mov ah,04h ; Get date
- int 1Ah ;
- mov al,ch ; Save it
- call conv_BCD_binary ;
- mov [CLK_date_century],al ;
- mov al,cl ;
- call conv_BCD_binary ;
- mov [CLK_date_year],al ;
- mov al,dh ;
- call conv_BCD_binary ;
- mov [CLK_date_month],al ;
- mov al,dl ;
- call conv_BCD_binary ;
- mov [CLK_date_day],al ;
- ;
- ; The date is in century, year, month, day form
- ; so it must be converted to a number of days since 1-Jan-1980.
- ;
- call CLK_conv_ccyymmdd_days
- ret
- debug_message '} '
- CLK_get_AT_BIOS_time endp
-
- ;
- ; Tell the AT BIOS to set the time.
- ;
-
- CLK_set_AT_BIOS_time proc near
- assume ds:cgroup
- debug_message '{ set AT BIOS '
- ;
- ; Convert the date from days since 1-Jan-1980 to year, month, day
- ;
- call CLK_conv_days_ccyymmdd
- ;
- ; Set the AT BIOS clock.
- ; Everything must be in BCD.
- ;
- mov al,[CLK_time_hour]
- call conv_binary_BCD
- mov ch,al
- mov al,[CLK_time_min]
- call conv_binary_BCD
- mov cl,al
- mov al,[CLK_time_sec]
- call conv_binary_BCD
- mov dh,al
- xor dl,dl ; Not daylight saving time
- mov ah,3
- int 1Ah
- mov al,[CLK_date_century]
- call conv_binary_BCD
- mov ch,al
- mov al,[CLK_date_year]
- call conv_binary_BCD
- mov cl,al
- mov al,[CLK_date_month]
- call conv_binary_BCD
- mov dh,al
- mov al,[CLK_date_day]
- call conv_binary_BCD
- mov dl,al
- mov ah,5
- int 1Ah
- debug_message '} '
- ret
- CLK_set_AT_BIOS_time endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Handle a National Semiconductor MM58167AN clock chip.
- ; Most add-on clock cards use this chip.
- ;
- ; There are at least three different conventions for using the chip
- ; RAM registers to remember the date. We use the method used by the
- ; TIMER program provided with some add-on clock cards; this is not
- ; the same as the method used by the SETCLOCK and GETCLOCK programs
- ; provided with some other add-on clock cards, and also differs from
- ; the usage suggested in the chip documentation.
- ;
- ; Note that some of these routines are in the startup_code and some
- ; are in the resident_code.
- ;
- ; There is a word in the resident_data segment to say whether or
- ; not to use these routines, and what base address to use for the
- ; clock chip.
- ;
-
- startup_code segment
-
- ;
- ; Look for the MM58167AN clock chip.
- ;
- ; The chip is usually addressed from I/O port 00C0, 0240, 02C0 or 0340,
- ; so we look in all these places, unless the user specified a location,
- ; in which case look there only. The user can also explicitly say don't
- ; use the clock chip.
- ;
- ; On entry, CLK_chip_IO_base is zero if chip will not be used, or
- ; 0FFFFh to search for chip in standard places, or some other value
- ; representing the chip's actual IO base address.
- ;
- ; Return with CARRY set if no clock chip.
- ; CLK_chip_IO_base will be set to the start address if there is a chip,
- ; or to zero if there is none.
- ;
-
- CLK_find_MM58167AN proc near
- assume ds:cgroup
- debug_message '{ find MM58167AN chip '
- ;
- ; Check whether the user told us not to use the clock chip
- ;
- mov ax,[CLK_chip_IO_base] ; What the user said
- or ax, ax ; If zero then don't use clock
- je CLK_find_no_MM58167AN
- ;
- ; Check whether user said where the chip resides in the IO map
- ;
- cmp ax,0FFFFh ; If not FFFF, look there only
- jne CLK_find_MM58167AN_last_resort
- ;
- ; Look in the standard places
- ;
- mov word ptr [CLK_chip_IO_base],0240h ; Base address
- call CLK_check_MM58167AN
- jnc CLK_find_MM58167AN_exit ; Exit if found
- mov word ptr [CLK_chip_IO_base],02C0h ; Base address
- call CLK_check_MM58167AN
- jnc CLK_find_MM58167AN_exit ; Exit if found
- mov word ptr [CLK_chip_IO_base],0340h ; Base address
- call CLK_check_MM58167AN
- jnc CLK_find_MM58167AN_exit ; Exit if found
- mov word ptr [CLK_chip_IO_base],00C0h ; Base address
- CLK_find_MM58167AN_last_resort:
- call CLK_check_MM58167AN ; See if there is a clock chip
- jc CLK_find_no_MM58167AN ; Error if not found
- CLK_find_MM58167AN_exit:
- debug_message '( ok '
- debug_hex_word <word ptr [CLK_chip_IO_base]>
- debug_message ' ) } '
- ret
- ;
- ; If there is no clock chip, set the carry flag and zero the base address.
- ;
- CLK_find_no_MM58167AN:
- mov word ptr [CLK_chip_IO_base],0
- debug_message '( failed ) } '
- stc
- ret
- CLK_find_MM58167AN endp
-
- ;
- ; Check if there is an MM58167AN clock chip at the IO base address
- ; specified in CLK_chip_IO_base. Return with CARRY if there is no chip.
- ;
-
- CLK_check_MM58167AN proc near
- assume ds:cgroup
- debug_message '{ check MM58167AN ( base '
- debug_hex_word <word ptr [CLK_chip_IO_base]>
- debug_message ' ) '
- ;
- ; Check the first seven registers, to ensure that they
- ; contain BCD data within the correct range.
- ;
- mov dx,[CLK_chip_IO_base] ; Get chip base address
- in al,dx ; 00: 1/10000 seconds
- call CLK_check_BCD
- jc CLK_check_no_MM58167AN
- inc dx ; 01: 1/10 and 1/100 seconds
- in al,dx
- call CLK_check_BCD
- jc CLK_check_no_MM58167AN
- inc dx ; 02: seconds
- in al,dx
- call CLK_check_BCD
- jc CLK_check_no_MM58167AN
- cmp al,59h ; Must be less than 60
- ja CLK_check_no_MM58167AN
- inc dx ; 03: minutes
- in al,dx
- call CLK_check_BCD
- jc CLK_check_no_MM58167AN
- cmp al,59h ; Must be less than 60
- ja CLK_check_no_MM58167AN
- inc dx ; 04: hours
- in al,dx
- call CLK_check_BCD
- jc CLK_check_no_MM58167AN
- cmp al,23h ; Must be less than 24
- ja CLK_check_no_MM58167AN
- inc dx ; 05: day of week
- in al,dx
- test al,al ; Must be between 1 and 7
- jz CLK_check_no_MM58167AN
- cmp al,07h
- ja CLK_check_no_MM58167AN
- inc dx ; 06: day of month
- in al,dx
- test al,al ; Must be between 1 and 31
- jz CLK_check_no_MM58167AN
- cmp al,31h
- ja CLK_check_no_MM58167AN
- inc dx ; 07: month
- in al,dx
- test al,al ; Must be between 1 and 12
- jz CLK_check_no_MM58167AN
- cmp al,12h
- ja CLK_check_no_MM58167AN
- ;
- ; It certainly looks like an MM58167AN clock chip.
- ; Return with carry flag clear.
- ;
- clc
- debug_message '(ok) } '
- ret
- CLK_check_no_MM58167AN:
- ;
- ; It is definitely not an MM58167AN clock chip.
- ; Return with carry flag set.
- ;
- stc
- debug_message '( failed ) } '
- ret
- CLK_check_MM58167AN endp
-
- startup_code ends
-
- resident_code segment
-
- ;
- ; Get the time from clock chip
- ;
-
- CLK_get_MM58167AN_time proc near
- assume ds:cgroup
- debug_message '{ get MM58167AN '
- ;
- ; Read all the relevant values from the chip's registers
- ;
- ; The "last month" value used by the TIMER program supplied with
- ; some clock cards actually stores the year in register 09 and the
- ; last month in register 08. We will do the same, for compatibility.
- ; The last month value is OR( SHL( MAX(month,7),4 ), 80h).
- ;
- mov dx,[CLK_chip_IO_base] ; Address of the first register
- inc dx ; 00: ten thousandths of seconds
- ; (ignore)
- in al,dx ; 01: hundredths of seconds
- debug_message '( centi '
- debug_hex_byte al
- debug_message ' BCD ) '
- call conv_BCD_binary ; convert from BCD
- mov [CLK_time_hsec],al ; save
- inc dx ; 02: seconds
- in al,dx
- debug_message '( secs '
- debug_hex_byte al
- debug_message ' BCD ) '
- call conv_BCD_binary
- mov [CLK_time_sec],al
- inc dx ; 03: minutes
- in al,dx
- debug_message '( mins '
- debug_hex_byte al
- debug_message ' BCD ) '
- call conv_BCD_binary
- mov [CLK_time_min],al
- inc dx ; 04: hours
- in al,dx
- debug_message '( hours '
- debug_hex_byte al
- debug_message ' BCD ) '
- call conv_BCD_binary
- mov [CLK_time_hour],al
- inc dx ; 05: day of week (ignore)
- inc dx ; 06: day of month
- in al,dx
- debug_message '( day of month '
- debug_hex_byte al
- debug_message ' BCD ) '
- call conv_BCD_binary
- mov [CLK_date_day],al
- inc dx ; 07: month
- in al,dx
- debug_message '( month '
- debug_hex_byte al
- debug_message ' BCD ) '
- mov bl,al ; (save BCD month value)
- call conv_BCD_binary
- mov [CLK_date_month],al
- inc dx ; 08: RAM : last month
- in al,dx
- debug_message '( last month '
- debug_hex_byte al
- debug_message ' ) '
- mov bh,al ; (save last month value)
- mov al,bl ; find MAX(7,current month)
- cmp al,7
- jng CLK_get_MM58167AN_month_7
- mov al,7
- CLK_get_MM58167AN_month_7:
- mov cl,4 ; Shift to high nybble
- shl al,cl
- or al,80h ; Set high bit
- debug_message '( new last month value '
- debug_hex_byte al
- debug_message ' ) '
- mov bl,al ; Save new last month
- out dx,al ; Store new last month
- inc dx ; 09: year
- in al,dx
- debug_message '( year '
- debug_hex_byte al
- debug_message ' BCD ) '
- ;
- ; See if the year has changed.
- ;
- cmp bl,bh ; Is current month smaller than
- ; previous month ?
- jae CLK_get_MM58167AN_same_year
- debug_message '( year has changed ) '
- inc al ; Increment year
- daa
- out dx,al ; Store new year into chip
- CLK_get_MM58167AN_same_year:
- call conv_BCD_binary
- mov [CLK_date_year],al
- ;
- ; Choose a suitable century
- ;
- mov ah,19 ; Assume the year is 19xx.
- cmp al,80 ; If last two digits less than 80
- ; then the year is 20xx
- jnb CLK_get_MM58167AN_century
- inc ah
- CLK_get_MM58167AN_century:
- debug_message '( century '
- debug_hex_byte ah
- debug_message' ) '
- mov [CLK_date_century],ah
- ;
- ; Convert the date to number of days since 1-Jan-1980
- ;
- call CLK_conv_ccyymmdd_days
- ;
- ; Finished getting time from chip
- ;
- ret
- debug_message '} '
- CLK_get_MM58167AN_time endp
-
- ;
- ; Set the time on the clock chip.
- ;
-
- CLK_set_MM58167AN_time proc near
- assume ds:cgroup
- debug_message '{ set MM58167AN '
- ;
- ; Convert date to correct format
- ;
- call CLK_conv_days_ccyymmdd ; Year, month, day from day number
- call CLK_conv_days_weekday ; Find current day of week
- ;
- ; Store data into clock chip
- ;
- ; The "last month" value used by the TIMER program supplied with
- ; some clock cards actually stores the year in register 09 and the
- ; last month in register 08. We will do the same, for compatibility.
- ; The last month value is OR( SHL( MAX(month,7),4 ), 80h).
- ;
- mov dx,[CLK_chip_IO_base] ; Base address of clock chip
- xor al,al ; 00: 1/10000 secs (zero)
- out dx,al
- debug_message '( zero 1/10000 sec ) '
- inc dx ; 01: 1/100 secs
- mov al,[CLK_time_hsec]
- call conv_binary_BCD
- debug_message '( centi '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 02: seconds
- mov al,[CLK_time_sec]
- call conv_binary_BCD
- debug_message '( secs '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 03: minutes
- mov al,[CLK_time_min]
- call conv_binary_BCD
- debug_message '( mins '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 04: hours
- mov al,[CLK_time_hour]
- call conv_binary_BCD
- debug_message '( hour '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 05: day of week
- mov al,[CLK_date_weekday]
- inc al ; Chip uses Sunday=1, not =0
- debug_message '( weekday '
- debug_hex_byte al
- debug_message ' ) '
- out dx,al
- inc dx ; 06: day of month
- mov al,[CLK_date_day]
- call conv_binary_BCD
- debug_message '( day of month '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 07: month
- mov al,[CLK_date_month]
- mov bl,al
- call conv_binary_BCD
- debug_message '( month '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- inc dx ; 08: RAM : last month
- mov al,bl ; Get current month
- cmp al,7 ; find MAX(7,current month)
- jng CLK_set_MM58167AN_month_7
- mov al,7
- CLK_set_MM58167AN_month_7:
- mov cl,4 ; Shift to high nybble
- shl al,cl
- or al,80h ; Set high bit
- debug_message '( new last month value '
- debug_hex_byte al
- debug_message ' ) '
- out dx,al ; Store new last month
- inc dx ; 09: RAM : year
- mov al,[CLK_date_year]
- call conv_binary_BCD
- debug_message '( year '
- debug_hex_byte al
- debug_message ' BCD ) '
- out dx,al
- ;
- ; Finished
- ;
- ret
- debug_message '} '
- CLK_set_MM58167AN_time endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Handle an MSM6242 clock chip.
- ;
- ; Note that some of these routines are in the startup_code and some
- ; are in the resident_code.
- ;
- ; There is a word in the resident_data segment to say whether or
- ; not to use these routines, and what base address to use for the
- ; clock chip.
- ;
-
- startup_code segment
-
- ;
- ; Look for the MSM6242 clock chip.
- ;
- ; The chip is usually addressed from I/O port 02C0,
- ; so we look there, unless the user specified a different location,
- ; in which case look there only. The user can also explicitly say don't
- ; use the clock chip.
- ;
- ; On entry, CLK_chip_IO_base is zero if chip will not be used, or
- ; 0FFFFh to search for chip in standard places, or some other value
- ; representing the chip's actual IO base address.
- ;
- ; Return with CARRY set if no clock chip.
- ; CLK_chip_IO_base will be set to the start address if there is a chip,
- ; or to zero if there is none.
- ;
-
- CLK_find_MSM6242 proc near
- assume ds:cgroup
- debug_message '{ find MSM6242 chip '
- ;
- ; Check whether the user told us not to use the clock chip
- ;
- mov ax,[CLK_chip_IO_base] ; What the user said
- or ax, ax ; If zero then don't use clock
- je CLK_find_no_MSM6242
- ;
- ; Check whether user said where the chip resides in the IO map
- ;
- cmp ax,0FFFFh ; If not FFFF, look there only
- jne CLK_find_MSM6242_last_resort
- ;
- ; Look in the standard places
- ;
- mov word ptr [CLK_chip_IO_base],02C0h ; Base address
- CLK_find_MSM6242_last_resort:
- call CLK_check_MSM6242 ; See if there is a clock chip
- jc CLK_find_no_MSM6242 ; Error if not found
- CLK_find_MSM6242_exit:
- debug_message '( ok '
- debug_hex_word <word ptr [CLK_chip_IO_base]>
- debug_message ' ) } '
- ret
- ;
- ; If there is no clock chip, set the carry flag and zero the base address.
- ;
- CLK_find_no_MSM6242:
- mov word ptr [CLK_chip_IO_base],0
- debug_message '( failed ) } '
- stc
- ret
- CLK_find_MSM6242 endp
-
- ;
- ; Check if there is an MSM6242 clock chip at the IO base address
- ; specified in CLK_chip_IO_base. Return with CARRY if there is no chip.
- ;
-
- CLK_check_MSM6242 proc near
- assume ds:cgroup
- debug_message '{ check MSM6242 ( base '
- debug_hex_word <word ptr [CLK_chip_IO_base]>
- debug_message ' ) '
- ;
- ; Check the first twelve registers, to ensure that they
- ; contain data within the correct range. The high 4 bits
- ; should be ignored.
- ;
- mov dx,[CLK_chip_IO_base] ; Get chip base address
- in al,dx ; 00: 1's of seconds
- and al, 0Fh
- cmp al, 10
- ja CLK_check_no_MSM6242
- inc dx ; 01: 10's of seconds
- in al,dx
- and al, 0Fh
- cmp al, 10
- ja CLK_check_no_MSM6242
- inc dx ; 02: 1's of minutes
- in al,dx
- and al, 0Fh
- cmp al, 10
- ja CLK_check_no_MSM6242
- inc dx ; 03: 10's of minutes
- in al,dx
- and al, 0Fh
- cmp al, 10
- ja CLK_check_no_MSM6242
- inc dx ; 04: 1's of hours
- in al,dx
- and al, 0Fh
- cmp al, 10
- ja CLK_check_no_MSM6242
- inc dx ; 05: 10's of hours
- in al,dx
- and al, 0Fh
- cmp al, 2
- ja CLK_check_no_MSM6242
- inc dx ; 06: 1's of day of month
- in al,dx
- and al, 0Fh
- cmp al,10
- ja CLK_check_no_MSM6242
- inc dx ; 07: 10's of day of month
- in al,dx
- and al, 0Fh
- cmp al,3
- ja CLK_check_no_MSM6242
- inc dx ; 08: 1's of month number
- in al,dx
- and al, 0Fh
- cmp al,10
- ja CLK_check_no_MSM6242
- inc dx ; 09: 10's of month number
- in al,dx
- and al, 0Fh
- cmp al,1
- ja CLK_check_no_MSM6242
- inc dx ; 0A: 1's of year (within century)
- in al,dx
- and al, 0Fh
- cmp al,10
- ja CLK_check_no_MSM6242
- inc dx ; 0B: 10's of year
- in al,dx
- and al, 0Fh
- cmp al,10
- ja CLK_check_no_MSM6242
- inc dx ; 0C: day of week (Sun=1, Sat=7))
- in al,dx
- and al, 0Fh
- jz CLK_check_no_MSM6242
- cmp al,7
- ja CLK_check_no_MSM6242
- ;
- ; It certainly looks like an MSM6242 clock chip.
- ; Return with carry flag clear.
- ;
- clc
- debug_message '(ok) } '
- ret
- CLK_check_no_MSM6242:
- ;
- ; It is definitely not an MSM6242 clock chip.
- ; Return with carry flag set.
- ;
- stc
- debug_message '( failed ) } '
- ret
- CLK_check_MSM6242 endp
-
- startup_code ends
-
- resident_code segment
-
- ;
- ; Get the time from clock chip
- ;
-
- CLK_get_MSM6242_time proc near
- assume ds:cgroup
- debug_message '{ get MSM6242 '
- ;
- ; Read all the relevant values from the chip's registers
- ;
- mov [CLK_time_hsec], 0 ; no hundredths available
- mov dx,[CLK_chip_IO_base] ; Get chip base address
- in al,dx ; 00: 1's of seconds
- mov ah, al
- inc dx ; 01: 10's of seconds
- in al, dx
- xchg ah, al
- and ax, 0F0Fh ; ignore junk bits
- aad ; AAD sets AL := AL + 10*AH.
- ; Now if Intel would just *document* the
- ; extension to bases other than 10, we
- ; could use it for other interesting things...
- debug_message '( secs '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_time_sec],al
- inc dx ; 02: 1's of minutes
- in al,dx
- mov ah, al
- inc dx ; 03: 10's of minutes
- in al,dx
- xchg ah, al
- and ax, 0F0Fh
- aad
- debug_message '( mins '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_time_min],al
- inc dx ; 04: 1's of hours
- in al,dx
- inc dx ; 05: 10's of hours
- in al,dx
- mov ah, al
- xchg ah, al
- and ax, 0F0Fh
- aad
- debug_message '( hour '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_time_hour],al
- inc dx ; 06: 1's of day of month
- in al,dx
- mov ah, al
- inc dx ; 07: 10's of day of month
- in al,dx
- xchg ah, al
- and ax, 0F0Fh
- aad
- debug_message '( day '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_date_day],al
- inc dx ; 08: 1's of month number
- in al,dx
- mov ah, al
- inc dx ; 09: 10's of month number
- in al,dx
- xchg ah, al
- and ax, 0F0Fh
- aad
- debug_message '( month '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_date_month],al
- inc dx ; 0A: 1's of year (within century)
- in al,dx
- mov ah, al
- inc dx ; 0B: 10's of year
- in al,dx
- xchg ah, al
- and ax, 0F0Fh
- aad
- debug_message '( year '
- debug_hex_byte al
- debug_message ' ) '
- mov [CLK_date_year],al
- ;inc dx ; 0C: day of week (Sun=1,Sat=7) (ignore)
- ;
- ; Choose a suitable century
- ;
- mov ah,19 ; Assume the year is 19xx.
- cmp al,80 ; If last two digits less than 80
- ; then the year is 20xx
- jnb CLK_get_MSM6242_century
- inc ah
- CLK_get_MSM6242_century:
- debug_message '( century '
- debug_hex_byte ah
- debug_message' ) '
- mov [CLK_date_century],ah
- ;
- ; Convert the date to number of days since 1-Jan-1980
- ;
- call CLK_conv_ccyymmdd_days
- ;
- ; Finished getting time from chip
- ;
- ret
- debug_message '} '
- CLK_get_MSM6242_time endp
-
- ;
- ; Set the time on the clock chip.
- ;
-
- CLK_set_MSM6242_time proc near
- assume ds:cgroup
- debug_message '{ set MSM6242 '
- ;
- ; Convert date to correct format
- ;
- call CLK_conv_days_ccyymmdd ; Year, month, day from day number
- call CLK_conv_days_weekday ; Find current day of week
- ;
- ; Store data into clock chip
- ;
- mov dx,[CLK_chip_IO_base] ; Get chip base address
- mov al,[CLK_time_sec] ; 00 and 01: secs
- debug_message '( secs '
- debug_hex_byte al
- debug_message ') '
- aam ; puts high digit in AH, low digit in AL
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_time_min] ; 02 and 03: mins
- debug_message '( mins '
- debug_hex_byte al
- debug_message ') '
- aam
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_time_min] ; 04 and 05: hours
- debug_message '( hours '
- debug_hex_byte al
- debug_message ') '
- aam
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_date_day] ; 06 and 07: day of month
- debug_message '( day '
- debug_hex_byte al
- debug_message ') '
- aam
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_date_month] ; 08 and 09: month
- debug_message '( month '
- debug_hex_byte al
- debug_message ') '
- aam
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_date_year] ; 0A and 0B: year
- debug_message '( year '
- debug_hex_byte al
- debug_message ') '
- aam
- out dx, al
- inc dx
- mov al, ah
- out dx, al
- inc dx
- mov al,[CLK_date_weekday] ; 0C: day of week
- inc al ; Chip uses Sunday=1, not =0
- debug_message '( weekday '
- debug_hex_byte al
- debug_message ' ) '
- out dx,al
- ;
- ; Finished
- ;
- ret
- debug_message '} '
- CLK_set_MSM6242_time endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Subroutines to read and set the BIOS timer.
- ;
- ; All these routines are in the resident_code.
- ;
- ; There are calibration values in the resident_data that may (one day)
- ; be controllable from the command line in the CONFIG.SYS file. At present
- ; they are fixed at assembly time.
- ;
-
- resident_data segment
-
- ;
- ; These values are used to calibrate the BIOS timer.
- ; CLK_tick_65536 should contain the number of ticks that will occur
- ; in 65536 seconds. This should be 1193180 (hex 001234DC), because
- ; the clock input to the 8253 timer chip is 1.19318 MHz.
- ; CLK_tick_65536_20 is one twentieth of the number of ticks per 65536
- ; seconds. It should be 59659 (hex E90B).
- ; CLK_tick_day should contain the number of ticks per day. This should
- ; be 1573040 (hex 001800B0).
- ; All these values should be changed simultaneously! There is no check
- ; to ensure that they are self-consistent.
- ; Note that unless the BIOS timer interrupt server is modified, changing the
- ; ticks per day value here will not have the desired effect.
- ; Even if these values are modified, some sections of the code assume that
- ; the number of ticks per second is 18.2. For this reason, small adjustments
- ; for calibration purposes are OK, but large adjustments may cause trouble.
- ;
- CLK_tick_65536 dd 1193180 ; Number of ticks in 65536 seconds
- CLK_tick_65536_20 dw 59659 ; CLK_tick_65536 divided by 20
- CLK_tick_day dd 1573040 ; Number of time-base ticks per day
-
- ;
- ; Keep the last BIOS tick count, to check for time going backwards.
- ; The initial value of zero is chosen so that the first time
- ; CLK_get_BIOS_time is called, it doesn't think time has gone backwards.
- ;
- CLK_last_tick dd 0
-
- ;
- ; The last date, hour and minute at which the timer was called.
- ; This is needed when the AT BIOS or some other coarse graoned clock
- ; is used.
- ; We don't bother updating this data if a fine grain clock
- ; chip is used.
- ;
- ; When we have to deal with a coarse grain clock chip, we can do better
- ; by usually using the normal BIOS timer (to get finer granularity)
- ; and whenever the get-time procedure is called more than a few minutes
- ; after the previous call, or when the date has changed, we update the
- ; BIOS timer from the chip, to get reasonable long term accuracy.
- ; The initial values in the DW's below ensure that the genuine chip
- ; clock is used the first time.
- ;
- CLK_last_date dw -10 ; The date
- CLK_last_time dw -10 ; Hours*60 + minutes
-
- resident_data ends
-
- resident_code segment
-
- ;
- ; Make the BIOS timer correspond to our time.
- ;
- CLK_set_BIOS_time proc near
- assume ds:cgroup
- debug_message '{ set BIOS '
- ;
- ; First convert the time to a number of seconds. Ignore the hundredths
- ; of seconds until later.
- ;
- mov al,CLK_time_hour ; Multiply hours by 60
- debug_message '( hour '
- debug_hex_byte al
- debug_message ' ) '
- mov bx,60
- mul bl
- mov cl,CLK_time_min ; Add minutes
- debug_message '( min '
- debug_hex_byte cl
- debug_message ' ) '
- xor ch,ch
- add ax,cx
- ; Now AX is number of minutes
- mul bx ; Multiply total minutes by 60
- mov cl,CLK_time_sec ; Add seconds
- debug_message '( sec '
- debug_hex_byte cl
- debug_message ' ) '
- add ax,cx
- adc dx,0
- ; Combined DX_AX is now the number of seconds.
- debug_message '( total secs '
- debug_hex_word dx
- debug_hex_word ax
- debug_message ' ) '
- ;
- ; Convert the number of seconds to a number of ticks.
- ; This requires multiplying by 1193180, then dividing by 65536.
- ;
- ; This is done by multiplying the 32-bit value in DX_AX by the 21-bit
- ; value 1193180 (hexadecimal 001234DC), and discarding the lowest 16 bits
- ; of the result. (This works because 65536 is 2^16.)
- ;
- ;
- ; The value in DX_AX can never be greater than 24*60*60,
- ; which is 86400, or hexadecimal 00015180. After
- ; multiplication by 1193180, the result will not be larger
- ; than about 103e9, which is representable in 37 bits.
- ; As the result will then be divided by 65536 (which is 2^16),
- ; we need not even calculate the lowest 16 bits. The result
- ; will therefore require 21 bits of storage, and 32 bits
- ; will actually be used.
- ;
- ; Let the low 16 bits of the multiplicand (now in DX_AX)
- ; be A0. Let the high 16 bits be A1.
- ; Let the low 16 bits of the multiplier (1193180) be B0.
- ; Let the high 16 bits be B1.
- ; In general, multiplying two 32-bit values could yield
- ; a 64-bit product.
- ; Let the four 16-bit sections of the product be C0, C1,
- ; C2 and C3, with C0 being the lowest 16 bits.
- ; Let L() and H() denote the low and high 16 bits of a
- ; 32-bit value.
- ; Let D0, D1, D2 and D3 be 32-bit intermediate results.
- ; Note that C3 will be zero.
- ; C0 will be ignored, unless it is greater than 2^15, in
- ; which case the total will be rounded up.
- ;
- ; D0 = A0*B0
- ; C0 = L( D0 )
- ; D1 = H( D0 ) + L( A0*B1 ) + L( A1*B0 )
- ; C1 = L( D1 )
- ; D2 = H( D1 ) + H( A0*B1 ) + H( A1*B0 ) + L( A1*B1 )
- ; C2 = L( D2 )
- ; D3 = H( D2 ) + H( A1*B1 )
- ; C3 = L( D3 )
- ;
- ; C3 will be zero, so D3 need not be calculated.
- ;
- ; Save the value currently in DX_AX
- mov t1,dx ; T1 := A1
- mov t2,ax ; T2 := A0
- ;
- ; Calculate D0 = A0*B0
- ; AX is already = A0
- mov bx,word ptr [CLK_tick_65536] ; BX := B0
- mul bx
- ;
- ; We don't need L(D0), which is in AX, but we must
- ; save H(D0), which is in DX. Before doing so, check
- ; if L(D0) is greater that hex 7FFF, in which case the
- ; result must be rounded up by incrementing DX. Note that
- ; DX cannot be larger than hex FFFE, so incrementing it
- ; here cannot cause overflow problems.
- mov cx,7FFFh
- cmp cx,ax ; Set carry flag if AX > 7FFFh
- adc dx,0 ; Increment DX if necessary
- mov t3,dx ; T3 := H(D0)
- ;
- ; Multiply A1*B0
- mov ax,t1 ; AX := A1
- ; BX is already = B0
- mul bx
- mov t4,dx ; T4 := H(A1*B0)
- mov t5,ax ; T5 := L(A1*B0)
- ;
- ; Multiply A0*B1
- mov ax,t2 ; AX := A0
- mov bx,word ptr [CLK_tick_65536+2] ; BX := B1
- mul bx
- mov t2,dx ; T2 := H(A0*B1)
- ;
- ; Calculate D1 = H(D0) + L(A0*B1) + L(A1*B0)
- ; AX is already = L(A0*B1)
- xor dx,dx
- add ax,t3 ; add H(D0)
- adc dx,0
- add ax,t5 ; add L(A1*B0)
- adc dx,0
- mov t3,dx ; T3 := H(D1)
- mov t5,ax ; T5 := C1 { = L(D1) }
- ;
- ; Multiply A1*B1
- mov ax,t1 ; AX := A1
- ; BX is already = B1
- mul bx
- ;
- ; H(A1*B1) should be zero, and is not needed.
- ; Calculate D2 = H(D1) + L(A1*B1) + H(A1*B0) + H(A0*B1).
- ; H(D2) will be zero, and need not be calculated.
- ; AX is already = L(A1*B1)
- add ax,t3 ; add H(D1)
- add ax,t4 ; add H(A1*B0)
- add ax,t2 ; add H(A0*B1)
- mov cx,ax ; CX := L(D2) { = C2 }
- ;
- ; CX is high word, T5 is low word of result
- debug_message '( ticks from secs '
- debug_hex_word cx
- debug_hex_word t5
- debug_message ' ) '
- ;
- ; We now have a number of clock ticks, but it ignores the hundredths of
- ; seconds. Treating the hundredths of seconds exactly would have required
- ; multiplying a 32-bit value by 100 and dividing a 32-bit value by 100, in
- ; addition to the code already used. That task is not difficult to code
- ; but it does add much extra code for very little benefit.
- ;
- ; An approximate number of extra ticks is added to the count now, to handle
- ; the hundredths of seconds. This approximate value is obtained simply by
- ; multiplying the hundredths by 10 and dividing by 55. Just before dividing
- ; by 55, add 27 to make the division round instead of truncating.
- ;
- mov al,CLK_time_hsec ; Calculate ticks for centi-secs
- debug_message '( centi '
- debug_hex_byte al
- debug_message ' ) '
- mov ah,10
- mul ah
- add ax,27
- mov bl,55
- div bl
- xor ah,ah ; Discard the remainder
- add ax,t5 ; Add to total
- adc cx,0 ;
- ; Now the total number of ticks is in CX_AX
- ;
- ; Remember the tick count for later
- ;
- mov word ptr ds:[CLK_last_tick+2], cx
- mov word ptr ds:[CLK_last_tick], ax
- ;
- ; Call the BIOS "set timer" interrupt
- ;
- mov dx,ax ; DX is low 16 bits of count
- ; CX is already high 16 bits
- debug_message '( total ticks '
- debug_hex_cx_dx
- debug_message ' ) '
- mov ah,1 ; INT 1Ah function 1
- int 1Ah ; make BIOS set its timer
- ;
- ; Finished, at last
- ;
- debug_message '} '
- ret
- CLK_set_BIOS_time endp
-
- ;
- ; Make our time correspond to the BIOS timer.
- ;
- CLK_get_BIOS_time proc near
- assume ds:cgroup
- debug_message '{ get BIOS '
- ;
- ; Call the BIOS "get timer" interrupt
- ;
- xor ah,ah ; INT 1Ah function 0
- int 1Ah ;
- debug_message '( total ticks '
- debug_hex_cx_dx
- debug_message ' ) '
- ; Tick count is now in combined CX_DX
- ;
- ; If time seems to have run backwards (AL=0 means date didn't change, but
- ; current tick count is less than remembered tick count) then assume the
- ; date changed.
- ;
- or al,al ; if AL <> 0 then
- jnz CLK_get_BIOS_date_change ; the date has changed
- cmp cx, word ptr ds:[CLK_last_tick+2] ; check high word
- ja CLK_get_BIOS_date_nochange ; time went forwards
- jb CLK_get_BIOS_time_backwards ; time went backwards
- cmp dx, word ptr ds:[CLK_last_tick] ; check low word
- jnb CLK_get_BIOS_date_nochange ; time went forwards
- CLK_get_BIOS_time_backwards:
- mov al, 1 ; assume date changed
- ;
- ; Increment the date if necessary.
- ;
- ; NOTE: Just add AL to the current date. AL is guaranteed to be zero if
- ; the date has not changed. On the IBM PC, AL will be 1 if the date has
- ; changed (even if the date has changed more than once). With a non-IBM
- ; BIOS, AL might have some other value. The best possible situation is
- ; AL = (number of times the date has changed). The worst possible situation
- ; is AL = (some arbitrary non-zero value).
- ;
- CLK_get_BIOS_date_change:
- xor ah,ah
- add CLK_date_days, ax
- CLK_get_BIOS_date_nochange:
- ;
- ; Remember the tick count for next time
- ;
- mov word ptr ds:[CLK_last_tick+2],cx
- mov word ptr ds:[CLK_last_tick],dx
- ;
- ;
- ; If for some reason the BIOS tick count is too large, adjust it to the
- ; maximum number of ticks per day.
- ;
- cmp cx,word ptr ds:[CLK_tick_day+2] ; Check high word
- jb CLK_get_BIOS_ok
- ja CLK_get_BIOS_too_big
- cmp dx,word ptr ds:[CLK_tick_day] ; Check high word
- jb CLK_get_BIOS_ok
- CLK_get_BIOS_too_big:
- mov cx,word ptr ds:[CLK_tick_day+2] ; Set to max count
- mov dx,word ptr ds:[CLK_tick_day]
- dec dx ; And subtract 1
- sbb cx,0
- debug_message '( total adjusted to '
- debug_hex_cx_dx
- debug_message ' ) '
- CLK_get_BIOS_ok:
- ;
- ; Now convert the tick count in CX_DX to a number of seconds.
- ; This requires multiplying by 65536 and dividing by 1193180.
- ; The remainder after the division will be used to determine the number
- ; of hundredths of seconds.
- ;
- ; Multiplication by 65536 is done simply by appending sixteen zero bits
- ; to the result (i.e., by shifting the result left 16 bits). This works
- ; because 65536 is 2^16. The 48-bit result (which will actually never be
- ; larger than hex 001800B00000) is then divided by the 32-bit value
- ; 1193180 (hex 001234DC).
- ;
- ; Dividing by a 32-bit value is complicated, but 1193180 factorises into
- ; 59659*20. It is then much simpler to divide by 59659, which can be
- ; represented in 16 bits. The result of this division will be a 32-bit
- ; value (no larger than hex 001A5E00), representing twenty times the
- ; number of seconds.
- ;
- ;
- ; Let A0, A1 and A2 be the three 16-bit portions making
- ; up the dividend. A0, the least significant 16 bits,
- ; will be zero. A1 and A2 are currently stored in DX and
- ; CX, as returned by the BIOS.
- ; Let B be the 16-bit divisor.
- ; Let C0, C1 and C2 be three 16-bit sections of the quotient.
- ; (In fact, C2 will be zero).
- ; Let D be the 16-bit remainder.
- ; Let (Q0,R0), (Q1,R1) and (Q2,R2) be pairs of 16-bit
- ; quotients and remainders resulting from division of a
- ; 32-bit dividend by a 16-bit divisor.
- ;
- ; (Q2,R2) = A2 / B
- ; C2 = Q2
- ; A2 will be less than B, so C2 = 0 and R2 = A2
- ; (Q1,R1) = (2^16*R2 + A1) / B
- ; C1 = Q1
- ; (Q0,R0) = (2^16*R1 + A0) / B
- ; C0 = Q0
- ; D = R0
- ;
- ; There is no need to calculate (Q2,R2).
- ; Calculate (Q1,R1) now.
- mov ax,dx ; AX := A1
- mov dx,cx ; DX := A2
- mov bx,[CLK_tick_65536_20]
- div bx
- mov cx,ax ; CX := Q1 { = C1 }
- ;
- ; Calculate (Q0,R0)
- xor ax,ax ; AX := A0 { = 0 }
- div bx
- ;
- ; Now C0 is in AX, C1 is in CX and D is in DX.
- ;
- ; We now have the number of twentieths of seconds.
- ; Divide by twenty to get number of seconds, and multiply the
- ; remainder from that division by 5 to get the number of extra
- ; hundredths.
- ;
- ;
- ; This is basically the same as the previous long division,
- ; except that the dividend is only 32 bits, and the low part
- ; is not zero.
- ;
- ; Calculate (Q1,R1)
- xchg ax,cx ; AX := A1, CX := A0
- xor dx,dx ; DX := A2 = 0
- mov bx,20
- div bx
- ;
- ; Calculate (Q0,R0)
- xchg ax,cx ; CX := Q1 { = C1 }, AX := A0
- div bx
- ;
- ; Now C0 is in AX, C1 is in CX and D is in DX.
- debug_message '( integer secs '
- debug_hex_word cx
- debug_hex_word ax
- debug_message ' spare ticks '
- debug_hex_word dx
- debug_message ' ) '
- ;
- ; Multiply the remainder (the number of spare twentieths) by 5
- ; to get the number of spare hundredths
- ;
- xchg ax,dx ; AX := remainder, DX := C0
- mov ah,5
- mul ah
- debug_message '( centi '
- debug_hex_byte al
- debug_message ' ) '
- mov CLK_time_hsec,al ; Store the number of hundredths
- ;
- ; Divide by 60 to get the total number of minutes. The remainder will
- ; be the number of loose seconds.
- ;
- mov ax,dx ; AX := low 16 bits
- mov dx,cx ; DX := high 16 bits
- mov bx,60
- div bx ; 32-bit dividend
- debug_message '( integer mins '
- debug_hex_word ax
- debug_message ' remainder '
- debug_hex_word dx
- debug_message ' ) '
- debug_message '( secs '
- debug_hex_byte dl
- debug_message ' ) '
- mov CLK_time_sec,dl ; Remainder is num. seconds
- ;
- ; Divide by 60 to get the number of hours. The remainder will be
- ; the number of minutes.
- ;
- div bl ; 16-bit dividend
- debug_message '( min '
- debug_hex_byte ah
- debug_message ' ) '
- debug_message '( hour '
- debug_hex_byte al
- debug_message ' ) '
- mov CLK_time_min,ah ; Remainder is num. minutes
- mov CLK_time_hour,al ; Quotient is num hours
- ;
- ; If everything worked correctly, the number of hours will not exceed 23.
- ; If there is a bug, it could be fixed by testing for 24 hours here, and
- ; setting the time to 23:59:59.99 instead.
- ;
- ; Finished, at last
- ;
- debug_message '} '
- ret
- CLK_get_BIOS_time endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Look for a clock chip of whatever type.
- ; On entry, AL says what to do:
- ; 0: Don't bother doing anything.
- ; FFh or FEh: Look for all types of clock chip.
- ; other: ASCII character saying what type of chip to look for.
- ; On exit, Carry flag and AL say what was found:
- ; CF set: No clock chip
- ; CF clear, AL=ASCII character saying what type of chip was found.
- ; If a chip was found, other data (such as CLK_chip_IO_base) will also
- ; have been set, as required by that chip.
- ;
-
- startup_code segment
-
- CLK_find_chip proc near
- or al, al
- jz CLK_find_no_chip ; don't bother if zero
- jl CLK_find_any_chip ; try all types if less than zero
- push ax ; remember type for later
- cmp al, '1' ; try MM58167AN ?
- jne CLK_find_chip_2
- call CLK_find_MM58167AN
- jmp CLK_find_chip_pop_ax
- CLK_find_chip_2:
- cmp al, '2' ; try MSM6242 ?
- jne CLK_find_no_chip
- call CLK_find_MSM6242
- ;
- ; Here when chip might or might not have been found, and AX
- ; needs to be popped before returning to caller.
- ;
- CLK_find_chip_pop_ax:
- pop ax
- ret
- ;
- ; Here to try all types of chip
- ;
- CLK_find_any_chip:
- call CLK_find_MM58167AN ; try MM58167AN
- mov al, '1'
- jnc CLK_find_chip_done ; yes ?
- call CLK_find_MSM6242 ; try MSM6242
- mov al, '2'
- jnc CLK_find_chip_done ; yes?
- ;
- ; Here when chip was not found
- ;
- CLK_find_no_chip:
- stc
- CLK_find_chip_done:
- ret
- CLK_find_chip endp
-
- startup_code ends
-
- ;***********************************************************************
-
- ;
- ; Several display output functions intended mainly debugging. Some may also
- ; be used by the startup routine.
- ;
-
- ;
- ; The display_message function is used by the startup routine,
- ; so must be in the resident_code segment if debugging is enabled,
- ; or in the startup_code segment if debugging is disabled.
- ; The same applies to the display_char routine.
- ;
- if do_debug_writes
- resident_code segment
- else
- startup_code segment
- endif ; do_debug_writes
-
- ;
- ; Display the single character in AL.
- ;
- display_char proc near
- ;
- ; Just tell BIOS to display it, in the normal colour.
- ;
- mov ah,0Eh
- mov bx,1
- int 10h
- ret
- display_char endp
-
- ;
- ; Display a message pointed to by DS:SI, with length in CX
- ;
- display_message proc near
- ;
- ; Save registers
- ;
- pushf
- push ax
- push bx
- push cx
- push si
- ;
- ; Loop displaying one byte at a time until count gets to zero
- ;
- cld
- display_msg_loop:
- lodsb
- call display_char
- loop display_msg_loop
- ;
- ; Restore all modified registers, and return.
- ;
- pop si
- pop cx
- pop bx
- pop ax
- popf
- ret
- display_message endp
-
- if do_debug_writes
- resident_code ends
- else
- startup_code ends
- endif ; do_debug_writes
-
- ;
- ; All the remaining functions in this section are used for debugging only
- ;
-
- if do_debug_writes
- resident_code segment
-
- ;
- ; Display the number in AL as a single hexadecimal digit
- ;
- display_hex_digit proc near
- ;
- ; Save registers
- ;
- pushf
- push ax
- push bx
- ;
- ; Convert and display the digit
- ;
- and al,0Fh
- add al,'0'
- cmp al,'9'
- jbe display_hex_output
- add al,'A'-0Ah-'0'
- display_hex_output:
- call display_char
- ;
- ; Restore registers and return
- ;
- pop bx
- pop ax
- popf
- ret
- display_hex_digit endp
-
- ;
- ; Display a byte passed in AL, as two hexadecimal digits.
- ;
- display_hex_byte proc near
- ;
- ; Save registers
- ;
- pushf
- push ax
- push cx
- ;
- ; Isolate and display high digit
- ;
- mov ah,al
- mov cl,4
- shr al,cl
- call display_hex_digit
- ;
- ; Isolate and display low digit
- ;
- mov al,ah
- call display_hex_digit
- ;
- ; Restore registers and return
- ;
- pop cx
- pop ax
- popf
- ret
- display_hex_byte endp
-
- ;
- ; Display several hex bytes, separated by blanks.
- ; Address passed in DS:SI, with count in CX.
- ;
- display_hex_bytes proc near
- ;
- ; Save registers
- ;
- pushf
- push ax
- push bx
- push cx
- push di
- ;
- ; Loop displaying bytes
- ;
- cld
- mov bx,1
- display_bytes_loop:
- lodsb
- call display_hex_byte
- mov al,' '
- call display_char
- loop display_bytes_loop
- ;
- ; Restore regs and return
- ;
- pop di
- pop cx
- pop bx
- pop ax
- popf
- ret
- display_hex_bytes endp
-
- resident_code ends
- endif ; do_debug_writes
-
- ;***********************************************************************
-
- ;
- ; This is the main initialisation code for the CLOCK$ device.
- ;
- ; It prints the initialisation message, processes command line
- ; options (from the DEVICE=... line in the CONFIG.SYS file),
- ; installs pointers to suitable time get and set ruotines (based on the
- ; command line options and the presence or absence of suitable
- ; hardware), and finally returns a pointer to the end of the
- ; permanently resident memory.
- ;
- ; Because the total size of the resident code is small (about 1.5K),
- ; there is no great need to free the space used by routines that deal
- ; with timers that are not present. The code to handle a clock chip thus
- ; remains permanently resident even on a machine with no clock chip, etc.
- ; (Or, if you prefer, I am just too lazy to implement a method of dynamic
- ; relocation so that unused code and data can be freed.)
- ;
-
- startup_data segment
-
- ;
- ; Various flags determined from the command line arguments and from
- ; tests done during the installation process.
- ; For most of these flags, the default value of 0FFh means the
- ; program must determine the correct value by performing tests
- ; during the initialisation. Values 0 and 1 mean respectively
- ; NO and YES. These values may be set by command line arguments or
- ; by tests done during the initialisation.
- ;
- CLK_use_AT_BIOS db 0FFh ; Use AT-style real time clock BIOS support ?
- CLK_use_chip db 0FFh ; Use add-on clock chip ?
- ; (Exception to normal meaning: 0FEh means
- ; command line said -C+ without saying
- ; what type of chip. Positive values are
- ; ASCII chars saying what type of chip
- ; will be used.)
- CLK_use_BIOS db 0FFh ; Use normal BIOS tick counter functions ?
- CLK_give_long_help db 0 ; Print long help message ?
- CLK_were_errors db 0 ; Were there any errors ?
- CLK_no_command_line db 1 ; Were there any command line options ?
-
- ;
- ; Messages displayed by the startup routine
- ;
- illegal_opt_msg db ' Unrecognised option character ignored: "'
- illegal_opt_char db 'X' ; Correct char gets inserted here
- db '".'
- db 13,10
- illegal_opt_msg_len equ $ - illegal_opt_msg
- short_help_msg db 'Use "DEVICE=CLOCK.DEV -H" for help.',13,10
- short_help_msg_len equ $ - short_help_msg
- long_help_msg label byte
- db 'Use "DEVICE=CLOCK.DEV <options>" in the CONFIG.SYS file.',13,10
- db 'Enable option X with "-X", "-X+", "/X" or "/X+"; disable with "-X-", "/X-".',13,10
- db 'If option takes a value, use "-X=value" or "/X=value".',13,10
- db 'Options are:',13,10
- db ' -H Display this help message.',13,10
- db ' (Use "-H-" to disable the short help message.)',13,10
- db ' -A Use real-time clock support provided in AT-type BIOS.',13,10
- db ' -B Use ordinary BIOS timer.',13,10
- db ' -Ctype=addr Use clock chip at the specified hexadecimal '
- db 'I/O address.',13,10
- db ' Type is optional. Use 1 for MM58167AN chip, 2 for MSM6242 '
- db 'chip.',13,10
- db ' Addr is optional. If omitted, program will try to find '
- db 'chip.',13,10
- db 'Default: Program tries to use AT BIOS real time clock. Failing that, '
- db 'it looks',13,10
- db 'for clock chips in several standard locations. If that also '
- db 'fails,',13,10
- db 'it uses the ordinary BIOS timer (but fixes the MS-DOS 3.2 date '
- db 'change bug).',13,10
- db 'Note: If a clock chip is found, ordinary DOS date and time commands '
- db 'will',13,10
- db 'set the chip time. No need for the AT SETUP program or for the TIMER '
- db 'or',13,10
- db 'SETCLOCK and GETCLOCK programs supplied with add-on clock cards.',13,10
- db 13,10
- long_help_msg_len equ $ - long_help_msg
- no_AT_BIOS_msg db ' AT-style clock BIOS support is unavailable.',13,10
- no_AT_BIOS_msg_len equ $ - no_AT_BIOS_msg
- no_chip_default_msg db ' Cannot find clock chip at standard address.',13,10
- no_chip_default_msg_len equ $ - no_chip_default_msg
- no_chip_specified_msg db ' Cannot find clock chip at specified address.',13,10
- no_chip_specified_msg_len equ $ - no_chip_specified_msg
- AT_BIOS_msg db ' DOS date and time operations will use AT BIOS '
- db 'real time clock.',13,10
- AT_BIOS_msg_len equ $ - AT_BIOS_msg
- chip_msg db ' DOS date and time operations will use clock chip.'
- db 13,10
- chip_msg_len equ $ - chip_msg
- BIOS_msg db ' DOS date and time operations will use the standard '
- db 'BIOS timer.',13,10
- BIOS_msg_len equ $ - BIOS_msg
- time_now_msg label byte
- db ' Current date and time: '
- msg_weekday db 'Day '
- msg_day db 'DD '
- msg_month db 'Mmm '
- msg_year db 'YYYY '
- msg_hour db 'hh:'
- msg_min db 'mm:'
- msg_sec db 'ss'
- db 13,10
- time_now_msg_len equ $ - time_now_msg
- month_names db 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec '
- day_names db 'Sun Mon Tue Wed Thu Fri Sat '
-
- startup_data ends
-
- startup_code segment
-
- CLK_init proc near
- debug_message '{ init '
- ;
- ; Print an initialisation message
- ;
- push cs ; Make DS:SI point to string
- pop ds ;
- mov si,offset cgroup:start_message
- mov cx,start_message_len ; Put the length into CX
- call display_message ; Display it
- ;
- ; Make DS:SI point to the command line arguments
- ;
- lds bx,cs:[request_pointer] ; Point to request header
- lds si,ds:[bx].D_INIT_args ; Point to command line
- cld ; Make sure SI gets incremented
- ;
- ; The command line pointer points just after
- ; the equals sign of the DEVICE=... line in the CONFIG.SYS file.
- ;
- ; Skip any initial white space, and the driver name itself.
- ;
- call opt_skip_space ; Ignore white space
- call opt_ignore ; and driver name
- ;
- ; Loop processing option characters. Exit when end of line is reached.
- ;
- CLK_init_opt_loop:
- ;
- ; Skip white space, slash and minus signs.
- ;
- call opt_skip_space
- call opt_skip_slash_minus
- jcond jc,CLK_init_end_options ; Exit if end of line
- ;
- ; Remember that there were command line options.
- ;
- mov cs:[CLK_no_command_line],0
- ;
- ; Get an option character. Carry flag will be set if there are no more
- ; options. Otherwise, option character will be in AL.
- ; The option character will be in upper-case.
- ;
- call opt_get_uppercase_char
- if do_debug_writes
- jcond jc,CLK_init_end_options ; Exit if end of line
- else
- jc CLK_init_end_options ; Exit if end of line
- endif
- debug_message '( option char '
- debug_show_char al
- debug_message ' ) '
- ;
- ; Process the option character
- ;
- cmp al,'H' ; Help
- je CLK_init_opt_H
- cmp al,'A' ; Use AT BIOS clock support
- je CLK_init_opt_A
- cmp al,'B' ; Use normal BIOS timer
- je CLK_init_opt_B
- cmp al,'C' ; Use clock chip
- je CLK_init_opt_C
- ;;; cmp al,'P' ; Prompt user for initial time (not yet implemented)
- ;;; je CLK_init_opt_P
- CLK_init_opt_illegal:
- ;
- ; The option character was illegal. Print a message and skip until
- ; white space or a slash.
- ;
- push ds ; Save DS:SI
- push si
- push cs ; Make DS:SI point to message
- pop ds
- mov si,offset cgroup:illegal_opt_msg
- mov cx,illegal_opt_msg_len ; Message length in CX
- mov ds:[illegal_opt_char],al ; Shove the character into
- ; the message
- call display_message
- mov [CLK_were_errors],1 ; Remember there were errors
- pop si ; Restore DS:SI
- pop ds
- call opt_ignore ; Skip until white space or slash
- jmp CLK_init_opt_loop ; Process next character
- CLK_init_opt_H:
- ;
- ; Check if it was H- or H+, and remember.
- ;
- call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
- mov cs:[CLK_give_long_help],al
- jmp CLK_init_opt_loop ; Process next character
- CLK_init_opt_A:
- ;
- ; Check if it was A- or A+, and remember.
- ;
- call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
- mov cs:[CLK_use_AT_BIOS],al
- jmp CLK_init_opt_loop ; Process next character
- CLK_init_opt_B:
- ;
- ; Check if it was B- or B+, and remember.
- ;
- call opt_get_plus_minus ; Returns AL=1 for "+", 0 for "-"
- mov cs:[CLK_use_BIOS],al
- jmp CLK_init_opt_loop ; Process next character
- CLK_init_opt_C:
- ;
- ; Start by assuming it was -C+
- ;
- mov cs:[CLK_use_chip], 0FEh
- ;
- ; Check for a number immediately after the C (tells what type of chip).
- ; Note: this bypasses opt_get_char etc.
- ;
- mov al, ds:[si] ; next char (just peek)
- cmp al, '1' ; is it in ['1'..'2'] ?
- jb CLK_init_opt_C_nonum ; no
- cmp al, '2'
- ja CLK_init_opt_C_nonum ; no
- mov cs:[CLK_use_chip], al ; yes, remember it
- inc si ; and skip past character
- CLK_init_opt_C_nonum:
- ;
- ; Check if it was C- or C+ or C=
- ;
- call opt_get_plus_minus_eq ; Returns AL=1 for "+", 0 for "-"
- ; AL = 2 for "="
- or al, al ; Was it "C-" ?
- jne CLK_init_opt_C_notminus
- mov cs:[CLK_use_chip], al ; Yes, clear flag
- jmp CLK_init_opt_loop ; and process nect option
- CLK_init_opt_C_notminus:
- cmp al,2 ; Was it "C=" ?
- jcond jne, CLK_init_opt_loop ; No, process next option
- CLK_init_opt_C_equals:
- call opt_get_hex_word ; Yes, get the address
- mov cs:[CLK_chip_IO_base],dx
- jmp CLK_init_opt_loop ; Process next option
- CLK_init_end_options:
- ;
- ; Finished reading the options from the command line.
- ; Now print help message if necessary.
- ;
- push cs ; Make DS point to the right place
- pop ds
- test [CLK_give_long_help],0FFh ; Long help message ?
- jz CLK_init_no_long_help
- mov si, offset cgroup:long_help_msg
- mov cx,long_help_msg_len
- call display_message
- jmp CLK_init_end_help
- CLK_init_no_long_help:
- mov al,[CLK_no_command_line] ; Short help message ?
- or al,[CLK_were_errors] ;
- jz CLK_init_end_help ;
- mov si, offset cgroup:short_help_msg
- mov cx,short_help_msg_len
- call display_message
- CLK_init_end_help:
- ;
- ; Check for AT BIOS clock support.
- ;
- debug_message '{ test for AT BIOS : '
- test [CLK_use_AT_BIOS],0FFh ; Don't even try if told not to
- jz CLK_init_end_AT_BIOS ;
- call CLK_find_AT_BIOS ; Try to use AT BIOS
- jc CLK_init_no_AT_BIOS ; Jump if not found
- mov [CLK_use_AT_BIOS],1 ; Remember it is available
- jmp CLK_init_end_AT_BIOS ;
- CLK_init_no_AT_BIOS:
- ;
- ; If user specified AT BIOS support but there is none, print error message.
- ;
- mov al,[CLK_use_AT_BIOS] ; See what user asked for
- mov [CLK_use_AT_BIOS],0 ; Remember not to use AT BIOS
- test al,al ; Error if user said it
- jl CLK_init_end_AT_BIOS ; ... was available
- mov si, offset cgroup:no_AT_BIOS_msg
- mov cx,no_AT_BIOS_msg_len
- call display_message
- CLK_init_end_AT_BIOS:
- debug_hex_byte [CLK_use_AT_BIOS]
- debug_message '} '
- ;
- ; Check for clock chip support.
- ;
- debug_message '{ test for clock chip : '
- mov al,[CLK_use_chip] ; Check what to do
- or al, al ; Don't even try if told not to
- jz CLK_init_end_chip
- call CLK_find_chip ; Look for the clock chip
- jc CLK_init_no_chip ; Jump if not found
- mov [CLK_use_chip],al ; Remember it is available
- jmp CLK_init_end_chip
- CLK_init_no_chip:
- ;
- ; If user specified clock chip support but there is none, print error message.
- ;
- mov al,[CLK_use_chip] ; See what user said
- mov [CLK_use_chip],0 ; Remember not to use chip
- cmp al, 0FFh ; FF means user said nothing
- je CLK_init_end_chip ; so remain silent
- test al,al ; FFFE means user said -C+
- jl CLK_init_no_default_chip ; "no chip at default addr"
- ; else "no chip at spec addr"
- mov si, offset cgroup:no_chip_specified_msg ; if "-C=xxxx"
- mov cx,no_chip_specified_msg_len
- call display_message
- jmp short CLK_init_end_chip
- CLK_init_no_default_chip:
- mov si, offset cgroup:no_chip_default_msg ; if "-C+" but no chip
- mov cx,no_chip_default_msg_len
- call display_message
- CLK_init_end_chip:
- debug_hex_word [CLK_chip_IO_base]
- debug_message '} '
- ;
- ; If neither AT BIOS nor clock chip support is available, use normal BIOS.
- ;
- mov al,[CLK_use_AT_BIOS] ; Use AT BIOS ?
- xor ah,ah
- or ax,[CLK_chip_IO_base] ; Or use clock chip ?
- jnz CLK_init_end_BIOS ; OK if one or the other
- debug_message '{ Must use normal BIOS } '
- mov [CLK_use_BIOS],1 ; Remember to use normal BIOS
- CLK_init_end_BIOS:
- ;
- ; Store pointers to suitable clock get and set procedures into the
- ; area used by CLK_fix_our_time and CLK_set_other_timers.
- ;
- ; If there is AT BIOS support then
- ; Use AT BIOS
- ; Else If clock chip support then
- ; Use clock chip, but make plain BIOS time match chip time
- ; Else (must be just plain BIOS support)
- ; Use only plain BIOS
- ;
- ; Instal AT BIOS access procedures.
- ;
- test [CLK_use_AT_BIOS],0FFh ; Use AT BIOS ?
- jz CLK_init_store_no_AT_BIOS
- ; Yes, use AT BIOS
- debug_message '( Installing AT BIOS procedures ) '
- mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
- mov [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_AT_BIOS_time
- mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_AT_BIOS_time
- mov si,offset cgroup:AT_BIOS_msg ; Print message
- mov cx,AT_BIOS_msg_len
- call display_message
- jmp CLK_init_store_end
- CLK_init_store_no_AT_BIOS:
- ;
- ; Instal clock chip access procedures.
- ;
- test [CLK_use_chip],0FFh ; Use clock chip ?
- jz CLK_init_store_no_chip
- ; Yes, use chip
- debug_message '( Installing clock chip [type '
- debug_hex_byte [CLK_use_chip]
- debug_message '] procedures ) '
- mov si,offset cgroup:chip_msg ; Print message
- mov cx,chip_msg_len
- call display_message
- cmp [CLK_use_chip],'1' ; MM58167AN chip?
- je CLK_init_store_MM58167AN
- cmp [CLK_use_chip],'2' ; MSM6242 chip?
- je CLK_init_store_MSM6242
- ; should never get here, but just in case...
- debug_message '*** Internal error : Invalid chip type *** '
- jmp CLK_init_store_no_chip ; must have been mistaken!
- CLK_init_store_MM58167AN:
- mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_MM58167AN_time
- mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_MM58167AN_time
- jmp CLK_init_store_end
- CLK_init_store_MSM6242:
- mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
- mov [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_MSM6242_time
- mov [CLK_set_procedure_ptr], offset cgroup:CLK_set_MSM6242_time
- jmp CLK_init_store_end
- CLK_init_store_no_chip:
- ;
- ; Instal plain BIOS access procedures.
- ;
- debug_message '( Installing plain BIOS procedures ) '
- mov si,offset cgroup:BIOS_msg ; Print message
- mov cx,BIOS_msg_len
- call display_message
- mov [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_time
- mov [CLK_set_procedure_ptr], offset cgroup:null_proc
- CLK_init_store_end:
- ;
- ; Get the current date and time.
- ; Ensure that year, month, day and weekday are known.
- ;
- call CLK_fix_our_time ; Get the time from somewhere
- call CLK_set_BIOS_time ; Make plain BIOS time match it
- ; (no harm done if time came from BIOS)
- call CLK_conv_days_ccyymmdd ; Convert to year, month, day
- call CLK_conv_days_weekday ; Find the day of the week
- ;
- ; Format the date and time for printing
- ;
- mov al,[CLK_date_century] ; Century
- call conv_binary_ASCII_decimal
- mov word ptr [msg_year],ax
- mov al,[CLK_date_year] ; Year
- call conv_binary_ASCII_decimal
- mov word ptr [msg_year+2],ax
- mov bl,[CLK_date_month] ; Month
- dec bl ; Subtract 1 and multiply by 4
- shl bl,1 ; to get offset into table
- shl bl,1
- xor bh,bh
- mov ax, word ptr [month_names][bx] ; Copy 4 bytes
- mov word ptr [msg_month],ax
- mov ax, word ptr [month_names][bx+2]
- mov word ptr [msg_month+2],ax
- mov al,[CLK_date_day] ; Day of month
- call conv_binary_ASCII_decimal
- mov word ptr [msg_day],ax
- mov bl,[CLK_date_weekday] ; Day of week
- shl bl,1 ; Multiply by 4
- shl bl,1 ; to get offset into table
- mov ax, word ptr [day_names][bx] ; Copy 4 bytes
- mov word ptr [msg_weekday],ax
- mov ax,word ptr [day_names][bx+2]
- mov word ptr [msg_weekday+2],ax
- mov al,[CLK_time_hour] ; Hour
- call conv_binary_ASCII_decimal
- mov word ptr [msg_hour],ax
- mov al,[CLK_time_min] ; Minute
- call conv_binary_ASCII_decimal
- mov word ptr [msg_min],ax
- mov al,[CLK_time_sec] ; Second
- call conv_binary_ASCII_decimal
- mov word ptr [msg_sec],ax
- ;
- ; Print the date and time.
- ;
- mov si,offset cgroup:time_now_msg
- mov cx,time_now_msg_len
- call display_message
- ;
- ; Return address of end of resident memory.
- ; Note: The IBM manual says it is the address of the first byte to be freed,
- ; while the Microsoft manual says it is the address of the last byte to be
- ; retained. Play safe and return the address of the first byte to be freed.
- ;
- mov ax, offset cgroup:end_resident_memory
- ; This is the first byte to be freed
- lds bx,cs:[request_pointer] ; Make DS:BX point to request
- mov word ptr ds:[bx].D_INIT_end, ax ; Return offset
- mov ax,cs ; Also return segment
- mov word ptr ds:[bx].D_INIT_end[2], ax
- jmp exit_ok
- CLK_init endp
-
- startup_code ends
-
- ;***********************************************************************
-
- ;
- ; Convert a byte to two ASCII decimal digits.
- ; The input binary calue (in AL) must be less than 100.
- ; The two bytes will be in AH (least significant) and AL (most significant).
- ; The bytes are in this order to allow a "MOV [memory],AX" instruction
- ; to put the high digit first.
- ;
-
- resident_code segment
-
- conv_binary_ASCII_decimal proc near
- aam ; AH will be high digit
- ; AL will be low digit
- add ax,'00' ; Convert to ASCII digits
- xchg ah,al ; Swap the order
- ret
- conv_binary_ASCII_decimal endp
-
- resident_code ends
-
- ;***********************************************************************
-
- ;
- ; Some subroutines concerned with command line option processing.
- ; Called with DS:SI pointing to the next character in the command line.
- ; They exit with the carry flag set when the end of the option line
- ; is reached.
- ;
-
- startup_code segment
-
- ;
- ; Get a single character
- ;
-
- opt_get_char proc near
- ;
- ; Get character
- ;
- lodsb ; Get a character
- cmp al,13 ; Is it the end of the line ?
- je opt_get_char_end_line
- cmp al,10
- je opt_get_char_end_line
- ;
- ; Exit with carry clear if not end of line.
- ;
- debug_message '( get_opt_char "'
- debug_show_char al
- debug_message '" hex '
- debug_hex_byte al
- debug_message ' ) '
- clc
- ret
- ;
- ; Decrement the pointer and exit with carry flag set if end of line
- ;
- opt_get_char_end_line:
- debug_message '( get_opt_char end of line ) '
- dec si ; Undo the pointer increment
- stc
- ret
- opt_get_char endp
-
- ;
- ; Get a single character, and convert it to upper-case.
- ;
-
- opt_get_uppercase_char proc near
- ;
- ; Get char and exit if end of line
- ;
- call opt_get_char
- jc opt_uppercase_end
- ;
- ; Convert to uppercase and clear carry flag
- ;
- cmp al,'a'
- jb opt_uppercase_OK ; It was not a lowercase char
- cmp al,'z'
- ja opt_uppercase_OK ; It was not a lowercase char
- add al,'A'-'a' ; Convert it to uppercase
- opt_uppercase_OK:
- clc
- opt_uppercase_end:
- ret
- opt_get_uppercase_char endp
-
- ;
- ; Skip leading white space.
- ;
-
- opt_skip_space proc near
- ;
- ; Get char and exit if end of line.
- ; Loop if it is a white space character.
- ; Exit with pointer at next non-white space character.
- ;
- ; Note: a NUL is treated as a white space character. This is because MS-DOS
- ; version 2 puts a NUL after the driver file name in the command line it
- ; passes to the device driver initialisation routine.
- ;
- opt_skip_space_loop:
- call opt_get_char
- jc opt_skip_end
- cmp al,' ' ; Is it a space ?
- je opt_skip_space_loop
- cmp al,9 ; Is it a TAB ?
- je opt_skip_space_loop
- cmp al,0 ; What about a NUL ?
- je opt_skip_space_loop
- ;
- ; Decrement pointer and exit with carry clear if all is OK
- ;
- dec si
- clc
- opt_skip_end:
- ret
- opt_skip_space endp
-
- ;
- ; Skip slash or minus sign before option character.
- ;
-
- opt_skip_slash_minus proc near
- ;
- ; Get char and exit if end of line.
- ; Un-get the char if not a slash or minus sign.
- ;
- call opt_get_char
- jc opt_skip_slash_end
- cmp al,'/' ; A slash ?
- je opt_skip_slash_OK
- cmp al,'-' ; Or a dash ?
- je opt_skip_slash_OK
- ;
- ; Decrement pointer and exit with carry clear if not a slash or dash
- ;
- dec si
- opt_skip_slash_OK:
- clc
- opt_skip_slash_end:
- ret
- opt_skip_slash_minus endp
-
- ;
- ; Ignore everything until next white space character.
- ;
-
- opt_ignore proc near
- ;
- ; Get char and exit if end of line.
- ; Exit if it is a white space character; else loop.
- ;
- ; Note: a NUL is treated as a white space character. This is because MS-DOS
- ; version 2 puts a NUL after the driver file name in the command line it
- ; passes to the device driver initialisation routine.
- ;
- opt_ignore_loop:
- call opt_get_char
- jc opt_ignore_end
- cmp al,' ' ; Is it a space ?
- je opt_ignore_OK
- cmp al,9 ; Is it a TAB ?
- je opt_ignore_OK
- cmp al,0 ; What about a NUL ?
- je opt_ignore_OK
- jmp opt_ignore_loop ; Loop for next char
- ;
- ; Decrement pointer and exit with carry clear if all is OK
- ;
- opt_ignore_OK:
- dec si
- clc
- opt_ignore_end:
- ret
- opt_ignore endp
-
- ;
- ; Get plus or minus sign (after an option character).
- ; Behave as if a plus sign was present if there was no plus or minus.
- ; Return AL=1 for plus, AL=0 for minus.
- ;
-
- opt_get_plus_minus proc near
- ;
- ; Get char and exit if end of line
- ;
- call opt_get_char
- mov ah,al ; Save char
- mov al,1 ; Assume "+"
- jc opt_plus_minus_end ; Exit if end of line
- ;
- ; Check for plus or minus sign. Decrement pointer if neither.
- ;
- xor al,al ; Ready in case it is a '-'
- cmp ah,'-'
- je opt_plus_minus_OK
- mov al,1 ; Ready in case it is a '+'
- cmp ah,'+'
- je opt_plus_minus_OK
- dec si ; Decrement pointer if neither
- opt_plus_minus_OK:
- clc
- opt_plus_minus_end:
- ret
- opt_get_plus_minus endp
-
- ;
- ; Get plus, minus or equals sign (after an option character).
- ; Behave as if a plus sign was present if there was nothing.
- ; Return AL=1 for plus, AL=0 for minus, AL=2 for equals.
- ;
-
- opt_get_plus_minus_eq proc near
- ;
- ; Get char and exit if end of line
- ;
- call opt_get_char
- mov ah,al ; Save char
- mov al,1 ; Assume "+"
- jc opt_plus_minus_eq_end ; Exit if end of line
- ;
- ; Check for plus or minus sign. Decrement pointer if neither.
- ;
- xor al,al ; Ready in case it is a '-'
- cmp ah,'-'
- je opt_plus_minus_eq_OK
- mov al,2 ; Ready in case it is an '='
- cmp ah,'='
- je opt_plus_minus_eq_OK
- mov al,1 ; Ready in case it is a '+'
- cmp ah,'+'
- je opt_plus_minus_eq_OK
- dec si ; Decrement pointer if neither
- opt_plus_minus_eq_OK:
- clc
- opt_plus_minus_eq_end:
- ret
- opt_get_plus_minus_eq endp
-
- ;
- ; Get a hexadecimal digit.
- ; Exit with carry flag set if end of line,
- ; or with zero flag set if no more hexadecimal digits,
- ; or with value in AL if valid digit found.
- ;
-
- opt_get_hex_digit proc near
- ;
- ; Get char and exit if end of line
- ;
- call opt_get_uppercase_char
- jc opt_hex_digit_end
- ;
- ; Check for hex digit.
- ;
- sub al,'0'
- jl opt_hex_digit_bad
- cmp al,9
- jle opt_hex_digit_OK
- sub al,'A'-'0'
- jl opt_hex_digit_bad
- add al,0Ah
- cmp al,0Fh
- jle opt_hex_digit_OK
- ;
- ; Decrement pointer and exit with carry clear and zero flag set for bad digit.
- ;
- opt_hex_digit_bad:
- dec si
- test al,0 ; Set zero flag and clear carry
- opt_hex_digit_end:
- ret
- ;
- ; Exit with carry and zero clear if no problem
- ;
- opt_hex_digit_OK:
- cmp al,0FFh ; Clear the zero flag
- clc ; Clear the carry flag
- ret
- opt_get_hex_digit endp
-
- ;
- ; Get hexadecimal word (after an option character).
- ; Value is returned in DX.
- ;
-
- opt_get_hex_word proc near
- ;
- ; Start with a value of zero.
- ; Loop until a non-hex character is found.
- ;
- xor dx,dx
- opt_hex_word_loop:
- ;
- ; Get a character and exit if end of line or if no more digits.
- ;
- call opt_get_hex_digit
- jc opt_hex_word_end ; End of line if carry flag set
- jz opt_hex_word_OK ; No more digits if zero flag set
- ;
- ; Shift previous result, add new digit, and loop for next digit.
- ;
- mov cl,4
- shl dx,cl
- xor ah,ah
- add dx,ax
- jmp opt_hex_word_loop
- ;
- ; Exit with no carry if completed successfully.
- ;
- opt_hex_word_OK:
- clc
- opt_hex_word_end:
- ret
- opt_get_hex_word endp
-
- startup_code ends
-
- ;***********************************************************************
-
- if add_test_code
-
- ;
- ; This code is intended to make debugging easier.
- ; If the program is compiled with the add_test_code option, the resulting
- ; .EXE program will not be convertible by EXE2BIN, but can be run as an
- ; ordinary program. It will pass its command line arguments to the
- ; device driver initialisation routine, and will also perform a device
- ; read and write operation, before terminating.
- ; If the program is run under the control of a debugger, passing suitable
- ; arguments to the device driver can be accomplished by modifying the
- ; data in the init_request, read_request and write_request buffers.
- ;
-
- startup_data segment
-
- ;
- ; The command line options passed to the initialisation routine
- ;
- command_line db 128 dup (13)
-
- ;
- ; The date and time, in the format used by the device driver read and write
- ; operations.
- ;
- buffer db 6 dup (?)
-
- ;
- ; A properly formatted initialisation request.
- ;
- init_request label byte
- init_length db 23
- init_unit db 0
- init_command db 0 ; INIT
- init_status dw ?
- init_reserv db 8 dup (0)
- init_units db 0
- init_end dd ?
- init_bpb dd cgroup:command_line
- init_blocknum db 0
-
- ;
- ; A properly formatted read request.
- ;
- read_request label byte
- read_length db 26
- read_unit db 0
- read_command db 4 ; READ
- read_status dw ?
- read_reserv db 8 dup (0)
- read_media db 0
- read_address dd cgroup:buffer
- read_datalen dw 6
- read_start dw 0
- read_volume dd ?
-
- ;
- ; A properly formatted write request.
- ;
- write_request label byte
- write_length db 26
- write_unit db 0
- write_command db 8 ; WRITE
- write_status dw ?
- write_reserv db 8 dup (0)
- write_media db 0
- write_address dd cgroup:buffer
- write_datalen dw 6
- write_start dw 0
- write_volume dd ?
-
- startup_data ends
-
- startup_code segment
-
- ;
- ; Main entry point for testing.
- ;
-
- test_proc proc near
- test_start:
- ;
- ; Copy command line parameters to buffer area.
- ;
- mov si,0081h
- push cs
- pop es
- mov di,offset cgroup:command_line
- cld
- mov cx,127
- rep movsb
- test_init:
- ;
- ; Initialise.
- ;
- mov bx,offset cgroup:init_request
- call test_call
- test_read:
- ;
- ; Read.
- ;
- mov bx,offset cgroup:read_request
- call test_call
- test_write:
- ;
- ; Write.
- ;
- mov bx,offset cgroup:write_request
- call test_call
- test_exit:
- ;
- ; Exit.
- ;
- mov ax,4C00h
- int 21h
- test_proc endp
-
- test_call proc near
- push cs ; Make ES:BX point to request area
- pop es
- nop
- call CLK_strategy ; Call strategy and interrupt
- call CLK_interrupt
- ret
- test_call endp
-
- startup_code ends
-
- endif ; add_test_code
-
- ;***********************************************************************
-
- ; Ugly, isn't it?
-
- if add_test_code
- end_statement equ <end cgroup:test_start>
- else
- end_statement equ <end>
- endif
-
- end_statement
-