Reduce stack usage for ISRs; minor fixes.
[seabios.git] / src / clock.c
index 5ca2b5ca2bb0e1a3a4c3b723ef79505e4cb8b746..c653c1f3bc38062e9d14c8393b4f422252d0c303 100644 (file)
 #include "biosvar.h" // struct bregs
 #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...)
+
+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);
+}
+
+static u8
+rtc_updating()
+{
+    // 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.
+
+    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
+}
+
+// get current clock count
+static void
+handle_1a00(struct bregs *regs)
+{
+    u32 ticks = GET_BDA(timer_counter);
+    regs->cx = ticks >> 16;
+    regs->dx = ticks;
+    regs->al = GET_BDA(timer_rollover);
+    SET_BDA(timer_rollover, 0); // reset flag
+    set_success(regs);
+}
+
+// Set Current Clock Count
+static void
+handle_1a01(struct bregs *regs)
+{
+    u32 ticks = (regs->cx << 16) | regs->dx;
+    SET_BDA(timer_counter, ticks);
+    SET_BDA(timer_rollover, 0); // reset flag
+    regs->ah = 0;
+    set_success(regs);
+}
+
+// Read CMOS Time
+static void
+handle_1a02(struct bregs *regs)
+{
+    if (rtc_updating()) {
+        set_fail(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->ah = 0;
+    regs->al = regs->ch;
+    set_success(regs);
+}
+
+// Set CMOS Time
+static void
+handle_1a03(struct bregs *regs)
+{
+    // Using a debugger, I notice the following masking/setting
+    // of bits in Status Register B, by setting Reg B to
+    // a few values and getting its value after INT 1A was called.
+    //
+    //        try#1       try#2       try#3
+    // before 1111 1101   0111 1101   0000 0000
+    // after  0110 0010   0110 0010   0000 0010
+    //
+    // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
+    // My assumption: RegB = ((RegB & 01100000b) | 00000010b)
+    if (rtc_updating()) {
+        init_rtc();
+        // fall through as if an update were not in progress
+    }
+    outb_cmos(regs->dh, CMOS_RTC_SECONDS);
+    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);
+    outb_cmos(val8, CMOS_STATUS_B);
+    regs->ah = 0;
+    regs->al = val8; // val last written to Reg B
+    set_success(regs);
+}
+
+// Read CMOS Date
+static void
+handle_1a04(struct bregs *regs)
+{
+    regs->ah = 0;
+    if (rtc_updating()) {
+        set_fail(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);
+    regs->al = regs->ch;
+    set_success(regs);
+}
+
+// Set CMOS Date
+static void
+handle_1a05(struct bregs *regs)
+{
+    // Using a debugger, I notice the following masking/setting
+    // of bits in Status Register B, by setting Reg B to
+    // a few values and getting its value after INT 1A was called.
+    //
+    //        try#1       try#2       try#3       try#4
+    // before 1111 1101   0111 1101   0000 0010   0000 0000
+    // after  0110 1101   0111 1101   0000 0010   0000 0000
+    //
+    // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
+    // My assumption: RegB = (RegB & 01111111b)
+    if (rtc_updating()) {
+        init_rtc();
+        set_fail(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);
+    u8 val8 = inb_cmos(CMOS_STATUS_B) & 0x7f; // clear halt-clock bit
+    outb_cmos(val8, CMOS_STATUS_B);
+    regs->ah = 0;
+    regs->al = val8; // AL = val last written to Reg B
+    set_success(regs);
+}
+
+// Set Alarm Time in CMOS
+static void
+handle_1a06(struct bregs *regs)
+{
+    // Using a debugger, I notice the following masking/setting
+    // of bits in Status Register B, by setting Reg B to
+    // a few values and getting its value after INT 1A was called.
+    //
+    //        try#1       try#2       try#3
+    // before 1101 1111   0101 1111   0000 0000
+    // after  0110 1111   0111 1111   0010 0000
+    //
+    // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
+    // My assumption: RegB = ((RegB & 01111111b) | 00100000b)
+    u8 val8 = inb_cmos(CMOS_STATUS_B); // Get Status Reg B
+    regs->ax = 0;
+    if (val8 & 0x20) {
+        // Alarm interrupt enabled already
+        set_fail(regs);
+        return;
+    }
+    if (rtc_updating()) {
+        init_rtc();
+        // fall through as if an update were not in progress
+    }
+    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 & 0x7f) | 0x20, CMOS_STATUS_B);
+    set_success(regs);
+}
+
+// Turn off Alarm
+static void
+handle_1a07(struct bregs *regs)
+{
+    // Using a debugger, I notice the following masking/setting
+    // of bits in Status Register B, by setting Reg B to
+    // a few values and getting its value after INT 1A was called.
+    //
+    //        try#1       try#2       try#3       try#4
+    // before 1111 1101   0111 1101   0010 0000   0010 0010
+    // after  0100 0101   0101 0101   0000 0000   0000 0010
+    //
+    // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
+    // My assumption: RegB = (RegB & 01010111b)
+    u8 val8 = inb_cmos(CMOS_STATUS_B); // Get Status Reg B
+    // clear clock-halt bit, disable alarm bit
+    outb_cmos(val8 & 0x57, CMOS_STATUS_B); // disable alarm bit
+    regs->ah = 0;
+    regs->al = val8; // val last written to Reg B
+    set_success(regs);
+}
+
+static void
+handle_1ab1(struct bregs *regs)
+{
+    // XXX - pcibios stuff
+    set_fail(regs);
+}
+
+// Unsupported
+static void
+handle_1aXX(struct bregs *regs)
+{
+    set_fail(regs);
+}
 
 // INT 1Ah Time-of-day Service Entry Point
-void VISIBLE
+void VISIBLE16
 handle_1a(struct bregs *regs)
 {
-    debug_enter(regs);
-    set_cf(regs, 1);
+    //debug_enter(regs);
+    switch (regs->ah) {
+    case 0x00: handle_1a00(regs); break;
+    case 0x01: handle_1a01(regs); break;
+    case 0x02: handle_1a02(regs); break;
+    case 0x03: handle_1a03(regs); break;
+    case 0x04: handle_1a04(regs); break;
+    case 0x05: handle_1a05(regs); break;
+    case 0x06: handle_1a06(regs); break;
+    case 0x07: handle_1a07(regs); break;
+    case 0xb1: handle_1ab1(regs); break;
+    default:   handle_1aXX(regs); break;
+    }
 }
 
 // User Timer Tick
-void VISIBLE
-handle_1c(struct bregs *regs)
+void VISIBLE16
+handle_1c()
 {
-    debug_enter(regs);
+    //debug_enter(regs);
 }
 
 // INT 08h System Timer ISR Entry Point
-void VISIBLE
-handle_08(struct bregs *regs)
+void VISIBLE16
+handle_08()
 {
-//    debug_enter(regs);
+    //debug_isr();
+    irq_enable();
 
     floppy_tick();
 
@@ -42,13 +274,117 @@ handle_08(struct bregs *regs)
     }
 
     SET_BDA(timer_counter, counter);
-    // XXX - int #0x1c
+
+    // chain to user timer tick INT #0x1c
+    struct bregs br;
+    memset(&br, 0, sizeof(br));
+    call16_int(0x1c, &br);
+
+    irq_disable();
+
     eoi_master_pic();
 }
 
+// Set Interval requested.
+static void
+handle_158300(struct bregs *regs)
+{
+    if (GET_BDA(rtc_wait_flag) & RWS_WAIT_PENDING) {
+        // Interval already set.
+        DEBUGF("int15: Func 83h, failed, already waiting.\n" );
+        set_code_fail(regs, RET_EUNSUPPORTED);
+    }
+    // Interval not already set.
+    SET_BDA(rtc_wait_flag, RWS_WAIT_PENDING);  // Set status byte.
+    u32 v = (regs->es << 16) | regs->bx;
+    SET_BDA(ptr_user_wait_complete_flag, v);
+    v = (regs->dx << 16) | regs->cx;
+    SET_BDA(user_wait_timeout, v);
+
+    // 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(CMOS_STATUS_B, bRegister | CSB_EN_ALARM_IRQ);
+
+    set_success(regs); // XXX - no set ah?
+}
+
+// Clear interval requested
+static void
+handle_158301(struct bregs *regs)
+{
+    SET_BDA(rtc_wait_flag, 0); // Clear status byte
+    // Turn off the Periodic Interrupt timer
+    u8 bRegister = inb_cmos(CMOS_STATUS_B);
+    outb_cmos(CMOS_STATUS_B, bRegister & ~CSB_EN_ALARM_IRQ);
+    set_success(regs); // XXX - no set ah?
+}
+
+static void
+handle_1583XX(struct bregs *regs)
+{
+    set_code_fail(regs, RET_EUNSUPPORTED);
+    regs->al--;
+}
+
+void
+handle_1583(struct bregs *regs)
+{
+    switch (regs->al) {
+    case 0x00: handle_158300(regs); break;
+    case 0x01: handle_158301(regs); break;
+    default:   handle_1583XX(regs); break;
+    }
+}
+
 // int70h: IRQ8 - CMOS RTC
-void VISIBLE
-handle_70(struct bregs *regs)
+void VISIBLE16
+handle_70()
 {
-    debug_enter(regs);
+    debug_isr();
+
+    // Check which modes are enabled and have occurred.
+    u8 registerB = inb_cmos(CMOS_STATUS_B);
+    u8 registerC = inb_cmos(CMOS_STATUS_C);
+
+    if (!(registerB & 0x60))
+        goto done;
+    if (registerC & 0x20) {
+        // Handle Alarm Interrupt.
+        struct bregs br;
+        memset(&br, 0, sizeof(br));
+        call16_int(0x4a, &br);
+    }
+    if (!(registerC & 0x40))
+        goto done;
+
+    // Handle Periodic Interrupt.
+
+    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) {
+        // Done waiting.
+        u32 segoff = GET_BDA(ptr_user_wait_complete_flag);
+        u16 segment = segoff >> 16;
+        u16 offset = segoff & 0xffff;
+        // Turn off status byte.
+        SET_BDA(rtc_wait_flag, 0);
+        // Clear the Periodic Interrupt.
+        outb_cmos(registerB & 0x37, CMOS_STATUS_B);
+        // Write to specified flag byte.
+        u8 oldval = GET_FARVAR(segment, *(u8*)(offset+0));
+        SET_FARVAR(segment, *(u8*)(offset+0), oldval | 0x80);
+    } else {
+        // Continue waiting.
+        time -= 0x3D1;
+        SET_BDA(user_wait_timeout, time);
+    }
+
+done:
+    eoi_both_pics();
 }