/* * 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; static unsigned long long q_ticks_m = 0; static int rtc_get_rate(Bit8u div) { if (!div) return 0; if (div < 3) div += 7; return (65536 >> div); } void rtc_run(void) { static hitimer_t last_time = -1; int rate; hitimer_t ticks_m, cur_time = GETusTIME(0); if (last_time == -1 || last_time > cur_time || !(GET_CMOS(CMOS_STATUSB) & 0x40)) { last_time = cur_time; return; } rate = rtc_get_rate(GET_CMOS(CMOS_STATUSA) & 0x0f); ticks_m = (cur_time - last_time) * rate; q_ticks_m += ticks_m; last_time = cur_time; if (debug_level('h') > 8) h_printf("RTC: A=%hhx B=%hhx C=%hhx rate=%i queued=%lli added=%lli\n", GET_CMOS(CMOS_STATUSA), GET_CMOS(CMOS_STATUSB), GET_CMOS(CMOS_STATUSC), rate, (long long)q_ticks_m, (long long)ticks_m); if (q_ticks_m >= 1000000) { Bit8u old_c = GET_CMOS(CMOS_STATUSC); SET_CMOS(CMOS_STATUSC, old_c | 0x40); if ((GET_CMOS(CMOS_STATUSB) & 0x40) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) { SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80); if (debug_level('h') > 7) h_printf("RTC: periodic IRQ, queued=%lli, added=%lli\n", (long long)q_ticks_m, (long long)ticks_m); pic_request(PIC_IRQ8); } if (!(old_c & 0x40)) q_ticks_m -= 1000000; } } Bit8u rtc_read(Bit8u reg) { Bit8u ret = GET_CMOS(reg); switch (reg) { case CMOS_SEC: case CMOS_SECALRM: case CMOS_MIN: case CMOS_MINALRM: 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 */ ret = BCD(ret); break; case CMOS_HOUR: /* RTC hour...bit 1 of 0xb set=24 hour mode, clear 12 hour */ case CMOS_HOURALRM: if (!(GET_CMOS(CMOS_STATUSB) & 2)) { /* 12-hour mode */ if (ret == 0) ret = 12; else if (ret > 12) ret -= 12; } ret = BCD(ret); break; case CMOS_STATUSC: if (debug_level('h') > 8) h_printf("RTC: Read C=%hhx\n", ret); SET_CMOS(CMOS_STATUSC, 0); pic_untrigger(PIC_IRQ8); rtc_run(); break; } return ret; } void rtc_write(Bit8u reg, Bit8u byte) { switch (reg) { case CMOS_SEC: case CMOS_MIN: case CMOS_HOUR: case CMOS_SECALRM: case CMOS_MINALRM: case CMOS_HOURALRM: case CMOS_DOW: case CMOS_DOM: case CMOS_MONTH: case CMOS_YEAR: case CMOS_CENTURY: SET_CMOS(reg, BIN(byte)); break; /* b7=r/o and unused * b4-6=always 010 (AT standard 32.768kHz) * b0-3=rate [65536/2^v], default 6, min 3, 0=disable */ case CMOS_STATUSA: h_printf("RTC: Write %hhx to A\n", byte); SET_CMOS(reg, byte & 0x7f); break; case CMOS_STATUSB: h_printf("RTC: Write %hhx to B\n", byte); SET_CMOS(reg, byte); break; case CMOS_STATUSC: case CMOS_STATUSD: h_printf("RTC: attempt to write %hhx to %hhx\n", byte, reg); break; default: SET_CMOS(reg, byte); } q_ticks_m = 0; } static void rtc_alarm_check (void) { static u_char last_sec = 0xff; /* any invalid value to begin with */ u_char h0,m0,s0,h,m,s; s0=GET_CMOS(CMOS_SEC); m0=GET_CMOS(CMOS_MIN); h0=GET_CMOS(CMOS_HOUR); /* 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>=0xc0 || h0==h) { /* Eric: all c0-ff are don't care */ m = GET_CMOS(CMOS_MINALRM); if (m>=0xc0 || m0==m) { s = GET_CMOS(CMOS_SECALRM); if (s>=0xc0 || (s0==s && s0!=last_sec)) { last_sec = s0; h_printf("RTC: got alarm at %02d:%02d:%02d\n",h,m,s); SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x20); if ((GET_CMOS(CMOS_STATUSB) & 0x20) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) { SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80); h_printf("RTC: alarm IRQ\n"); 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}; if (GET_CMOS(CMOS_STATUSA) & 0x80) error("RTC: A/UIP set on update\n"); if (GET_CMOS(CMOS_STATUSB) & 0x80) { h_printf("RTC updates inhibited\n"); return; } 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); h_printf("RTC: cmos update %02d:%02d:%02d, B=%#x\n",h0,m0,s0,GET_CMOS(CMOS_STATUSB)); rtc_alarm_check(); /* per-second IRQ */ SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x10); if ((GET_CMOS(CMOS_STATUSB) & 0x10) && !(GET_CMOS(CMOS_STATUSC) & 0x80)) { SET_CMOS(CMOS_STATUSC, GET_CMOS(CMOS_STATUSC) | 0x80); h_printf("RTC: update IRQ\n"); pic_request(PIC_IRQ8); } SET_CMOS(CMOS_STATUSA, GET_CMOS(CMOS_STATUSA)&~0x80); } /* * 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); SET_CMOS(CMOS_HOURALRM, 0); SET_CMOS(CMOS_MINALRM, 0); SET_CMOS(CMOS_SECALRM, 0); } /* ========================================================================= */ /* > 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! */ 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; 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; 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! */ } return (unsigned long)tt; }