#include "windows.h" /* included for Sleep() */ #include "insignia.h" #include "host_def.h" /* * SoftPC Revision 2.0 * * File : time_day.c * * Title : Time of day * * Sccs ID : @(#)time_day.c 1.27 4/20/94 * * Description : Get/Set time of day * * Author : Henry Nash * * Notes : The PC-XT version has an interrupt 18.203 times a second * to keep the counter up to date. We interrupt at a similar * rate, but because of occasional heavy graphics or disk * operations we lose ticks. In an attempt to still keep * good time, we correct the stored time whenever the host * detects a timer event, using the host time facilities. * * Upon reset time_of_day_init() grabs the host system time & * puts it into the BIOS data area variables. Subsequent * time of day accesses are maintained using the host system * time. This enables well behaved programs to keep good time * even if ticks are missed. * * Mods: (r3.4) : Make use of the host time structures host_timeval, * host_timezone, and host_tm, which are equivalent * to the Unix BSD4.2 structures. * * Removed calls to cpu_sw_interrupt and replaced with * host_simulate */ #ifdef SCCSID static char SccsID[]="@(#)time_day.c 1.27 4/20/94 Copyright Insignia Solutions Ltd."; #endif #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_BIOS.seg" #endif /* * O/S include files. */ #include #include #include TimeH #include TypesH /* * SoftPC include files */ #include "xt.h" #include "sas.h" #include "ios.h" #include CpuH #include "bios.h" #include "fla.h" #include "host.h" #include "timeval.h" #include "timer.h" #include "error.h" #include "cmos.h" #include "cmosbios.h" #include "ica.h" /* * =========================================================================== * Local static data and defines * =========================================================================== */ #ifdef XTSFD # define DAY_COUNT BIOS_VAR_START + 0xCE #endif #ifdef NTVDM BOOL UpDateInProgress(void); #define UPDATE_IN_PROGRESS UpDateInProgress() IMPORT VOID host_init_bda_timer(void); #else #define UPDATE_IN_PROGRESS ( cmos_read(CMOS_REG_A ) & 0x80 ) static sys_addr user_timer_int_vector; static IVT_ENTRY standard_user_timer_int_vector; static IVT_ENTRY compatibility_user_timer_int_vector; #endif #ifdef ANSI LOCAL void get_host_timestamp(word *, word *, half_word *); LOCAL void write_host_timestamp(int, int); LOCAL void TimeToTicks(int, int, int, word *, word *); LOCAL void get_host_time(int *, int *, int *); #else LOCAL void get_host_timestamp(); LOCAL void write_host_timestamp(); LOCAL void TimeToTicks(); LOCAL void get_host_time(); #endif /* ANSI */ #define TICKS_PER_HOUR 65543L #define TICKS_PER_MIN 1092L #define TICKS_PER_SEC 18L /* * ============================================================================ * External functions * ============================================================================ */ void time_of_day() { /* * BIOS function to return the number of PC interrupts since boot. */ half_word mask; word low, high; half_word overflow, alarm; /* * Block the Alarm signal whilst we are looking at the clock timer */ #ifdef BSD4_2 host_block_timer(); #endif switch (getAH()) { case 0x00: /* Get time */ #ifdef NTVDM sas_loadw(TIMER_LOW, &low); setDX(low); sas_loadw(TIMER_HIGH, &high); setCX(high); sas_load(TIMER_OVFL, &overflow); setAL(overflow); sas_store(TIMER_OVFL, 0); /* Always write zero after read */ #else /* ! NTVDM */ #ifndef PROD if (host_getenv("TIME_OF_DAY_FRIG") == NULL){ #endif /* * First get the time from the host */ get_host_timestamp(&low, &high, &overflow); /* * Use it to return the time AND overwrite the BIOS data */ setDX(low); sas_storew(TIMER_LOW, low); setCX(high); sas_storew(TIMER_HIGH, high); setAL(overflow); sas_store(TIMER_OVFL, 0); /* Always write zero after read */ #ifndef PROD }else{ SAVED int first=1; if (first){ first = 0; printf ("FRIG ALERT!!!! - time of day frozen!\n"); } setDX(1); sas_storew(TIMER_LOW, 1); setCX(1); sas_storew(TIMER_HIGH, 1); setAL(0); sas_store(TIMER_OVFL, 0); /* Always write zero after read */ } #endif #endif /* NTVDM */ break; case 0x01: /* Set time */ /* * Load the BIOS variables */ sas_storew(TIMER_LOW, getDX()); sas_storew(TIMER_HIGH, getCX()); sas_store(TIMER_OVFL, 0); #ifndef NTVDM /* * Also the host timestamp */ write_host_timestamp(getDX(), getCX()); #endif break; case 2: /* read the real time clock */ #ifndef NTVDM #ifndef PROD if (host_getenv("TIME_OF_DAY_FRIG") == NULL){ #endif #endif if( UPDATE_IN_PROGRESS ) setCF(1); else { setDH( cmos_read( CMOS_SECONDS ) ); setDL( cmos_read( CMOS_REG_B ) & 1 ); /* DSE bit */ setCL( cmos_read( CMOS_MINUTES ) ); setCH( cmos_read( CMOS_HOURS ) ); setCF(0); } #ifndef NTVDM #ifndef PROD }else{ SAVED int first=1; if (first){ first = 0; printf ("FRIG ALERT!!!! - real time clock frozen!\n"); } setDH( 1 ); setDL( 0 ); /* DSE bit */ setCL( 1 ); setCH( 1 ); setCF(0); } #endif #endif break; case 3: /* Set the real time clock */ if( UPDATE_IN_PROGRESS ) { /* initialise real time clock */ cmos_write( CMOS_REG_A, 0x26 ); cmos_write( CMOS_REG_B, 0x82 ); cmos_read( CMOS_REG_C ); cmos_read( CMOS_REG_D ); } cmos_write( CMOS_SECONDS, getDH() ); cmos_write( CMOS_MINUTES, getCL() ); cmos_write( CMOS_HOURS, getCH() ); alarm = ( cmos_read( CMOS_REG_B ) & 0x62 ) | 2; alarm |= (getDL() & 1); /* only use the DSE bit */ cmos_write( CMOS_REG_B, alarm ); setCF(0); break; case 4: /* read the date from the real time clock */ #ifndef NTVDM #ifndef PROD if (host_getenv("TIME_OF_DAY_FRIG") == NULL){ #endif #endif if( UPDATE_IN_PROGRESS ) setCF(1); else { setDL( cmos_read( CMOS_DAY_MONTH ) ); setDH( cmos_read( CMOS_MONTH ) ); setCL( cmos_read( CMOS_YEAR ) ); setCH( cmos_read( CMOS_CENTURY ) ); setCF(0); } #ifndef NTVDM #ifndef PROD }else{ SAVED int first=1; if (first){ first = 0; printf ("FRIG ALERT!!!! - date frozen!\n"); } setDL( 1 ); setDH( 4 ); setCL( 91 ); setCH( 19 ); setCF(0); } #endif #endif break; case 5: /* Set the date into the real time clock */ if( UPDATE_IN_PROGRESS ) { /* initialise real time clock */ cmos_write( CMOS_REG_A, 0x26 ); cmos_write( CMOS_REG_B, 0x82 ); cmos_read( CMOS_REG_C ); cmos_read( CMOS_REG_D ); } cmos_write( CMOS_DAY_WEEK, 0 ); cmos_write( CMOS_DAY_MONTH, getDL() ); cmos_write( CMOS_MONTH, getDH() ); cmos_write( CMOS_YEAR, getCL() ); cmos_write( CMOS_CENTURY, getCH() ); alarm = cmos_read( CMOS_REG_B ) & 0x7f; /* clear 'set bit' */ cmos_write( CMOS_REG_B, alarm); setCF(0); break; case 6: /* set the alarm */ if( cmos_read(CMOS_REG_B) & 0x20 ) /* alarm already enabled? */ { setCF(1); #ifdef BSD4_2 host_release_timer(); #endif return; } if( UPDATE_IN_PROGRESS ) { /* initialise real time clock */ cmos_write( CMOS_REG_A, 0x26 ); cmos_write( CMOS_REG_B, 0x82 ); cmos_read( CMOS_REG_C ); cmos_read( CMOS_REG_D ); } cmos_write( CMOS_SEC_ALARM, getDH() ); cmos_write( CMOS_MIN_ALARM, getCL() ); cmos_write( CMOS_HR_ALARM, getCH() ); inb( ICA1_PORT_1, &mask ); mask &= 0xfe; /* enable alarm timer int. */ outb( ICA1_PORT_1, mask ); alarm = cmos_read( CMOS_REG_B ) & 0x7f; /* ensure set bit turned off */ alarm |= 0x20; /* turn on alarm enable */ cmos_write( CMOS_REG_B, alarm ); break; case 7: alarm = cmos_read( CMOS_REG_B ); alarm &= 0x57; /* turn off alarm enable */ cmos_write( CMOS_REG_B, alarm ); break; #ifdef XTSFD case 0x0A: { word count; sas_loadw(DAY_COUNT, &count); setCX( count ); break; } case 0x0B: sas_storew(DAY_COUNT, getCX() ); break; default: setCF( 1 ); #else default: ; /* Do nothing */ #endif } setAH( 0 ); #ifdef BSD4_2 host_release_timer(); #endif } void time_int() { /* * NT port does everything in 16 bit int08 handler */ #ifndef NTVDM /* * The BIOS timer interrupt routine. */ word low, high; half_word motor_count, motor_flags; /* * Increment the low portion */ sas_loadw(TIMER_LOW, &low); sas_storew(TIMER_LOW, ++low); /* 1.9.92 MG We need to actually load the timer high value before doing the 24 hour test below here. */ sas_loadw(TIMER_HIGH, &high); if (low == 0) { /* * Timer has wrapped so update the high count */ sas_storew(TIMER_HIGH, ++high); } /* * Wrap at 24 hrs */ if (high == 0x0018 && low == 0x00b0) { sas_storew(TIMER_LOW, 0x0000); sas_storew(TIMER_HIGH, 0x0000); sas_store(TIMER_OVFL, 0x01); } /* * Decrement motor count */ sas_load(MOTOR_COUNT, &motor_count); if(motor_count < 4) motor_count = 0; else motor_count -= 4; sas_store(MOTOR_COUNT, motor_count); if (motor_count == 0) { /* * Turn off motor running bits */ sas_load(MOTOR_STATUS,&motor_flags); motor_flags &= 0xF0; sas_store(MOTOR_STATUS,motor_flags); /* * Provided FLA is not busy, then actually turn the motor off. */ if (!fla_busy) outb(DISKETTE_DOR_REG, 0x0C); } if ( getVM() || ((standard_user_timer_int_vector.all != sas_dw_at(user_timer_int_vector)) && (compatibility_user_timer_int_vector.all != sas_dw_at(user_timer_int_vector))) ) /* * There is a user time routine defined - so lets call it */ { exec_sw_interrupt(USER_TIMER_INT_SEGMENT, USER_TIMER_INT_OFFSET); } #endif /* NTVDM */ } /* * ============================================================================ * Internal Functions * ============================================================================ */ /* * NT's sense of time in the bios data area is always * kept in sync with the real systems tic count * Most of the compensation to readjust tics according * to the time of day stuff is not needed */ #ifndef NTVDM /* * The routines get_host_timestamp() and write_host_timestamp() are used to * override the BIOS record of time, since timer events are known to be lost. * Internally the routines work in seconds and microseconds, using the "timeval" * struct provided by 4.2BSD. Since System V does not provide this, we supply a * version of the 4.2BSD gettimeofday() function locally, making use of the * System V function ticks(). */ /* * Our own timestamp for calculating PC time */ static struct host_timeval time_stamp; LOCAL void get_host_timestamp(low, high, overflow) word *low, *high; half_word *overflow; { /* * Provide the time in PC interrupts since startup, in the * 32-bit value high:low. The parameter overflow is set to 1 * if a 24-hour boundary has been passed since the last call. */ struct host_timeval now, interval; struct host_timezone junk; /* Not used */ unsigned long ticks; /* Total ticks elapsed */ long days; SAVED long last_time = 0; long hours, mins, secs; /* * Obtain the current time (since host boot-up) */ host_gettimeofday(&now, &junk); /* * Calculate how long has passed since the time stamp */ interval.tv_sec = now.tv_sec - time_stamp.tv_sec; interval.tv_usec = now.tv_usec - time_stamp.tv_usec; /* * Handle the "borrow" correction */ if (interval.tv_sec > 0 && interval.tv_usec < 0) { interval.tv_usec += 1000000L; interval.tv_sec -= 1; }; /* * TMM 8/1/92: * ----------- * * If someone changes the date forwards by >= 24 hours then we should set * the overflow flag and ensure that we don't return an interval greater * than 24 hours. If the date has changed by >= 48 hours then we will have * lost a day. So we put up a panel to tell the user. * * If some one has set the date backwards and the interval has gone * negative then all we can do is put up an error panel informing * the user and ensure that we don't set the interval to a negative * value. * * Notes: * * 1. Setting the overflow flag causes DOS to add a day onto the current * date. * * 2. Setting the interval to a value greater than 24 hours causes DOS * to print a "Divide Overflow" error. * * 3. Setting the interval to a -ve value causes DOS to go into an * infinite loop printing "Divide Overflow". */ days = interval.tv_sec / (24 * 60 * 60); if (days >= 1) { /* * Someone has set the clock forwards, or we have been frozen for a * couple of days. Ensure that the interval is not more than 24 hours, * adjust the time_stamp to take care of the lost days. */ interval.tv_sec %= 24 * 60 * 60; time_stamp.tv_sec += days * (24 * 60 * 60); if (days > 1) { host_error (EG_DATE_FWD, ERR_CONT | ERR_RESET, ""); } *overflow = 1; } else if (interval.tv_sec < 0) { /* * Somebody has set the clock backwards, all we can do is maintain * the same time that we had before the clock went back. */ time_stamp.tv_sec -= (last_time - now.tv_sec ); interval.tv_sec = now.tv_sec - time_stamp.tv_sec; *overflow = 0; host_error (EG_DATE_BACK, ERR_CONT | ERR_RESET, ""); } else *overflow = 0; /* * Convert seconds to hours/minutes/seconds */ hours = interval.tv_sec / (60L*60L); /* Hours */ interval.tv_sec %= (60L*60L); mins = interval.tv_sec / 60L; /* Minutes */ secs = interval.tv_sec % 60L; /* Seconds */ /* * Now convert the interval into PC ticks * One tick lasts 54925 microseconds. */ ticks = hours * TICKS_PER_HOUR + mins * TICKS_PER_MIN + secs * TICKS_PER_SEC + interval.tv_usec/54925 ; /* * Split the value into two 16-bit quantities and return */ *low = ticks & 0xffff; *high = ticks >> 16; } LOCAL void write_host_timestamp(low, high) int low, high; { /* * Update our timestamp so that subsequent calls of get_host_timestamp * return the correct value. A call of get_host_timestamp() made immediately * after this call must return the values set here, so set the timestamp * to be the current time less the value set here. */ struct host_timeval now, interval; struct host_timezone junk; /* Not used */ long lowms; /* * Get the current time. */ host_gettimeofday(&now, &junk); interval.tv_sec = high * 3599 + high/2; /* high ticks to seconds */ /* * The multiply below can overflow, which has the interesting effect * of making Softpc 1 hr 12 mins 40 secs (4300 secs, or 2^32 us) slow * if booted in the last third of every hour. So compensate by * letting the overflow occur and correcting interval by 4300 secs. */ lowms = (IS32) (low & 0xffff) * 54925 + (low & 0xffff)/2; if (low > 39098) interval.tv_sec += 4300; interval.tv_sec += lowms / 1000000; interval.tv_usec = lowms % 1000000; /* * The timestamp is the current time less this interval */ time_stamp.tv_sec = now.tv_sec - interval.tv_sec; time_stamp.tv_usec = now.tv_usec - interval.tv_usec; /* * Handle the "borrow" correction, including negative timestamps */ if (time_stamp.tv_sec > 0 && time_stamp.tv_usec < 0) { time_stamp.tv_usec += 1000000L; time_stamp.tv_sec -= 1; } else if (time_stamp.tv_sec < 0 && time_stamp.tv_usec > 0) { time_stamp.tv_usec -= 1000000L; time_stamp.tv_sec += 1; } } #ifdef SEGMENTATION /* * The following #include specifies the code segment into which this * module will by placed by the MPW C compiler on the Mac II running * MultiFinder. */ #include "SOFTPC_INIT.seg" #endif LOCAL void get_host_time( h, m, s ) int *h, *m, *s; /* hours, minutes and secs */ { struct host_tm *tp; time_t SecsSince1970; SecsSince1970 = host_time(NULL); tp = host_localtime(&SecsSince1970); *h = tp->tm_hour; *m = tp->tm_min; *s = tp->tm_sec; } /* ** Take a normal time in hours, minutes and seconds then ** transmutate it into PC ticks since the beginning of the day. */ LOCAL void TimeToTicks( hour, minutes, sec, low, hi ) int hour, minutes, sec; /* inputs */ word *low, *hi; /* outputs */ { unsigned long ticks; /* Total ticks elapsed */ /* * Calculate ticks to date */ ticks = hour * TICKS_PER_HOUR + minutes * TICKS_PER_MIN + sec * TICKS_PER_SEC; /* * Split the value into two 16-bit quantities and return */ *low = ticks & 0xffff; *hi = ticks >> 16; } #endif /* ifndef NTVDM */ void time_of_day_init() { #ifndef NTVDM int hour, minutes, sec; /* Current host time */ word low, hi; /* Host time in PC ticks */ /* * Initialise the clock timer. */ get_host_time( &hour, &minutes, &sec ); /* get the time from the host */ TimeToTicks( hour, minutes, sec, &low, &hi ); /* convert to PC time */ sas_storew(TIMER_LOW, low ); sas_storew(TIMER_HIGH, hi ); sas_store(TIMER_OVFL,0x01); /* * Initialise the host time stamp */ write_host_timestamp( low, hi ); /* * Build the standard IVT entry for the user timer interrupt(s) */ compatibility_user_timer_int_vector.all = ((double_word)ADDR_COMPATIBILITY_SEGMENT << 16) + ADDR_COMPATIBILITY_OFFSET; standard_user_timer_int_vector.all = ((double_word)DUMMY_INT_SEGMENT << 16) + DUMMY_INT_OFFSET; user_timer_int_vector = BIOS_USER_TIMER_INT * 4; #endif /* NTVDM */ } #ifdef NTVDM /* * NTVDM: the rtc is setup so that the UIP bit is set on a cmos * port read if the cmos ports haven't been touched for at least * 1 second. The IBM pc bios routine for accessing the clock * polls RegA for UIP bit in a tight loop 600h times before * failing the call. This means that MOST of the time the int1ah * rtc fns almost never fail! To mimic this behaviour we poll * the port until success, since we know that our rtc will clear * UIP bit very quickly. */ BOOL UpDateInProgress(void) { while (cmos_read(CMOS_REG_A) & 0x80) { Sleep(0); // give other threads a chance to work } return FALSE; } #endif