/* * (C) Copyright 1992, ..., 2005 the "DOSEMU-Development-Team". * * for details see file COPYING in the DOSEMU distribution */ /* * SIDOC_BEGIN_MODULE * * Description: CMOS handling and RTC emulation * * Maintainers: Alberto Vignani (vignani@tin.it) * * SIDOC_END_MODULE * */ #include #include #include "emu.h" #include "cmos.h" #include "disks.h" #include "timers.h" #include "int.h" #include "iodev.h" long sys_base_ticks = 0; long usr_delta_ticks = 0; unsigned long last_ticks = 0; int cmos_date(int reg) { unsigned char tmp; switch (reg) { case CMOS_SEC: case CMOS_MIN: case CMOS_DOW: case CMOS_DOM: case CMOS_MONTH: case CMOS_YEAR: case CMOS_CENTURY: /* Note - the inline function BCD() in cmos.h will check bit 2 of * status reg B for proper output format */ return BCD(GET_CMOS(reg)); case CMOS_HOUR: /* RTC hour...bit 1 of 0xb set=24 hour mode, clear 12 hour */ tmp = GET_CMOS(reg); /* stored internally as bin, not BCD */ if (!(GET_CMOS(CMOS_STATUSB) & 2)) { /* 12-hour mode */ if (tmp == 0) return BCD(12); else if (tmp > 12) return BCD(tmp-12); } return BCD(tmp); default: h_printf("CMOS: cmos_time() register 0x%02x defaulted to 0\n", reg); return 0; } } /* @@@ MOVE_END @@@ 32768 */ int rtc_int8(int ilevel) /* int70 */ { r_printf("RTC: interrupt\n"); /* XXX - FIXTHIS! where this supposed to be? */ LOCK_CMOS; SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC)&~0x80); UNLOCK_CMOS; return 1; } static void rtc_alarm_check (void) { static u_char last_sec = 0xff; /* any invalid value to begin with */ u_char a,h0,m0,s0,h,m,s; a = GET_CMOS(CMOS_STATUSB); if (a&0x80) return; r_printf("RTC: alarm check e=%#x\n",a); s0=GET_CMOS(CMOS_SEC); m0=GET_CMOS(CMOS_MIN); h0=GET_CMOS(CMOS_HOUR); if (a&0x10) goto do_alrm; if (a&0x20) { /* this is a test for equality - we can't just call a lib time * function because a second could be skipped */ h = GET_CMOS(CMOS_HOURALRM); if (h==0xff || h0==h) { m = GET_CMOS(CMOS_MINALRM); if (m==0xff || m0==m) { s = GET_CMOS(CMOS_SECALRM); if (s==0xff || (s0==s && s0!=last_sec)) { last_sec = s0; r_printf("RTC: got alarm at %02d:%02d:%02d\n",h,m,s); do_alrm: SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC)|0x80); pic_request(PIC_IRQ8); } } } } } void rtc_update (void) /* called every 1s from SIGALRM */ { u_char h0,m0,s0,D0,M0,Y0,C0,days; static const u_char dpm[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; LOCK_CMOS; SET_CMOS(CMOS_STATUSA, GET_CMOS(CMOS_STATUSA)|0x80); s0=GET_CMOS(CMOS_SEC); m0=GET_CMOS(CMOS_MIN); h0=GET_CMOS(CMOS_HOUR); D0=GET_CMOS(CMOS_DOM); M0=GET_CMOS(CMOS_MONTH); Y0=GET_CMOS(CMOS_YEAR); C0=GET_CMOS(CMOS_CENTURY); if ((++s0)>59) { s0=0; if ((++m0)>59) { m0=0; if ((++h0)>23) { h0=0; /* Compute days in current month. */ if (M0>12 || M0<1) M0=1; /* Error! */ days = dpm[M0]; if ((Y0&3) == 0 && M0 == 2) days++; /* Leap year & Feb */ if (++D0>days) { D0=1; if (++M0>12) { M0=1; if (++Y0>99) { Y0=0; ++C0; /* Only 19->20 transition realistic. */ SET_CMOS(CMOS_CENTURY, C0); } SET_CMOS(CMOS_YEAR, Y0); } SET_CMOS(CMOS_MONTH, M0); } SET_CMOS(CMOS_DOM, D0); /* As well as day-of-month, do day-of-week */ days=GET_CMOS(CMOS_DOW); days++; if(days > 7 || days < 1) days=1; SET_CMOS(CMOS_DOW, days); } SET_CMOS(CMOS_HOUR, h0); } SET_CMOS(CMOS_MIN, m0); } SET_CMOS(CMOS_SEC, s0); r_printf("RTC: cmos update %02d:%02d:%02d\n",h0,m0,s0); rtc_alarm_check(); SET_CMOS(CMOS_STATUSA, GET_CMOS(CMOS_STATUSA)&~0x80); UNLOCK_CMOS; } /* * Initialise the RTC. Key work is now in get_linux_ticks() where we initialise * all of the RTC time/date fields, and only zero the alarm stuff here. */ void rtc_init (void) { usr_delta_ticks = 0; last_ticks = sys_base_ticks = get_linux_ticks(1, NULL); LOCK_CMOS; SET_CMOS(CMOS_HOURALRM, 0); SET_CMOS(CMOS_MINALRM, 0); SET_CMOS(CMOS_SECALRM, 0); UNLOCK_CMOS; } /* ========================================================================= */ /* > get_linux_ticks.c * * 1.03 arb Fri Jul 22 19:13:03 BST 2005 - removed config options for logging * suspicious time jumps and using re-entrant-safe calls. * 1.02 arb Fri Aug 6 13:08:21 BST 2004 - tidied up ifdefs * 1.01 arb Mon Jun 14 17:50:00 BST 2004 - added timelog * * Copyright (c) 2004 Paul S. Crawford, All Rights Reserved * */ /* * DANG_BEGIN_MODULE * * Description: Convert LINUX time to DOS / RTC time. * * Maintainers: Paul Crawford * * REMARK * This reads the LINUX time and converts it to the DOS ticks per day * value, and if necessary sets the RTC registers to match. It attempts * to put all LINUX->DOS time conversion to one function. Used for RTC init * and for the INT-1A time calls. * /REMARK * DANG_END_MODULE * */ /* * Configuration: * Define USE_PIT_TICK_RATE to use the PIT tick rate. */ #undef USE_PIT_TICK_RATE /* * Safety check to make sure 'freedos' is not supplied with a tick value greater than one * day (max = 86399.99 seconds) with its not quite correct 1193180 PIC rate (should be * 1193182 I gather). Correct value should be 1573042, so a difference of about 0.15s * just before midnight. * */ #define FREEDOS_TICKS_IN_A_DAY 1573039 /* * This function attempts to place all code for converting LINUX system time to DOS 'ticks' * and/or the CMOS RTC time in one place. As DOS is based on local time (stupid, I know) it * converts the LINUX/UNIX linear UTC time_t in to local time values and from these it computes * the corresponding number of DOS ~18.2Hz interrupt 'ticks' in the current day. If you want/need * UTC in DOS then start dosemu with a TZ=GMT0 environment variable. * * As the DOS notion of time is based on interrupts per day, and a day counter when this * overflows at midnight, this function can also supply the roll-over flag based on the day * having changed since the last time it was called WITH THE FLAG REQUESTED. This allows it * to be used for both the INT-1A AH=0 function (get ticks) and the AH=2 or 4 (get RTC time/date) * without confusing the once per day roll-over count. * * In addition, should the LINUX time go backwards over midnight (very unfortunate occurance, but * it is just possible if ntpdate was used to get things correct at once, rather then the ntm deamon * running to slew time to correctness slowly) it will hold the DOS notion of time at 00:00:00 * until the LINUX time crosses the midnight boundary again, but without producing two day counts. * * The calling arguments are: * * set_cmos : Non-zero to initialise the CMOS RTC date/time fields. Otherwise RTC unchanged. * * day_rollover : Non-NULL pointer if checking for the day crossing for INT-1A AH=0 use. * * The return value is the DOS 'ticks' from 00:00:00 for this day. * An error results in -1 retun (e.g. from problems converting time, perhaps bad TZ environment). * * NOTE: A check in the freedos source shows a *slightly* different value for the nominal tick count * spanning one day, compared to the PIT_TICK_RATE value in timers.h (which is correct, I think). So * if you compile with the freedos values for 100% time conversion compatability with it, that is fine * but if you compile with the 'correct' PIT_TICK_RATE value, it limits it near midnight so freedos * never gets a value corresponding equal to, or more than, 24 hours. * * NOTE: When setting the RTC it will do this in local time, with any "daylight saving" shift applied, * but it makes no attempt to set the DST flag in the RTC. That is a horrible idea anyway, and I do * not see this as being a big problem for anyone. * * Finally, if time matters for anything important - set it to UTC and set TZ=GMT0 ! * */ unsigned long get_linux_ticks(int set_cmos, int *day_rollover) { static long last_ds70 = 0; /* For day changing test. */ long ds70, isec; long long tt; /* Needs > 32 bits for intermediate product! */ static long long last_lint = 0; static struct timeval prev_tv; long long lint; struct timeval tv; int year; struct tm *tm; gettimeofday(&tv, NULL); lint = ((long long)(unsigned long)tv.tv_sec) * 1000; lint += tv.tv_usec / 1000; last_lint = lint; prev_tv = tv; tm = localtime(&tv.tv_sec); if(tm == NULL) return -1; year = 1900 + tm->tm_year; isec = tm->tm_sec + 60 * (tm->tm_min + 60 * tm->tm_hour); /* Seconds in day. */ ds70 = tm->tm_yday + (year-1970)*365L + (year-1969)/4; /* Days from 1970 */ if(last_ds70 == 0) last_ds70 = ds70; /* Compute time of day in 1/100 second units, then to DOS ticks. */ tt = isec * 100 + (tv.tv_usec / 10000); #if USE_PIT_TICK_RATE tt = (tt * PIT_TICK_RATE) / 6553600; /* Correct as per LINUX code, 44-bit intermediate product.*/ if(tt > FREEDOS_TICKS_IN_A_DAY) tt = FREEDOS_TICKS_IN_A_DAY; #else tt = (tt * 59659) / 327680; /* Use same magic numbers as freedos: ke2033/kernel/sysclk.c */ #endif if(day_rollover != NULL) { /* Here we deal with INT-1A AH=0 calls. */ long day_diff = ds70 - last_ds70; *day_rollover = 0; if(day_diff < 0) { /* Day change has us go back over midnight - keep 00:00:00 on same day until time caught up */ tt = 0; } else if(day_diff > 0) { /* Some DOS assume incrementing flag, others only set. Here we return days passed. */ if(day_diff > 255) day_diff = 255; *day_rollover = (int)day_diff; last_ds70 = ds70; /* Re-set day only if flag requested & forward time. */ } } if(set_cmos) { /* Initialise the date settings of the RTC (CMOS clock) */ int cent; LOCK_CMOS; SET_CMOS(CMOS_HOUR, tm->tm_hour); SET_CMOS(CMOS_MIN, tm->tm_min); SET_CMOS(CMOS_SEC, tm->tm_sec); SET_CMOS(CMOS_DOW, tm->tm_wday + 1); SET_CMOS(CMOS_DOM, tm->tm_mday); SET_CMOS(CMOS_MONTH, tm->tm_mon + 1); cent = year/100; SET_CMOS(CMOS_YEAR, year - 100*cent); /* mod 100 */ SET_CMOS(CMOS_CENTURY, cent); /* Really - we don't want to consider daylight saving in the RTC! */ UNLOCK_CMOS; } return (unsigned long)tt; }