avoid rounding problems
[qemu] / hw / i8254.c
index 6e96825..ad61a14 100644 (file)
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
-#include <string.h>
-#include <getopt.h>
-#include <inttypes.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <time.h>
-#include <sys/time.h>
-#include <malloc.h>
-#include <termios.h>
-#include <sys/poll.h>
-#include <errno.h>
-#include <sys/wait.h>
-#include <netinet/in.h>
-
-#include "cpu.h"
 #include "vl.h"
 
+//#define DEBUG_PIT
+
 #define RW_STATE_LSB 0
 #define RW_STATE_MSB 1
 #define RW_STATE_WORD0 2
 
 PITChannelState pit_channels[3];
 
+static void pit_irq_timer_update(PITChannelState *s, int64_t current_time);
+
 static int pit_get_count(PITChannelState *s)
 {
     uint64_t d;
     int counter;
 
-    d = muldiv64(cpu_get_ticks() - s->count_load_time, PIT_FREQ, ticks_per_sec);
+    d = muldiv64(qemu_get_clock(vm_clock) - s->count_load_time, PIT_FREQ, ticks_per_sec);
     switch(s->mode) {
     case 0:
     case 1:
@@ -77,12 +61,12 @@ static int pit_get_count(PITChannelState *s)
 }
 
 /* get pit output bit */
-int pit_get_out(PITChannelState *s)
+int pit_get_out(PITChannelState *s, int64_t current_time)
 {
     uint64_t d;
     int out;
 
-    d = muldiv64(cpu_get_ticks() - s->count_load_time, PIT_FREQ, ticks_per_sec);
+    d = muldiv64(current_time - s->count_load_time, PIT_FREQ, ticks_per_sec);
     switch(s->mode) {
     default:
     case 0:
@@ -108,53 +92,55 @@ int pit_get_out(PITChannelState *s)
     return out;
 }
 
-/* get the number of 0 to 1 transitions we had since we call this
-   function */
-/* XXX: maybe better to use ticks precision to avoid getting edges
-   twice if checks are done at very small intervals */
-int pit_get_out_edges(PITChannelState *s)
+/* return -1 if no transition will occur.  */
+static int64_t pit_get_next_transition_time(PITChannelState *s, 
+                                            int64_t current_time)
 {
-    uint64_t d1, d2;
-    int64_t ticks;
-    int ret, v;
+    uint64_t d, next_time, base;
+    int period2;
 
-    ticks = cpu_get_ticks();
-    d1 = muldiv64(s->count_last_edge_check_time - s->count_load_time, 
-                 PIT_FREQ, ticks_per_sec);
-    d2 = muldiv64(ticks - s->count_load_time, 
-                  PIT_FREQ, ticks_per_sec);
-    s->count_last_edge_check_time = ticks;
+    d = muldiv64(current_time - s->count_load_time, PIT_FREQ, ticks_per_sec);
     switch(s->mode) {
     default:
     case 0:
-        if (d1 < s->count && d2 >= s->count)
-            ret = 1;
-        else
-            ret = 0;
-        break;
     case 1:
-        ret = 0;
+        if (d < s->count)
+            next_time = s->count;
+        else
+            return -1;
         break;
     case 2:
-        d1 /= s->count;
-        d2 /= s->count;
-        ret = d2 - d1;
+        base = (d / s->count) * s->count;
+        if ((d - base) == 0 && d != 0)
+            next_time = base + s->count;
+        else
+            next_time = base + s->count + 1;
         break;
     case 3:
-        v = s->count - ((s->count + 1) >> 1);
-        d1 = (d1 + v) / s->count;
-        d2 = (d2 + v) / s->count;
-        ret = d2 - d1;
+        base = (d / s->count) * s->count;
+        period2 = ((s->count + 1) >> 1);
+        if ((d - base) < period2) 
+            next_time = base + period2;
+        else
+            next_time = base + s->count;
         break;
     case 4:
     case 5:
-        if (d1 < s->count && d2 >= s->count)
-            ret = 1;
+        if (d < s->count)
+            next_time = s->count;
+        else if (d == s->count)
+            next_time = s->count + 1;
         else
-            ret = 0;
+            return -1;
         break;
     }
-    return ret;
+    /* convert to timer units */
+    next_time = s->count_load_time + muldiv64(next_time, ticks_per_sec, PIT_FREQ);
+    /* fix potential rounding problems */
+    /* XXX: better solution: use a clock at PIT_FREQ Hz */
+    if (next_time <= current_time)
+        next_time = current_time + 1;
+    return next_time;
 }
 
 /* val must be 0 or 1 */
@@ -170,16 +156,16 @@ void pit_set_gate(PITChannelState *s, int val)
     case 5:
         if (s->gate < val) {
             /* restart counting on rising edge */
-            s->count_load_time = cpu_get_ticks();
-            s->count_last_edge_check_time = s->count_load_time;
+            s->count_load_time = qemu_get_clock(vm_clock);
+            pit_irq_timer_update(s, s->count_load_time);
         }
         break;
     case 2:
     case 3:
         if (s->gate < val) {
             /* restart counting on rising edge */
-            s->count_load_time = cpu_get_ticks();
-            s->count_last_edge_check_time = s->count_load_time;
+            s->count_load_time = qemu_get_clock(vm_clock);
+            pit_irq_timer_update(s, s->count_load_time);
         }
         /* XXX: disable/enable counting */
         break;
@@ -191,14 +177,9 @@ static inline void pit_load_count(PITChannelState *s, int val)
 {
     if (val == 0)
         val = 0x10000;
-    s->count_load_time = cpu_get_ticks();
-    s->count_last_edge_check_time = s->count_load_time;
+    s->count_load_time = qemu_get_clock(vm_clock);
     s->count = val;
-    if (s == &pit_channels[0] && val <= pit_min_timer_count) {
-        fprintf(stderr, 
-                "\nWARNING: qemu: on your system, accurate timer emulation is impossible if its frequency is more than %d Hz. If using a 2.6 guest Linux kernel, you must patch asm/param.h to change HZ from 1000 to 100.\n\n", 
-                PIT_FREQ / pit_min_timer_count);
-    }
+    pit_irq_timer_update(s, s->count_load_time);
 }
 
 static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val)
@@ -222,6 +203,7 @@ static void pit_ioport_write(void *opaque, uint32_t addr, uint32_t val)
             s->mode = (val >> 1) & 7;
             s->bcd = val & 1;
             s->rw_state = access - 1 +  RW_STATE_LSB;
+            /* XXX: update irq timer ? */
             break;
         }
     } else {
@@ -279,18 +261,100 @@ static uint32_t pit_ioport_read(void *opaque, uint32_t addr)
     return ret;
 }
 
-void pit_init(int base)
+static void pit_irq_timer_update(PITChannelState *s, int64_t current_time)
+{
+    int64_t expire_time;
+    int irq_level;
+
+    if (!s->irq_timer)
+        return;
+    expire_time = pit_get_next_transition_time(s, current_time);
+    irq_level = pit_get_out(s, current_time);
+    pic_set_irq(s->irq, irq_level);
+#ifdef DEBUG_PIT
+    printf("irq_level=%d next_delay=%f\n",
+           irq_level, 
+           (double)(expire_time - current_time) / ticks_per_sec);
+#endif
+    s->next_transition_time = expire_time;
+    if (expire_time != -1)
+        qemu_mod_timer(s->irq_timer, expire_time);
+    else
+        qemu_del_timer(s->irq_timer);
+}
+
+static void pit_irq_timer(void *opaque)
+{
+    PITChannelState *s = opaque;
+
+    pit_irq_timer_update(s, s->next_transition_time);
+}
+
+static void pit_save(QEMUFile *f, void *opaque)
+{
+    PITChannelState *s;
+    int i;
+    
+    for(i = 0; i < 3; i++) {
+        s = &pit_channels[i];
+        qemu_put_be32s(f, &s->count);
+        qemu_put_be16s(f, &s->latched_count);
+        qemu_put_8s(f, &s->rw_state);
+        qemu_put_8s(f, &s->mode);
+        qemu_put_8s(f, &s->bcd);
+        qemu_put_8s(f, &s->gate);
+        qemu_put_be64s(f, &s->count_load_time);
+        if (s->irq_timer) {
+            qemu_put_be64s(f, &s->next_transition_time);
+            qemu_put_timer(f, s->irq_timer);
+        }
+    }
+}
+
+static int pit_load(QEMUFile *f, void *opaque, int version_id)
+{
+    PITChannelState *s;
+    int i;
+    
+    if (version_id != 1)
+        return -EINVAL;
+
+    for(i = 0; i < 3; i++) {
+        s = &pit_channels[i];
+        qemu_get_be32s(f, &s->count);
+        qemu_get_be16s(f, &s->latched_count);
+        qemu_get_8s(f, &s->rw_state);
+        qemu_get_8s(f, &s->mode);
+        qemu_get_8s(f, &s->bcd);
+        qemu_get_8s(f, &s->gate);
+        qemu_get_be64s(f, &s->count_load_time);
+        if (s->irq_timer) {
+            qemu_get_be64s(f, &s->next_transition_time);
+            qemu_get_timer(f, s->irq_timer);
+        }
+    }
+    return 0;
+}
+
+void pit_init(int base, int irq)
 {
     PITChannelState *s;
     int i;
 
     for(i = 0;i < 3; i++) {
         s = &pit_channels[i];
+        if (i == 0) {
+            /* the timer 0 is connected to an IRQ */
+            s->irq_timer = qemu_new_timer(vm_clock, pit_irq_timer, s);
+            s->irq = irq;
+        }
         s->mode = 3;
         s->gate = (i != 2);
         pit_load_count(s, 0);
     }
 
+    register_savevm("i8254", base, 1, pit_save, pit_load, NULL);
+
     register_ioport_write(base, 4, 1, pit_ioport_write, NULL);
     register_ioport_read(base, 3, 1, pit_ioport_read, NULL);
 }