tsc crap :/
[seabios.git] / src / clock.c
index aef1be83755e8ed1162f46fb4fa4efd49fbba110..d88515c45c482153144365c90b099c465b5076e8 100644 (file)
 // 16bit code to handle system clocks.
 //
-// Copyright (C) 2008  Kevin O'Connor <kevin@koconnor.net>
+// Copyright (C) 2008-2010  Kevin O'Connor <kevin@koconnor.net>
 // Copyright (C) 2002  MandrakeSoft S.A.
 //
-// This file may be distributed under the terms of the GNU GPLv3 license.
+// This file may be distributed under the terms of the GNU LGPLv3 license.
 
-#include "biosvar.h" // struct bregs
+#include "biosvar.h" // SET_BDA
 #include "util.h" // debug_enter
 #include "disk.h" // floppy_tick
 #include "cmos.h" // inb_cmos
-
-#define DEBUGF1(fmt, args...) bprintf(0, fmt , ##args)
-#define DEBUGF(fmt, args...)
+#include "pic.h" // eoi_pic1
+#include "bregs.h" // struct bregs
+#include "biosvar.h" // GET_GLOBAL
+#include "usb-hid.h" // usb_check_event
 
 // RTC register flags
 #define RTC_A_UIP 0x80
-#define RTC_B_SET 0x80
-#define RTC_B_PIE 0x40
-#define RTC_B_AIE 0x20
-#define RTC_B_UIE 0x10
+
+#define RTC_B_SET  0x80
+#define RTC_B_PIE  0x40
+#define RTC_B_AIE  0x20
+#define RTC_B_UIE  0x10
+#define RTC_B_BIN  0x04
+#define RTC_B_24HR 0x02
+#define RTC_B_DSE  0x01
+
+
+// Bits for PORT_PS2_CTRLB
+#define PPCB_T2GATE (1<<0)
+#define PPCB_SPKR   (1<<1)
+#define PPCB_T2OUT  (1<<5)
+
+// Bits for PORT_PIT_MODE
+#define PM_SEL_TIMER0   (0<<6)
+#define PM_SEL_TIMER1   (1<<6)
+#define PM_SEL_TIMER2   (2<<6)
+#define PM_SEL_READBACK (3<<6)
+#define PM_ACCESS_LATCH  (0<<4)
+#define PM_ACCESS_LOBYTE (1<<4)
+#define PM_ACCESS_HIBYTE (2<<4)
+#define PM_ACCESS_WORD   (3<<4)
+#define PM_MODE0 (0<<1)
+#define PM_MODE1 (1<<1)
+#define PM_MODE2 (2<<1)
+#define PM_MODE3 (3<<1)
+#define PM_MODE4 (4<<1)
+#define PM_MODE5 (5<<1)
+#define PM_CNT_BINARY (0<<0)
+#define PM_CNT_BCD    (1<<0)
+#define PM_READ_COUNTER0 (1<<1)
+#define PM_READ_COUNTER1 (1<<2)
+#define PM_READ_COUNTER2 (1<<3)
+#define PM_READ_STATUSVALUE (0<<4)
+#define PM_READ_VALUE       (1<<4)
+#define PM_READ_STATUS      (2<<4)
+
+
+/****************************************************************
+ * TSC timer
+ ****************************************************************/
+
+#define CALIBRATE_COUNT 0x800   // Approx 1.7ms
+
+u32 cpu_khz VAR16VISIBLE;
+u8 no_tsc VAR16VISIBLE;
+
+static void
+calibrate_tsc(void)
+{
+    u32 eax, ebx, ecx, edx, cpuid_features = 0;
+    cpuid(0, &eax, &ebx, &ecx, &edx);
+    if (eax > 0)
+        cpuid(1, &eax, &ebx, &ecx, &cpuid_features);
+
+    if (!(cpuid_features & CPUID_TSC)) {
+        SET_GLOBAL(no_tsc, 1);
+        SET_GLOBAL(cpu_khz, PIT_TICK_RATE / 1000);
+        dprintf(3, "386/486 class CPU. Using TSC emulation\n");
+        return;
+    }
+
+    // Setup "timer2"
+#if 0
+    u8 orig = inb(PORT_PS2_CTRLB);
+    outb((orig & ~PPCB_SPKR) | PPCB_T2GATE, PORT_PS2_CTRLB);
+    /* binary, mode 0, LSB/MSB, Ch 2 */
+    outb(PM_SEL_TIMER2|PM_ACCESS_WORD|PM_MODE0|PM_CNT_BINARY, PORT_PIT_MODE);
+    /* LSB of ticks */
+    outb(CALIBRATE_COUNT & 0xFF, PORT_PIT_COUNTER2);
+    /* MSB of ticks */
+    outb(CALIBRATE_COUNT >> 8, PORT_PIT_COUNTER2);
+
+    u64 start = rdtscll();
+    while ((inb(PORT_PS2_CTRLB) & PPCB_T2OUT) == 0)
+        ;
+    u64 end = rdtscll();
+
+    // Restore PORT_PS2_CTRLB
+    outb(orig, PORT_PS2_CTRLB);
+
+    // Store calibrated cpu khz.
+    u64 diff = end - start;
+    dprintf(6, "tsc calibrate start=%u end=%u diff=%u\n"
+            , (u32)start, (u32)end, (u32)diff);
+    u32 hz = diff * PIT_TICK_RATE / CALIBRATE_COUNT;
+    SET_GLOBAL(cpu_khz, hz / 1000);
+#else
+       u32 hz = 800 * 1000000;
+    SET_GLOBAL(cpu_khz, hz / 1000);
+#endif
+
+    dprintf(1, "CPU Mhz=%u\n", hz / 1000000);
+}
+
+static u64
+emulate_tsc(void)
+{
+    int cnt, d;
+    u16 ebda_seg = get_ebda_seg();
+    u64 ret;
+    /* read timer 0 current count */
+    ret = GET_EBDA2(ebda_seg, tsc_8254);
+    /* readback mode has slightly shifted registers, works on all 8254, readback PIT0 latch */
+    outb(PM_SEL_READBACK | PM_READ_VALUE | PM_READ_COUNTER0, PORT_PIT_MODE);
+    cnt = (inb(PORT_PIT_COUNTER0) | (inb(PORT_PIT_COUNTER0) << 8));
+    d = GET_EBDA2(ebda_seg, last_tsc_8254) - cnt;
+    /* Determine the ticks count from last invocation of this function */
+    ret += (d > 0) ? d : (PIT_TICK_INTERVAL + d);
+    SET_EBDA2(ebda_seg, last_tsc_8254, cnt);
+    SET_EBDA2(ebda_seg, tsc_8254, ret);
+    return ret;
+}
+
+static u64
+get_tsc(void)
+{
+    if (unlikely(GET_GLOBAL(no_tsc)))
+        return emulate_tsc();
+    return rdtscll();
+}
+
+int
+check_tsc(u64 end)
+{
+    return (s64)(get_tsc() - end) > 0;
+}
+
+static void
+tscdelay(u64 diff)
+{
+    u64 start = get_tsc();
+    u64 end = start + diff;
+    while (!check_tsc(end))
+        cpu_relax();
+}
+
+static void
+tscsleep(u64 diff)
+{
+    u64 start = get_tsc();
+    u64 end = start + diff;
+    while (!check_tsc(end))
+        yield();
+}
+
+void ndelay(u32 count) {
+    tscdelay(count * GET_GLOBAL(cpu_khz) / 1000000);
+}
+void udelay(u32 count) {
+    tscdelay(count * GET_GLOBAL(cpu_khz) / 1000);
+}
+void mdelay(u32 count) {
+    tscdelay(count * GET_GLOBAL(cpu_khz));
+}
+
+void nsleep(u32 count) {
+    tscsleep(count * GET_GLOBAL(cpu_khz) / 1000000);
+}
+void usleep(u32 count) {
+    tscsleep(count * GET_GLOBAL(cpu_khz) / 1000);
+}
+void msleep(u32 count) {
+    tscsleep(count * GET_GLOBAL(cpu_khz));
+}
+
+// Return the TSC value that is 'msecs' time in the future.
+u64
+calc_future_tsc(u32 msecs)
+{
+    u32 khz = GET_GLOBAL(cpu_khz);
+    return get_tsc() + ((u64)khz * msecs);
+}
+u64
+calc_future_tsc_usec(u32 usecs)
+{
+    u32 khz = GET_GLOBAL(cpu_khz);
+    return get_tsc() + ((u64)(khz/1000) * usecs);
+}
 
 
 /****************************************************************
  * Init
  ****************************************************************/
 
+static int
+rtc_updating(void)
+{
+    // This function checks to see if the update-in-progress bit
+    // is set in CMOS Status Register A.  If not, it returns 0.
+    // If it is set, it tries to wait until there is a transition
+    // to 0, and will return 0 if such a transition occurs.  A -1
+    // is returned only after timing out.  The maximum period
+    // that this bit should be set is constrained to (1984+244)
+    // useconds, but we wait for longer just to be sure.
+
+    if ((inb_cmos(CMOS_STATUS_A) & RTC_A_UIP) == 0)
+        return 0;
+    u64 end = calc_future_tsc(15);
+    for (;;) {
+        if ((inb_cmos(CMOS_STATUS_A) & RTC_A_UIP) == 0)
+            return 0;
+        if (check_tsc(end))
+            // update-in-progress never transitioned to 0
+            return -1;
+        yield();
+    }
+}
+
 static void
-pit_setup()
+pit_setup(void)
 {
     // timer0: binary count, 16bit count, mode 2
-    outb(0x34, PORT_PIT_MODE);
+    outb(PM_SEL_TIMER0|PM_ACCESS_WORD|PM_MODE2|PM_CNT_BINARY, PORT_PIT_MODE);
     // maximum count of 0000H = 18.2Hz
     outb(0x0, PORT_PIT_COUNTER0);
     outb(0x0, PORT_PIT_COUNTER0);
 }
 
+static void
+init_rtc(void)
+{
+    outb_cmos(0x26, CMOS_STATUS_A);    // 32,768Khz src, 976.5625us updates
+    u8 regB = inb_cmos(CMOS_STATUS_B);
+    outb_cmos((regB & RTC_B_DSE) | RTC_B_24HR, CMOS_STATUS_B);
+    inb_cmos(CMOS_STATUS_C);
+    inb_cmos(CMOS_STATUS_D);
+}
+
 static u32
 bcd2bin(u8 val)
 {
@@ -42,28 +254,37 @@ bcd2bin(u8 val)
 }
 
 void
-timer_setup()
+timer_setup(void)
 {
     dprintf(3, "init timer\n");
+#if 1
+    calibrate_tsc();
+#endif
+    dprintf(3, "init timer: 01\n");
     pit_setup();
+    dprintf(3, "init timer: 02\n");
 
+    init_rtc();
+    dprintf(3, "init timer: 03\n");
+    rtc_updating();
+    dprintf(3, "init timer: 04\n");
     u32 seconds = bcd2bin(inb_cmos(CMOS_RTC_SECONDS));
-    u32 ticks = (seconds * 18206507) / 1000000;
+    dprintf(3, "init timer: 05\n");
     u32 minutes = bcd2bin(inb_cmos(CMOS_RTC_MINUTES));
-    ticks += (minutes * 10923904) / 10000;
+    dprintf(3, "init timer: 06\n");
     u32 hours = bcd2bin(inb_cmos(CMOS_RTC_HOURS));
-    ticks += (hours * 65543427) / 1000;
+    dprintf(3, "init timer: 07\n");
+    u32 ticks = (hours * 60 + minutes) * 60 + seconds;
+    dprintf(3, "init timer: 08\n");
+    ticks = ((u64)ticks * PIT_TICK_RATE) / PIT_TICK_INTERVAL;
+    dprintf(3, "init timer: 09\n");
     SET_BDA(timer_counter, ticks);
-    SET_BDA(timer_rollover, 0);
-}
+    dprintf(3, "init timer: 10\n");
 
-static void
-init_rtc()
-{
-    outb_cmos(0x26, CMOS_STATUS_A);
-    outb_cmos(0x02, CMOS_STATUS_B);
-    inb_cmos(CMOS_STATUS_C);
-    inb_cmos(CMOS_STATUS_D);
+    enable_hwirq(0, FUNC16(entry_08));
+    dprintf(3, "init timer: 11\n");
+    enable_hwirq(8, FUNC16(entry_70));
+    dprintf(3, "init timer: 12\n");
 }
 
 
@@ -71,30 +292,40 @@ init_rtc()
  * Standard clock functions
  ****************************************************************/
 
-static u8
-rtc_updating()
+#define TICKS_PER_DAY (u32)((u64)60*60*24*PIT_TICK_RATE / PIT_TICK_INTERVAL)
+
+// Calculate the timer value at 'count' number of full timer ticks in
+// the future.
+u32
+calc_future_timer_ticks(u32 count)
 {
-    // This function checks to see if the update-in-progress bit
-    // is set in CMOS Status Register A.  If not, it returns 0.
-    // If it is set, it tries to wait until there is a transition
-    // to 0, and will return 0 if such a transition occurs.  A 1
-    // is returned only after timing out.  The maximum period
-    // that this bit should be set is constrained to 244useconds.
-    // The count I use below guarantees coverage or more than
-    // this time, with any reasonable IPS setting.
+    return (GET_BDA(timer_counter) + count + 1) % TICKS_PER_DAY;
+}
 
-    u16 count = 25000;
-    while (--count != 0) {
-        if ( (inb_cmos(CMOS_STATUS_A) & 0x80) == 0 )
-            return 0;
-    }
-    return 1; // update-in-progress never transitioned to 0
+// Return the timer value that is 'msecs' time in the future.
+u32
+calc_future_timer(u32 msecs)
+{
+    if (!msecs)
+        return GET_BDA(timer_counter);
+    u32 kticks = DIV_ROUND_UP((u64)msecs * PIT_TICK_RATE, PIT_TICK_INTERVAL);
+    u32 ticks = DIV_ROUND_UP(kticks, 1000);
+    return calc_future_timer_ticks(ticks);
+}
+
+// Check if the given timer value has passed.
+int
+check_timer(u32 end)
+{
+    return (((GET_BDA(timer_counter) + TICKS_PER_DAY - end) % TICKS_PER_DAY)
+            < (TICKS_PER_DAY/2));
 }
 
 // get current clock count
 static void
 handle_1a00(struct bregs *regs)
 {
+    yield();
     u32 ticks = GET_BDA(timer_counter);
     regs->cx = ticks >> 16;
     regs->dx = ticks;
@@ -110,6 +341,7 @@ handle_1a01(struct bregs *regs)
     u32 ticks = (regs->cx << 16) | regs->dx;
     SET_BDA(timer_counter, ticks);
     SET_BDA(timer_rollover, 0); // reset flag
+    // XXX - should use set_code_success()?
     regs->ah = 0;
     set_success(regs);
 }
@@ -119,14 +351,14 @@ static void
 handle_1a02(struct bregs *regs)
 {
     if (rtc_updating()) {
-        set_fail(regs);
+        set_invalid(regs);
         return;
     }
 
     regs->dh = inb_cmos(CMOS_RTC_SECONDS);
     regs->cl = inb_cmos(CMOS_RTC_MINUTES);
     regs->ch = inb_cmos(CMOS_RTC_HOURS);
-    regs->dl = inb_cmos(CMOS_STATUS_B) & 0x01;
+    regs->dl = inb_cmos(CMOS_STATUS_B) & RTC_B_DSE;
     regs->ah = 0;
     regs->al = regs->ch;
     set_success(regs);
@@ -154,7 +386,8 @@ handle_1a03(struct bregs *regs)
     outb_cmos(regs->cl, CMOS_RTC_MINUTES);
     outb_cmos(regs->ch, CMOS_RTC_HOURS);
     // Set Daylight Savings time enabled bit to requested value
-    u8 val8 = (inb_cmos(CMOS_STATUS_B) & 0x60) | 0x02 | (regs->dl & 0x01);
+    u8 val8 = ((inb_cmos(CMOS_STATUS_B) & (RTC_B_PIE|RTC_B_AIE))
+               | RTC_B_24HR | (regs->dl & RTC_B_DSE));
     outb_cmos(val8, CMOS_STATUS_B);
     regs->ah = 0;
     regs->al = val8; // val last written to Reg B
@@ -167,13 +400,20 @@ handle_1a04(struct bregs *regs)
 {
     regs->ah = 0;
     if (rtc_updating()) {
-        set_fail(regs);
+        set_invalid(regs);
         return;
     }
     regs->cl = inb_cmos(CMOS_RTC_YEAR);
     regs->dh = inb_cmos(CMOS_RTC_MONTH);
     regs->dl = inb_cmos(CMOS_RTC_DAY_MONTH);
-    regs->ch = inb_cmos(CMOS_CENTURY);
+    if (CONFIG_COREBOOT) {
+        if (regs->cl > 0x80)
+            regs->ch = 0x19;
+        else
+            regs->ch = 0x20;
+    } else {
+        regs->ch = inb_cmos(CMOS_CENTURY);
+    }
     regs->al = regs->ch;
     set_success(regs);
 }
@@ -194,13 +434,14 @@ handle_1a05(struct bregs *regs)
     // My assumption: RegB = (RegB & 01111111b)
     if (rtc_updating()) {
         init_rtc();
-        set_fail(regs);
+        set_invalid(regs);
         return;
     }
     outb_cmos(regs->cl, CMOS_RTC_YEAR);
     outb_cmos(regs->dh, CMOS_RTC_MONTH);
     outb_cmos(regs->dl, CMOS_RTC_DAY_MONTH);
-    outb_cmos(regs->ch, CMOS_CENTURY);
+    if (!CONFIG_COREBOOT)
+        outb_cmos(regs->ch, CMOS_CENTURY);
     // clear halt-clock bit
     u8 val8 = inb_cmos(CMOS_STATUS_B) & ~RTC_B_SET;
     outb_cmos(val8, CMOS_STATUS_B);
@@ -225,9 +466,9 @@ handle_1a06(struct bregs *regs)
     // My assumption: RegB = ((RegB & 01111111b) | 00100000b)
     u8 val8 = inb_cmos(CMOS_STATUS_B); // Get Status Reg B
     regs->ax = 0;
-    if (val8 & 0x20) {
+    if (val8 & RTC_B_AIE) {
         // Alarm interrupt enabled already
-        set_fail(regs);
+        set_invalid(regs);
         return;
     }
     if (rtc_updating()) {
@@ -237,7 +478,6 @@ handle_1a06(struct bregs *regs)
     outb_cmos(regs->dh, CMOS_RTC_SECONDS_ALARM);
     outb_cmos(regs->cl, CMOS_RTC_MINUTES_ALARM);
     outb_cmos(regs->ch, CMOS_RTC_HOURS_ALARM);
-    outb(inb(PORT_PIC2_DATA) & ~PIC2_IRQ8, PORT_PIC2_DATA); // enable IRQ 8
     // enable Status Reg B alarm bit, clear halt clock bit
     outb_cmos((val8 & ~RTC_B_SET) | RTC_B_AIE, CMOS_STATUS_B);
     set_success(regs);
@@ -269,14 +509,14 @@ handle_1a07(struct bregs *regs)
 static void
 handle_1aXX(struct bregs *regs)
 {
-    set_fail(regs);
+    set_unimplemented(regs);
 }
 
 // INT 1Ah Time-of-day Service Entry Point
 void VISIBLE16
 handle_1a(struct bregs *regs)
 {
-    //debug_enter(regs);
+    debug_enter(regs, DEBUG_HDL_1a);
     switch (regs->ah) {
     case 0x00: handle_1a00(regs); break;
     case 0x01: handle_1a01(regs); break;
@@ -291,26 +531,18 @@ handle_1a(struct bregs *regs)
     }
 }
 
-// User Timer Tick
-void VISIBLE16
-handle_1c()
-{
-    //debug_enter(regs);
-}
-
 // INT 08h System Timer ISR Entry Point
 void VISIBLE16
-handle_08()
+handle_08(void)
 {
-    //debug_isr();
-    irq_enable();
+    debug_isr(DEBUG_ISR_08);
 
     floppy_tick();
 
     u32 counter = GET_BDA(timer_counter);
     counter++;
     // compare to one days worth of timer ticks at 18.2 hz
-    if (counter >= 0x001800B0) {
+    if (counter >= TICKS_PER_DAY) {
         // there has been a midnight rollover at this point
         counter = 0;
         SET_BDA(timer_rollover, GET_BDA(timer_rollover) + 1);
@@ -318,14 +550,13 @@ handle_08()
 
     SET_BDA(timer_counter, counter);
 
-    // chain to user timer tick INT #0x1c
-    struct bregs br;
-    memset(&br, 0, sizeof(br));
-    call16_int(0x1c, &br);
+    usb_check_event();
 
-    irq_disable();
+    // chain to user timer tick INT #0x1c
+    u32 eax=0, flags;
+    call16_simpint(0x1c, &eax, &flags);
 
-    eoi_master_pic();
+    eoi_pic1();
 }
 
 
@@ -333,6 +564,32 @@ handle_08()
  * Periodic timer
  ****************************************************************/
 
+void
+useRTC(void)
+{
+    u16 ebda_seg = get_ebda_seg();
+    int count = GET_EBDA2(ebda_seg, RTCusers);
+    SET_EBDA2(ebda_seg, RTCusers, count+1);
+    if (count)
+        return;
+    // Turn on the Periodic Interrupt timer
+    u8 bRegister = inb_cmos(CMOS_STATUS_B);
+    outb_cmos(bRegister | RTC_B_PIE, CMOS_STATUS_B);
+}
+
+void
+releaseRTC(void)
+{
+    u16 ebda_seg = get_ebda_seg();
+    int count = GET_EBDA2(ebda_seg, RTCusers);
+    SET_EBDA2(ebda_seg, RTCusers, count-1);
+    if (count != 1)
+        return;
+    // Clear the Periodic Interrupt.
+    u8 bRegister = inb_cmos(CMOS_STATUS_B);
+    outb_cmos(bRegister & ~RTC_B_PIE, CMOS_STATUS_B);
+}
+
 static int
 set_usertimer(u32 usecs, u16 seg, u16 offset)
 {
@@ -341,70 +598,39 @@ set_usertimer(u32 usecs, u16 seg, u16 offset)
 
     // Interval not already set.
     SET_BDA(rtc_wait_flag, RWS_WAIT_PENDING);  // Set status byte.
-    SET_BDA(ptr_user_wait_complete_flag, (seg << 16) | offset);
+    SET_BDA(user_wait_complete_flag, SEGOFF(seg, offset));
     SET_BDA(user_wait_timeout, usecs);
-
-    // Unmask IRQ8 so INT70 will get through.
-    u8 irqDisable = inb(PORT_PIC2_DATA);
-    outb(irqDisable & ~PIC2_IRQ8, PORT_PIC2_DATA);
-    // Turn on the Periodic Interrupt timer
-    u8 bRegister = inb_cmos(CMOS_STATUS_B);
-    outb_cmos(bRegister | RTC_B_PIE, CMOS_STATUS_B);
-
+    useRTC();
     return 0;
 }
 
 static void
-clear_usertimer()
+clear_usertimer(void)
 {
+    if (!(GET_BDA(rtc_wait_flag) & RWS_WAIT_PENDING))
+        return;
     // Turn off status byte.
     SET_BDA(rtc_wait_flag, 0);
-    // Clear the Periodic Interrupt.
-    u8 bRegister = inb_cmos(CMOS_STATUS_B);
-    outb_cmos(bRegister & ~RTC_B_PIE, CMOS_STATUS_B);
-}
-
-// Sleep for n microseconds.
-int
-usleep(u32 count)
-{
-#ifdef MODE16
-    // In 16bit mode, use the rtc to wait for the specified time.
-    u8 statusflag = 0;
-    int ret = set_usertimer(count, GET_SEG(SS), (u32)&statusflag);
-    if (ret)
-        return -1;
-    irq_enable();
-    while (!statusflag)
-        cpu_relax();
-    irq_disable();
-    return 0;
-#else
-    // In 32bit mode, we need to call into 16bit mode to sleep.
-    struct bregs br;
-    memset(&br, 0, sizeof(br));
-    br.ah = 0x86;
-    br.cx = count >> 16;
-    br.dx = count;
-    call16_int(0x15, &br);
-    if (br.flags & F_CF)
-        return -1;
-    return 0;
-#endif
+    releaseRTC();
 }
 
 #define RET_ECLOCKINUSE  0x83
 
-// Wait for CX:DX microseconds. currently using the
-// refresh request port 0x61 bit4, toggling every 15usec
+// Wait for CX:DX microseconds
 void
 handle_1586(struct bregs *regs)
 {
-    int ret = usleep((regs->cx << 16) | regs->dx);
-    if (ret)
-        set_code_fail(regs, RET_ECLOCKINUSE);
-    else
-        set_success(regs);
+    // Use the rtc to wait for the specified time.
+    u8 statusflag = 0;
+    u32 count = (regs->cx << 16) | regs->dx;
+    int ret = set_usertimer(count, GET_SEG(SS), (u32)&statusflag);
+    if (ret) {
+        set_code_invalid(regs, RET_ECLOCKINUSE);
+        return;
+    }
+    while (!statusflag)
+        wait_irq();
+    set_success(regs);
 }
 
 // Set Interval requested.
@@ -414,7 +640,7 @@ handle_158300(struct bregs *regs)
     int ret = set_usertimer((regs->cx << 16) | regs->dx, regs->es, regs->bx);
     if (ret)
         // Interval already set.
-        set_code_fail(regs, RET_EUNSUPPORTED);
+        set_code_invalid(regs, RET_EUNSUPPORTED);
     else
         set_success(regs);
 }
@@ -430,7 +656,7 @@ handle_158301(struct bregs *regs)
 static void
 handle_1583XX(struct bregs *regs)
 {
-    set_code_fail(regs, RET_EUNSUPPORTED);
+    set_code_unimplemented(regs, RET_EUNSUPPORTED);
     regs->al--;
 }
 
@@ -444,11 +670,13 @@ handle_1583(struct bregs *regs)
     }
 }
 
+#define USEC_PER_RTC DIV_ROUND_CLOSEST(1000000, 1024)
+
 // int70h: IRQ8 - CMOS RTC
 void VISIBLE16
-handle_70()
+handle_70(void)
 {
-    //debug_isr();
+    debug_isr(DEBUG_ISR_70);
 
     // Check which modes are enabled and have occurred.
     u8 registerB = inb_cmos(CMOS_STATUS_B);
@@ -456,38 +684,38 @@ handle_70()
 
     if (!(registerB & (RTC_B_PIE|RTC_B_AIE)))
         goto done;
-    if (registerC & 0x20) {
+    if (registerC & RTC_B_AIE) {
         // Handle Alarm Interrupt.
-        struct bregs br;
-        memset(&br, 0, sizeof(br));
-        call16_int(0x4a, &br);
-        irq_disable();
+        u32 eax=0, flags;
+        call16_simpint(0x4a, &eax, &flags);
     }
-    if (!(registerC & 0x40))
+    if (!(registerC & RTC_B_PIE))
         goto done;
 
     // Handle Periodic Interrupt.
 
+    check_preempt();
+
     if (!GET_BDA(rtc_wait_flag))
         goto done;
 
     // Wait Interval (Int 15, AH=83) active.
     u32 time = GET_BDA(user_wait_timeout);  // Time left in microseconds.
-    if (time < 0x3D1) {
+    if (time < USEC_PER_RTC) {
         // Done waiting - write to specified flag byte.
-        u32 segoff = GET_BDA(ptr_user_wait_complete_flag);
-        u16 segment = segoff >> 16;
-        u16 offset = segoff & 0xffff;
-        u8 oldval = GET_FARVAR(segment, *(u8*)(offset+0));
-        SET_FARVAR(segment, *(u8*)(offset+0), oldval | 0x80);
+        struct segoff_s segoff = GET_BDA(user_wait_complete_flag);
+        u16 ptr_seg = segoff.seg;
+        u8 *ptr_far = (u8*)(segoff.offset+0);
+        u8 oldval = GET_FARVAR(ptr_seg, *ptr_far);
+        SET_FARVAR(ptr_seg, *ptr_far, oldval | 0x80);
 
         clear_usertimer();
     } else {
         // Continue waiting.
-        time -= 0x3D1;
+        time -= USEC_PER_RTC;
         SET_BDA(user_wait_timeout, time);
     }
 
 done:
-    eoi_both_pics();
+    eoi_pic2();
 }