Browse Source

idle on hlt instruction

Fabian 2 years ago
parent
commit
74dca95e23
7 changed files with 154 additions and 171 deletions
  1. 1 0
      src/acpi.js
  2. 6 9
      src/apic.js
  3. 31 45
      src/cpu.js
  4. 3 1
      src/hpet.js
  5. 92 109
      src/main.js
  6. 9 4
      src/pit.js
  7. 12 3
      src/rtc.js

+ 1 - 0
src/acpi.js

@@ -144,6 +144,7 @@ ACPI.prototype.timer = function(now)
     }
 
     this.last_timer = timer;
+    return 100; // TODO
 };
 
 ACPI.prototype.get_timer = function(now)

+ 6 - 9
src/apic.js

@@ -373,19 +373,14 @@ APIC.prototype.timer = function(now)
 {
     if(this.timer_current_count === 0)
     {
-        return;
+        return 100;
     }
-    //dbg_log(now + " " + this.next_tick, LOG_APIC);
-
-    var steps = (now - this.next_tick) * APIC_TIMER_FREQ / (1 << this.timer_divider_shift) >>> 0;
 
-    if(steps === 0)
-    {
-        return;
-    }
+    const freq = APIC_TIMER_FREQ / (1 << this.timer_divider_shift);
 
-    this.next_tick += steps / APIC_TIMER_FREQ * (1 << this.timer_divider_shift);
+    const steps = (now - this.next_tick) * freq >>> 0;
 
+    this.next_tick += steps / freq;
     this.timer_current_count -= steps;
 
     if(this.timer_current_count <= 0)
@@ -418,6 +413,8 @@ APIC.prototype.timer = function(now)
             }
         }
     }
+
+    return Math.max(0, this.timer_current_count / freq);
 };
 
 APIC.prototype.route = function(vector, mode, is_level, destination, destination_mode)

+ 31 - 45
src/cpu.js

@@ -11,8 +11,9 @@ var CPU_LOG_VERBOSE = false;
 
 
 /** @constructor */
-function CPU(bus, wm)
+function CPU(bus, wm, next_tick_immediately)
 {
+    this.next_tick_immediately = next_tick_immediately;
     this.wm = wm;
     this.wasm_patch();
     this.create_jit_imports();
@@ -562,15 +563,7 @@ CPU.prototype.main_run = function()
 {
     if(this.in_hlt[0])
     {
-        //if(false)
-        //{
-        //    var _t = this.hlt_loop();
-        //    var t = 0;
-        //}
-        //else
-        //{
-            var t = this.hlt_loop();
-        //}
+        const t = this.hlt_loop();
 
         if(this.in_hlt[0])
         {
@@ -578,7 +571,23 @@ CPU.prototype.main_run = function()
         }
     }
 
-    this.do_run();
+    const start = v86.microtick();
+    let now = start;
+
+    for(; now - start < TIME_PER_FRAME;)
+    {
+        this.do_many_cycles();
+
+        now = v86.microtick();
+
+        const t = this.run_hardware_timers(now);
+        this.handle_irqs();
+
+        if(this.in_hlt[0])
+        {
+            return t;
+        }
+    }
 
     return 0;
 };
@@ -1196,32 +1205,6 @@ CPU.prototype.load_bios = function()
         }.bind(this));
 };
 
-CPU.prototype.do_run = function()
-{
-    /** @type {number} */
-    var start = v86.microtick();
-
-    /** @type {number} */
-    var now = start;
-
-    // outer loop:
-    // runs cycles + timers
-    for(; now - start < TIME_PER_FRAME;)
-    {
-        this.run_hardware_timers(now);
-        this.handle_irqs();
-
-        this.do_many_cycles();
-
-        if(this.in_hlt[0])
-        {
-            return;
-        }
-
-        now = v86.microtick();
-    }
-};
-
 CPU.prototype.do_many_cycles = function()
 {
     if(DEBUG)
@@ -1409,12 +1392,9 @@ CPU.prototype.hlt_loop = function()
 {
     if(this.get_eflags_no_arith() & FLAG_INTERRUPT)
     {
-        //dbg_log("In HLT loop", LOG_CPU);
-
-        this.run_hardware_timers(v86.microtick());
+        const t = this.run_hardware_timers(v86.microtick());
         this.handle_irqs();
-
-        return 0;
+        return t;
     }
     else
     {
@@ -1428,19 +1408,24 @@ CPU.prototype.run_hardware_timers = function(now)
     {
         var pit_time = this.devices.pit.timer(now, this.devices.hpet.legacy_mode);
         var rtc_time = this.devices.rtc.timer(now, this.devices.hpet.legacy_mode);
-        this.devices.hpet.timer(now);
+        var hpet_time = this.devices.hpet.timer(now);
     }
     else
     {
         var pit_time = this.devices.pit.timer(now, false);
         var rtc_time = this.devices.rtc.timer(now, false);
+        var hpet_time = 100;
     }
 
+    let acpi_time = 100;
+    let apic_time = 100;
     if(this.acpi_enabled[0])
     {
-        this.devices.acpi.timer(now);
-        this.devices.apic.timer(now);
+        acpi_time = this.devices.acpi.timer(now);
+        apic_time = this.devices.apic.timer(now);
     }
+
+    return Math.min(pit_time, rtc_time, hpet_time, acpi_time, apic_time);
 };
 
 CPU.prototype.hlt_op = function()
@@ -1467,6 +1452,7 @@ CPU.prototype.handle_irqs = function()
     if(this.get_eflags_no_arith() & FLAG_INTERRUPT)
     {
         this.pic_acknowledge();
+        this.next_tick_immediately();
     }
 };
 

+ 3 - 1
src/hpet.js

@@ -45,7 +45,7 @@ function HPET(cpu)
     {
         if(!hpet_enabled)
         {
-            return;
+            return 100;
         }
 
         var
@@ -107,6 +107,8 @@ function HPET(cpu)
         }
 
         last_check = counter_value;
+
+        return 100; // TODO
     };
 
     function get_counter()

+ 92 - 109
src/main.js

@@ -12,8 +12,11 @@ function v86(bus, wasm)
     /** @type {boolean} */
     this.stopped = false;
 
+    this.tick_counter = 0;
+    this.worker = null;
+
     /** @type {CPU} */
-    this.cpu = new CPU(bus, wasm);
+    this.cpu = new CPU(bus, wasm, () => { this.idle && this.next_tick(0); });
 
     this.bus = bus;
     bus.register("cpu-init", this.init, this);
@@ -21,7 +24,7 @@ function v86(bus, wasm)
     bus.register("cpu-stop", this.stop, this);
     bus.register("cpu-restart", this.restart, this);
 
-    this.register_tick();
+    this.register_yield();
 }
 
 v86.prototype.run = function()
@@ -30,30 +33,40 @@ v86.prototype.run = function()
 
     if(!this.running)
     {
+        this.running = true;
         this.bus.send("emulator-started");
-        this.fast_next_tick();
     }
+
+    this.next_tick(0);
 };
 
 v86.prototype.do_tick = function()
 {
-    if(this.stopped)
+    if(this.stopped || !this.running)
     {
         this.stopped = this.running = false;
         this.bus.send("emulator-stopped");
         return;
     }
 
-    this.running = true;
-    var dt = this.cpu.main_run();
+    this.idle = false;
+    const t = this.cpu.main_run();
 
-    if(dt <= 0)
-    {
-        this.fast_next_tick();
-    }
-    else
+    this.next_tick(t);
+};
+
+v86.prototype.next_tick = function(t)
+{
+    const tick = ++this.tick_counter;
+    this.idle = true;
+    this.yield(t, tick);
+};
+
+v86.prototype.yield_callback = function(tick)
+{
+    if(tick === this.tick_counter)
     {
-        this.next_tick(dt);
+        this.do_tick();
     }
 };
 
@@ -67,7 +80,7 @@ v86.prototype.stop = function()
 
 v86.prototype.destroy = function()
 {
-    this.unregister_tick();
+    this.unregister_yield();
 };
 
 v86.prototype.restart = function()
@@ -82,132 +95,102 @@ v86.prototype.init = function(settings)
     this.bus.send("emulator-ready");
 };
 
-
-if(typeof importScripts === "function" && typeof queueMicrotask === "function")
+if(typeof process !== "undefined")
 {
-    let tick_counter = 0;
-
-    /** @this {v86} */
-    var fast_next_tick = function()
+    v86.prototype.yield = function(t, tick)
     {
-        if(tick_counter === 256)
+        if(t < 1)
         {
-            tick_counter = 0;
-            setTimeout(() => { this.do_tick(); }, 0);
+            global.setImmediate(tick => this.yield_callback(tick), tick);
         }
         else
         {
-            tick_counter++;
-            queueMicrotask(() => { this.do_tick(); });
+            setTimeout(tick => this.yield_callback(tick), t, tick);
         }
     };
 
-    /** @this {v86} */
-    var register_tick = function() {};
-
-    /** @this {v86} */
-    var unregister_tick = function() {};
+    v86.prototype.register_yield = function() {};
+    v86.prototype.unregister_yield = function() {};
 }
-else if(typeof setImmediate !== "undefined")
+else if(typeof Worker !== "undefined")
 {
-    /** @this {v86} */
-    fast_next_tick = function()
-    {
-        setImmediate(() => { this.do_tick(); });
-    };
-
-    /** @this {v86} */
-    register_tick = function() {};
+    // XXX: This has a slightly lower throughput compared to window.postMessage
 
-    /** @this {v86} */
-    unregister_tick = function() {};
-}
-else if(typeof window !== "undefined" && typeof postMessage !== "undefined")
-{
-    // setImmediate shim for the browser.
-    // TODO: Make this deactivatable, for other applications
-    //       using postMessage
-
-    /** @const */
-    let MAGIC_POST_MESSAGE = 0xAA55;
-
-    /** @this {v86} */
-    fast_next_tick = function()
+    function the_worker()
     {
-        window.postMessage(MAGIC_POST_MESSAGE, "*");
-    };
-
-    let tick;
-
-    /** @this {v86} */
-    register_tick = function()
-    {
-        tick = e =>
+        globalThis.onmessage = function(e)
         {
-            if(e.source === window && e.data === MAGIC_POST_MESSAGE)
-            {
-                this.do_tick();
-            }
+            const t = e.data.t;
+            if(t < 1) postMessage(e.data.tick);
+            else setTimeout(() => postMessage(e.data.tick), t);
         };
+    }
 
-        window.addEventListener("message", tick, false);
-    };
-
-    /** @this {v86} */
-    unregister_tick = function()
+    v86.prototype.register_yield = function()
     {
-        window.removeEventListener("message", tick);
-        tick = null;
+        const url = URL.createObjectURL(new Blob(["(" + the_worker.toString() + ")()"], { type: "text/javascript" }));
+        this.worker = new Worker(url);
+        this.worker.onmessage = e => this.yield_callback(e.data);
+        URL.revokeObjectURL(url);
     };
-}
-else
-{
-    /** @this {v86} */
-    fast_next_tick = function()
+
+    v86.prototype.yield = function(t, tick)
     {
-        setTimeout(() => { this.do_tick(); }, 0);
+        this.worker.postMessage({ t, tick });
     };
 
-    /** @this {v86} */
-    register_tick = function() {};
-
-    /** @this {v86} */
-    unregister_tick = function() {};
-}
-
-v86.prototype.fast_next_tick = fast_next_tick;
-v86.prototype.register_tick = register_tick;
-v86.prototype.unregister_tick = unregister_tick;
-
-if(typeof document !== "undefined" && typeof document.hidden === "boolean")
-{
-    /** @this {v86} */
-    var next_tick = function(t)
+    v86.prototype.unregister_yield = function()
     {
-        if(t < 4 || document.hidden)
-        {
-            // Avoid sleeping for 1 second (happens if page is not
-            // visible), it can break boot processes. Also don't try to
-            // sleep for less than 4ms, since the value is clamped up
-            this.fast_next_tick();
-        }
-        else
-        {
-            setTimeout(() => { this.do_tick(); }, t);
-        }
+        this.worker.terminate();
+        this.worker = null;
     };
 }
+//else if(typeof window !== "undefined" && typeof postMessage !== "undefined")
+//{
+//    // setImmediate shim for the browser.
+//    // TODO: Make this deactivatable, for other applications
+//    //       using postMessage
+//
+//    /** @const */
+//    let MAGIC_POST_MESSAGE = 0xAA55;
+//
+//    v86.prototype.yield = function(t)
+//    {
+//        // XXX: Use t
+//        window.postMessage(MAGIC_POST_MESSAGE, "*");
+//    };
+//
+//    let tick;
+//
+//    v86.prototype.register_yield = function()
+//    {
+//        tick = e =>
+//        {
+//            if(e.source === window && e.data === MAGIC_POST_MESSAGE)
+//            {
+//                this.do_tick();
+//            }
+//        };
+//
+//        window.addEventListener("message", tick, false);
+//    };
+//
+//    v86.prototype.unregister_yield = function()
+//    {
+//        window.removeEventListener("message", tick);
+//        tick = null;
+//    };
+//}
 else
 {
-    // In environments that aren't browsers, we might as well use setTimeout
-    /** @this {v86} */
-    next_tick = function(t)
+    v86.prototype.yield = function(t)
     {
         setTimeout(() => { this.do_tick(); }, t);
     };
-}
 
-v86.prototype.next_tick = next_tick;
+    v86.prototype.register_yield = function() {};
+    v86.prototype.unregister_yield = function() {};
+}
 
 v86.prototype.save_state = function()
 {

+ 9 - 4
src/pit.js

@@ -106,8 +106,6 @@ PIT.prototype.timer = function(now, no_irq)
     {
         if(this.counter_enabled[0] && this.did_rollover(0, now))
         {
-            time_to_next_interrupt = 0;
-
             this.counter_start_value[0] = this.get_counter_value(0, now);
             this.counter_start_time[0] = now;
 
@@ -130,8 +128,15 @@ PIT.prototype.timer = function(now, no_irq)
         {
             this.cpu.device_lower_irq(0);
         }
+
+        if(this.counter_enabled[0])
+        {
+            const diff = now - this.counter_start_time[0];
+            const diff_in_ticks = Math.floor(diff * OSCILLATOR_FREQ);
+            const ticks_missing = this.counter_start_value[0] - diff_in_ticks; // XXX: to simplify
+            time_to_next_interrupt = ticks_missing / OSCILLATOR_FREQ;
+        }
     }
-    time_to_next_interrupt = 0;
 
     return time_to_next_interrupt;
 };
@@ -172,7 +177,7 @@ PIT.prototype.did_rollover = function(i, now)
     if(diff < 0)
     {
         // should only happen after restore_state
-        dbg_log("Warning: PIT timer difference is negative, resetting");
+        dbg_log("Warning: PIT timer difference is negative, resetting (timer " + i + ")");
         return true;
     }
     var diff_in_ticks = Math.floor(diff * OSCILLATOR_FREQ);

+ 12 - 3
src/rtc.js

@@ -134,8 +134,6 @@ RTC.prototype.timer = function(time, legacy_mode)
 
         this.next_interrupt += this.periodic_interrupt_time *
                 Math.ceil((time - this.next_interrupt) / this.periodic_interrupt_time);
-
-        return Math.max(0, time - this.next_interrupt);
     }
     else if(this.next_interrupt_alarm && this.next_interrupt_alarm < time)
     {
@@ -145,7 +143,18 @@ RTC.prototype.timer = function(time, legacy_mode)
         this.next_interrupt_alarm = 0;
     }
 
-    return 100;
+    let t = 100;
+
+    if(this.periodic_interrupt && this.next_interrupt)
+    {
+        t = Math.min(t, Math.max(0, this.next_interrupt - time));
+    }
+    if(this.next_interrupt_alarm)
+    {
+        t = Math.min(t, Math.max(0, this.next_interrupt_alarm - time));
+    }
+
+    return t;
 };
 
 RTC.prototype.bcd_pack = function(n)