diff --git a/src/browser/starter.js b/src/browser/starter.js index ecd21613e7..1000c26fe0 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -66,6 +66,17 @@ export function V86(options) "cpu_event_halt": () => { this.emulator_bus.send("cpu-event-halt"); }, "abort": function() { dbg_assert(false); }, "microtick": v86.microtick, + "get_epoch_milis": function () { return Date.now(); }, + "getDate": function (date) { return (new Date(date)).getUTCDate(); }, + "getDay": function (date) { return (new Date(date)).getUTCDay(); }, + "getFullYear": function (date) { return (new Date(date)).getUTCFullYear(); }, + "getMonth": function (date) { return (new Date(date)).getUTCMonth(); }, + "getHours": function (date) { return (new Date(date)).getUTCHours(); }, + "getMinutes": function (date) { return (new Date(date)).getUTCMinutes(); }, + "getSeconds": function (date) { return (new Date(date)).getUTCSeconds(); }, + "newDate": function (year, month, day, hour, minute, second) { + return new Date(Date.UTC(year, month, day, hour, minute, second)); + }, "get_rand_int": function() { return get_rand_int(); }, "stop_idling": function() { return cpu.stop_idling(); }, diff --git a/src/cpu.js b/src/cpu.js index bd8687571d..d391194f84 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -425,6 +425,45 @@ CPU.prototype.wasm_patch = function() this.get_apic_addr = get_import("get_apic_addr"); this.get_ioapic_addr = get_import("get_ioapic_addr"); + this.rtc_new = get_import("rtc_new"); + this.port_70_write = get_import("port_70_write"); + this.port_71_read = get_import("port_71_read"); + this.port_71_write = get_import("port_71_write"); + this.cmos_read = get_import("cmos_read"); + this.cmos_write = get_import("cmos_write"); + this.rtc_timer = get_import("rtc_timer"); + this.get_cmos_index = get_import("get_cmos_index"); + this.set_cmos_index = get_import("set_cmos_index"); + this.get_cmos_data_byte = get_import("get_cmos_data_byte"); + this.set_cmos_data_byte = get_import("set_cmos_data_byte"); + this.get_rtc_time = get_import("get_rtc_time"); + this.set_rtc_time = get_import("set_rtc_time"); + this.get_last_update = get_import("get_last_update"); + this.set_last_update = get_import("set_last_update"); + this.get_next_interrupt = get_import("get_next_interrupt"); + this.set_next_interrupt = get_import("set_next_interrupt"); + this.get_next_interrupt_alarm = get_import("get_next_interrupt_alarm"); + this.set_next_interrupt_alarm = get_import("set_next_interrupt_alarm"); + this.get_periodic_interrupt = get_import("get_periodic_interrupt"); + this.set_periodic_interrupt = get_import("set_periodic_interrupt"); + this.get_periodic_interrupt_time = get_import("get_periodic_interrupt_time"); + this.set_periodic_interrupt_time = get_import("set_periodic_interrupt_time"); + this.get_cmos_a = get_import("get_cmos_a"); + this.set_cmos_a = get_import("set_cmos_a"); + this.get_cmos_b = get_import("get_cmos_b"); + this.set_cmos_b = get_import("set_cmos_b"); + this.get_cmos_c = get_import("get_cmos_c"); + this.set_cmos_c = get_import("set_cmos_c"); + this.get_cmos_diag_status = get_import("get_cmos_diag_status"); + this.set_cmos_diag_status = get_import("set_cmos_diag_status"); + this.get_nmi_disabled = get_import("get_nmi_disabled"); + this.set_nmi_disabled = get_import("set_nmi_disabled"); + this.get_update_interrupt = get_import("get_update_interrupt"); + this.set_update_interrupt = get_import("set_update_interrupt"); + this.get_update_interrupt_time = get_import("get_update_interrupt_time"); + this.set_update_interrupt_time = get_import("set_update_interrupt_time"); + + this.zstd_create_ctx = get_import("zstd_create_ctx"); this.zstd_get_src_ptr = get_import("zstd_get_src_ptr"); this.zstd_free_ctx = get_import("zstd_free_ctx"); diff --git a/src/rtc.js b/src/rtc.js index c5e2c08951..4a1dc7f00f 100644 --- a/src/rtc.js +++ b/src/rtc.js @@ -64,343 +64,71 @@ export function RTC(cpu) { /** @const @type {CPU} */ this.cpu = cpu; + this.rtc = cpu.rtc_new(); - this.cmos_index = 0; - this.cmos_data = new Uint8Array(128); - - // used for cmos entries - this.rtc_time = Date.now(); - this.last_update = this.rtc_time; - - // used for periodic interrupt - this.next_interrupt = 0; - - // next alarm interrupt - this.next_interrupt_alarm = 0; - - this.periodic_interrupt = false; - - // corresponds to default value for cmos_a - this.periodic_interrupt_time = 1000 / 1024; - - this.cmos_a = 0x26; - this.cmos_b = 2; - this.cmos_c = 0; - - this.cmos_diag_status = 0; - - this.nmi_disabled = 0; - - this.update_interrupt = false; - this.update_interrupt_time = 0; + cpu.io.register_write(0x70, this, function(out_byte) { + this.cpu.port_70_write(this.rtc, out_byte & 0x7F); + }); - cpu.io.register_write(0x70, this, function(out_byte) - { - this.cmos_index = out_byte & 0x7F; - this.nmi_disabled = out_byte >> 7; + cpu.io.register_write(0x71, this, function(value) { + this.cpu.port_71_write(this.rtc, value); }); - cpu.io.register_write(0x71, this, this.cmos_port_write); - cpu.io.register_read(0x71, this, this.cmos_port_read); + cpu.io.register_read(0x71, this, function() { + return this.cpu.port_71_read(this.rtc); + }); } RTC.prototype.get_state = function() { var state = []; - state[0] = this.cmos_index; - state[1] = this.cmos_data; - state[2] = this.rtc_time; - state[3] = this.last_update; - state[4] = this.next_interrupt; - state[5] = this.next_interrupt_alarm; - state[6] = this.periodic_interrupt; - state[7] = this.periodic_interrupt_time; - state[8] = this.cmos_a; - state[9] = this.cmos_b; - state[10] = this.cmos_c; - state[11] = this.nmi_disabled; - state[12] = this.update_interrupt; - state[13] = this.update_interrupt_time; - state[14] = this.cmos_diag_status; + state[0] = Number(this.cpu.get_cmos_index(this.rtc)); + var cmos_data = []; + for(var i = 0; i < 128; i++) { + cmos_data[i] = this.cpu.get_cmos_data_byte(this.rtc, i); + } + state[1] = cmos_data; + state[2] = this.cpu.get_rtc_time(this.rtc); + state[3] = this.cpu.get_last_update(this.rtc); + state[4] = this.cpu.get_next_interrupt(this.rtc); + state[5] = this.cpu.get_next_interrupt_alarm(this.rtc); + state[6] = this.cpu.get_periodic_interrupt(this.rtc); + state[7] = this.cpu.get_periodic_interrupt_time(this.rtc); + state[8] = this.cpu.get_cmos_a(this.rtc); + state[9] = this.cpu.get_cmos_b(this.rtc); + state[10] = this.cpu.get_cmos_c(this.rtc); + state[11] = this.cpu.get_nmi_disabled(this.rtc); + state[12] = this.cpu.get_update_interrupt(this.rtc); + state[13] = this.cpu.get_update_interrupt_time(this.rtc); + state[14] = this.cpu.get_cmos_diag_status(this.rtc); return state; }; RTC.prototype.set_state = function(state) { - this.cmos_index = state[0]; - this.cmos_data = state[1]; - this.rtc_time = state[2]; - this.last_update = state[3]; - this.next_interrupt = state[4]; - this.next_interrupt_alarm = state[5]; - this.periodic_interrupt = state[6]; - this.periodic_interrupt_time = state[7]; - this.cmos_a = state[8]; - this.cmos_b = state[9]; - this.cmos_c = state[10]; - this.nmi_disabled = state[11]; - this.update_interrupt = state[12] || false; - this.update_interrupt_time = state[13] || 0; - this.cmos_diag_status = state[14] || 0; -}; - -RTC.prototype.timer = function(time, legacy_mode) -{ - time = Date.now(); // XXX - this.rtc_time += time - this.last_update; - this.last_update = time; - - if(this.periodic_interrupt && this.next_interrupt < time) - { - this.cpu.device_raise_irq(8); - this.cmos_c |= 1 << 6 | 1 << 7; - - this.next_interrupt += this.periodic_interrupt_time * - Math.ceil((time - this.next_interrupt) / this.periodic_interrupt_time); - } - else if(this.next_interrupt_alarm && this.next_interrupt_alarm < time) - { - this.cpu.device_raise_irq(8); - this.cmos_c |= 1 << 5 | 1 << 7; - - this.next_interrupt_alarm = 0; - } - else if(this.update_interrupt && this.update_interrupt_time < time) - { - this.cpu.device_raise_irq(8); - this.cmos_c |= 1 << 4 | 1 << 7; - - this.update_interrupt_time = time + 1000; // 1 second - } - - 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)); - } - if(this.update_interrupt) - { - t = Math.min(t, Math.max(0, this.update_interrupt_time - time)); - } - - return t; -}; - -RTC.prototype.bcd_pack = function(n) -{ - var i = 0, - result = 0, - digit; - - while(n) - { - digit = n % 10; - - result |= digit << (4 * i); - i++; - n = (n - digit) / 10; - } - - return result; -}; - -RTC.prototype.bcd_unpack = function(n) -{ - const low = n & 0xF; - const high = n >> 4 & 0xF; - - dbg_assert(n < 0x100); - dbg_assert(low < 10); - dbg_assert(high < 10); - - return low + 10 * high; -}; - -RTC.prototype.encode_time = function(t) -{ - if(this.cmos_b & 4) - { - // binary mode - return t; - } - else - { - return this.bcd_pack(t); - } -}; - -RTC.prototype.decode_time = function(t) -{ - if(this.cmos_b & 4) - { - // binary mode - return t; - } - else - { - return this.bcd_unpack(t); + this.cpu.set_cmos_index(this.rtc, state[0]); + for(var i = 0; i < 128; i++) { + this.cpu.set_cmos_data_byte(this.rtc, i, state[1][i]); } + this.cpu.set_rtc_time(this.rtc, state[2]); + this.cpu.set_last_update(this.rtc, state[3]); + this.cpu.set_next_interrupt(this.rtc, state[4]); + this.cpu.set_next_interrupt_alarm(this.rtc, state[5]); + this.cpu.set_periodic_interrupt(this.rtc, state[6]); + this.cpu.set_periodic_interrupt_time(this.rtc, state[7]); + this.cpu.set_cmos_a(this.rtc, state[8]); + this.cpu.set_cmos_b(this.rtc, state[9]); + this.cpu.set_cmos_c(this.rtc, state[10]); + this.cpu.set_nmi_disabled(this.rtc, state[11]); + this.cpu.set_update_interrupt(this.rtc, state[12] || false); + this.cpu.set_update_interrupt_time(this.rtc, state[13] || 0); + this.cpu.set_cmos_diag_status(this.rtc, state[14] || 0); }; -// TODO -// - interrupt on update -// - countdown -// - letting bios/os set values -// (none of these are used by seabios or the OSes we're -// currently testing) -RTC.prototype.cmos_port_read = function() -{ - var index = this.cmos_index; - - //this.cmos_index = 0xD; - - switch(index) - { - case CMOS_RTC_SECONDS: - dbg_log("read second: " + h(this.encode_time(new Date(this.rtc_time).getUTCSeconds())), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCSeconds()); - case CMOS_RTC_MINUTES: - dbg_log("read minute: " + h(this.encode_time(new Date(this.rtc_time).getUTCMinutes())), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCMinutes()); - case CMOS_RTC_HOURS: - dbg_log("read hour: " + h(this.encode_time(new Date(this.rtc_time).getUTCHours())), LOG_RTC); - // TODO: 12 hour mode - return this.encode_time(new Date(this.rtc_time).getUTCHours()); - case CMOS_RTC_DAY_WEEK: - dbg_log("read day: " + h(this.encode_time(new Date(this.rtc_time).getUTCDay() + 1)), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCDay() + 1); - case CMOS_RTC_DAY_MONTH: - dbg_log("read day of month: " + h(this.encode_time(new Date(this.rtc_time).getUTCDate())), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCDate()); - case CMOS_RTC_MONTH: - dbg_log("read month: " + h(this.encode_time(new Date(this.rtc_time).getUTCMonth() + 1)), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCMonth() + 1); - case CMOS_RTC_YEAR: - dbg_log("read year: " + h(this.encode_time(new Date(this.rtc_time).getUTCFullYear() % 100)), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCFullYear() % 100); - - case CMOS_STATUS_A: - if(v86.microtick() % 1000 >= 999) - { - // Set update-in-progress for one millisecond every second (we - // may not have precision higher than that in browser - // environments) - return this.cmos_a | 0x80; - } - return this.cmos_a; - case CMOS_STATUS_B: - //dbg_log("cmos read from index " + h(index)); - return this.cmos_b; - - case CMOS_STATUS_C: - // It is important to know that upon a IRQ 8, Status Register C - // will contain a bitmask telling which interrupt happened. - // What is important is that if register C is not read after an - // IRQ 8, then the interrupt will not happen again. - this.cpu.device_lower_irq(8); - - dbg_log("cmos reg C read", LOG_RTC); - // Missing IRQF flag - //return cmos_b & 0x70; - var c = this.cmos_c; - - this.cmos_c &= ~0xF0; - - return c; - - case CMOS_STATUS_D: - return 1 << 7; // CMOS battery charged - - case CMOS_DIAG_STATUS: - dbg_log("cmos diagnostic status read", LOG_RTC); - return this.cmos_diag_status; - - case CMOS_CENTURY: - case CMOS_CENTURY2: - dbg_log("read century: " + h(this.encode_time(new Date(this.rtc_time).getUTCFullYear() / 100 | 0)), LOG_RTC); - return this.encode_time(new Date(this.rtc_time).getUTCFullYear() / 100 | 0); - - default: - dbg_log("cmos read from index " + h(index), LOG_RTC); - return this.cmos_data[this.cmos_index]; - } -}; - -RTC.prototype.cmos_port_write = function(data_byte) -{ - switch(this.cmos_index) - { - case 0xA: - this.cmos_a = data_byte & 0x7F; - this.periodic_interrupt_time = 1000 / (32768 >> (this.cmos_a & 0xF) - 1); - - dbg_log("Periodic interrupt, a=" + h(this.cmos_a, 2) + " t=" + this.periodic_interrupt_time , LOG_RTC); - break; - case 0xB: - this.cmos_b = data_byte; - if(this.cmos_b & 0x80) - { - // remove update interrupt flag - this.cmos_b &= 0xEF; - } - if(this.cmos_b & 0x40) - { - this.next_interrupt = Date.now(); - } - - if(this.cmos_b & 0x20) - { - const now = new Date(); - - const seconds = this.decode_time(this.cmos_data[CMOS_RTC_SECONDS_ALARM]); - const minutes = this.decode_time(this.cmos_data[CMOS_RTC_MINUTES_ALARM]); - const hours = this.decode_time(this.cmos_data[CMOS_RTC_HOURS_ALARM]); - - const alarm_date = new Date(Date.UTC( - now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), - hours, minutes, seconds - )); - - const ms_from_now = alarm_date - now; - dbg_log("RTC alarm scheduled for " + alarm_date + - " hh:mm:ss=" + hours + ":" + minutes + ":" + seconds + - " ms_from_now=" + ms_from_now, LOG_RTC); - - this.next_interrupt_alarm = +alarm_date; - } - - if(this.cmos_b & 0x10) - { - dbg_log("update interrupt", LOG_RTC); - this.update_interrupt_time = Date.now(); - } - - dbg_log("cmos b=" + h(this.cmos_b, 2), LOG_RTC); - break; - - case CMOS_DIAG_STATUS: - this.cmos_diag_status = data_byte; - break; - - case CMOS_RTC_SECONDS_ALARM: - case CMOS_RTC_MINUTES_ALARM: - case CMOS_RTC_HOURS_ALARM: - this.cmos_write(this.cmos_index, data_byte); - break; - - default: - dbg_log("cmos write index " + h(this.cmos_index) + ": " + h(data_byte), LOG_RTC); - } - - this.update_interrupt = (this.cmos_b & 0x10) === 0x10 && (this.cmos_a & 0xF) > 0; - this.periodic_interrupt = (this.cmos_b & 0x40) === 0x40 && (this.cmos_a & 0xF) > 0; +RTC.prototype.timer = function (time, legacy_mode) { + this.cpu.rtc_timer(this.rtc); }; /** @@ -408,8 +136,7 @@ RTC.prototype.cmos_port_write = function(data_byte) */ RTC.prototype.cmos_read = function(index) { - dbg_assert(index < 128); - return this.cmos_data[index]; + return this.cpu.cmos_read(this.rtc, index); }; /** @@ -418,7 +145,5 @@ RTC.prototype.cmos_read = function(index) */ RTC.prototype.cmos_write = function(index, value) { - dbg_log("cmos " + h(index) + " <- " + h(value), LOG_RTC); - dbg_assert(index < 128); - this.cmos_data[index] = value; + this.cpu.cmos_write(this.rtc, index, value); }; diff --git a/src/rust/cpu/cpu.rs b/src/rust/cpu/cpu.rs index e28f3c56bd..c2deae2a92 100644 --- a/src/rust/cpu/cpu.rs +++ b/src/rust/cpu/cpu.rs @@ -4335,7 +4335,7 @@ unsafe fn pic_call_irq(interrupt_nr: u8) { } #[no_mangle] -unsafe fn device_raise_irq(i: u8) { +pub unsafe fn device_raise_irq(i: u8) { pic::set_irq(i); if *acpi_enabled { ioapic::set_irq(i); @@ -4344,7 +4344,7 @@ unsafe fn device_raise_irq(i: u8) { } #[no_mangle] -unsafe fn device_lower_irq(i: u8) { +pub unsafe fn device_lower_irq(i: u8) { pic::clear_irq(i); if *acpi_enabled { ioapic::clear_irq(i); diff --git a/src/rust/lib.rs b/src/rust/lib.rs index 0027145cad..90c1bb4fc3 100644 --- a/src/rust/lib.rs +++ b/src/rust/lib.rs @@ -5,6 +5,7 @@ mod dbg; mod paging; pub mod cpu; +pub mod rtc; pub mod js_api; pub mod profiler; diff --git a/src/rust/rtc.rs b/src/rust/rtc.rs new file mode 100644 index 0000000000..deee2b46e0 --- /dev/null +++ b/src/rust/rtc.rs @@ -0,0 +1,461 @@ +use crate::cpu::cpu::{device_lower_irq, device_raise_irq}; + +pub const CMOS_RTC_SECONDS: usize = 0x00; +pub const CMOS_RTC_SECONDS_ALARM: usize = 0x01; +pub const CMOS_RTC_MINUTES: usize = 0x02; +pub const CMOS_RTC_MINUTES_ALARM: usize = 0x03; +pub const CMOS_RTC_HOURS: usize = 0x04; +pub const CMOS_RTC_HOURS_ALARM: usize = 0x05; +pub const CMOS_RTC_DAY_WEEK: usize = 0x06; +pub const CMOS_RTC_DAY_MONTH: usize = 0x07; +pub const CMOS_RTC_MONTH: usize = 0x08; +pub const CMOS_RTC_YEAR: usize = 0x09; +pub const CMOS_STATUS_A: usize = 0x0a; +pub const CMOS_STATUS_B: usize = 0x0b; +pub const CMOS_STATUS_C: usize = 0x0c; +pub const CMOS_STATUS_D: usize = 0x0d; +pub const CMOS_DIAG_STATUS: usize = 0x0e; +pub const CMOS_RESET_CODE: usize = 0x0f; + +pub const CMOS_FLOPPY_DRIVE_TYPE: usize = 0x10; +pub const CMOS_DISK_DATA: usize = 0x12; +pub const CMOS_EQUIPMENT_INFO: usize = 0x14; +pub const CMOS_MEM_BASE_LOW: usize = 0x15; +pub const CMOS_MEM_BASE_HIGH: usize = 0x16; +pub const CMOS_MEM_OLD_EXT_LOW: usize = 0x17; +pub const CMOS_MEM_OLD_EXT_HIGH: usize = 0x18; +pub const CMOS_DISK_DRIVE1_TYPE: usize = 0x19; +pub const CMOS_DISK_DRIVE2_TYPE: usize = 0x1a; +pub const CMOS_DISK_DRIVE1_CYL: usize = 0x1b; +pub const CMOS_DISK_DRIVE2_CYL: usize = 0x24; +pub const CMOS_MEM_EXTMEM_LOW: usize = 0x30; +pub const CMOS_MEM_EXTMEM_HIGH: usize = 0x31; +pub const CMOS_CENTURY: usize = 0x32; +pub const CMOS_MEM_EXTMEM2_LOW: usize = 0x34; +pub const CMOS_MEM_EXTMEM2_HIGH: usize = 0x35; +pub const CMOS_CENTURY2: usize = 0x37; +pub const CMOS_BIOS_BOOTFLAG1: usize = 0x38; +pub const CMOS_BIOS_DISKTRANSFLAG: usize = 0x39; +pub const CMOS_BIOS_BOOTFLAG2: usize = 0x3d; +pub const CMOS_MEM_HIGHMEM_LOW: usize = 0x5b; +pub const CMOS_MEM_HIGHMEM_MID: usize = 0x5c; +pub const CMOS_MEM_HIGHMEM_HIGH: usize = 0x5d; +pub const CMOS_BIOS_SMP_COUNT: usize = 0x5f; + +// see CPU.prototype.fill_cmos +pub const BOOT_ORDER_CD_FIRST: usize = 0x123; +pub const BOOT_ORDER_HD_FIRST: usize = 0x312; +pub const BOOT_ORDER_FD_FIRST: usize = 0x321; + +extern "C" { + fn get_epoch_milis() -> f64; + fn microtick() -> f64; + fn getDate(_: f64) -> u8; + fn getDay(_: f64) -> u8; + fn getFullYear(_: f64) -> u32; + fn getMonth(_: f64) -> u8; + fn getHours(_: f64) -> u8; + fn getMinutes(_: f64) -> u8; + fn getSeconds(_: f64) -> u8; + fn newDate(_: u32, _: u8, _: u8, _: u8, _: u8, _: u8) -> f64; +} + +macro_rules! define_struct { + ($($field_name:ident $(, $get_fun:ident, $set_fun:ident: $field_js_type:ty)?: $field_type:ty);* $(;)?) => { + /// RTC (real time clock) and CMOS + #[derive(Debug, Clone, Copy)] + pub struct RTC { + $($field_name: $field_type),* + } + + $($( + // getter function + #[no_mangle] + pub unsafe extern "C" fn $set_fun(rtc: &mut RTC, value: $field_js_type) { + let value = value as $field_type; + dbg_log!("RTC {}: {:?}", stringify!($set_fun), value); + rtc.$field_name = value; + } + // setter function + #[no_mangle] + pub unsafe extern "C" fn $get_fun(rtc: &RTC) -> $field_js_type { + let value = rtc.$field_name as $field_js_type; + dbg_log!("RTC {}: {:?}", stringify!($get_fun), value); + value + } + )*)* + }; +} + +// NOTE the f64 <-> f64 conversion is used to maintain compatibility with states saved from before the rust migration +define_struct!( + cmos_index, get_cmos_index, set_cmos_index: u8: usize; + + // the javascript <-> rust translation is acting strangely with [u8; 128], + // even tho &[u8; 128] is FFI complient, maybe the translation is done incorrectly. + // TODO investigate if this is a bug. + // meanwhile use the manually implemented versions bellow + cmos_data: [u8; 128]; + + // used for cmos entries + rtc_time, get_rtc_time, set_rtc_time: f64: f64; + last_update, get_last_update, set_last_update: f64: f64; + + // used for periodic interrupt + next_interrupt, get_next_interrupt, set_next_interrupt: f64: f64; + + // next alarm interrupt + next_interrupt_alarm, get_next_interrupt_alarm, set_next_interrupt_alarm: f64: f64; + + periodic_interrupt, get_periodic_interrupt, set_periodic_interrupt: bool: bool; + + // corresponds to default value for cmos_a + periodic_interrupt_time, get_periodic_interrupt_time, set_periodic_interrupt_time: f64: f64; + + cmos_a, get_cmos_a, set_cmos_a: u8: u8; + cmos_b, get_cmos_b, set_cmos_b: u8: u8; + cmos_c, get_cmos_c, set_cmos_c: u8: u8; + + cmos_diag_status, get_cmos_diag_status, set_cmos_diag_status: u8: u8; + + nmi_disabled, get_nmi_disabled, set_nmi_disabled: bool: bool; + + update_interrupt, get_update_interrupt, set_update_interrupt: bool: bool; + update_interrupt_time, get_update_interrupt_time, set_update_interrupt_time: f64: f64; +); + +#[no_mangle] +pub unsafe extern "C" fn set_cmos_data_byte(rtc: &mut RTC, index: usize, value: u8) { + rtc.cmos_data[index] = value; +} +#[no_mangle] +pub unsafe extern "C" fn get_cmos_data_byte(rtc: &RTC, index: usize) -> u8 { rtc.cmos_data[index] } + +impl RTC { + pub fn new() -> Self { + let rtc_time = unsafe { get_epoch_milis() } as f64; + Self { + cmos_index: 0, + cmos_data: [0; 128], + rtc_time, + last_update: rtc_time, + next_interrupt: 0.0, + periodic_interrupt: false, + next_interrupt_alarm: 0.0, + periodic_interrupt_time: 1000.0 / 1024.0, + cmos_a: 0x26, + cmos_b: 2, + cmos_c: 0, + cmos_diag_status: 0, + nmi_disabled: false, + update_interrupt: false, + update_interrupt_time: 0.0, + } + } + + pub fn timer(&mut self) -> f64 { + let time_f = unsafe { get_epoch_milis() }; + let time = time_f as f64; + // note time is not garantied to be monolitic. + self.rtc_time += time - self.last_update; + self.last_update = time; + + if self.periodic_interrupt && self.next_interrupt < time { + unsafe { device_raise_irq(8) }; + self.cmos_c |= 1 << 6 | 1 << 7; + + self.next_interrupt += (self.periodic_interrupt_time + * f64::ceil((time_f - self.next_interrupt as f64) / self.periodic_interrupt_time)) + as f64; + } + else if self.next_interrupt_alarm != 0.0 && self.next_interrupt_alarm < time { + unsafe { device_raise_irq(8) }; + self.cmos_c |= 1 << 5 | 1 << 7; + + self.next_interrupt_alarm = 0.0; + } + else if self.update_interrupt && self.update_interrupt_time < time { + unsafe { device_raise_irq(8) }; + self.cmos_c |= 1 << 4 | 1 << 7; + + self.update_interrupt_time = time + 1000.0; // 1 second + } + + let mut t = 100f64; + + if self.periodic_interrupt && self.next_interrupt != 0.0 { + t = t.min(0f64.max(self.next_interrupt - time)); + } + if self.next_interrupt_alarm != 0.0 { + t = t.min(0f64.max(self.next_interrupt_alarm - time)); + } + if self.update_interrupt { + t = t.min(0f64.max(self.update_interrupt_time - time)); + } + + t + } + fn encode_time(&self, t: u8) -> u8 { + if self.cmos_b & 4 != 0 { + // binary mode + t + } + else { + bcd_pack(t) + } + } + + fn decode_time(&self, t: u8) -> u8 { + if self.cmos_b & 4 != 0 { + // binary mode + t + } + else { + bcd_unpack(t) + } + } + + // TODO + // - interrupt on update + // - countdown + // - letting bios/os set values + // (none of these are used by seabios or the OSes we're + // currently testing) + pub fn port_read(&mut self) -> u8 { self.cmos_port_read(self.cmos_index) } + + pub fn port_write(&mut self, value: u8) { self.cmos_port_write(self.cmos_index, value) } + + pub fn cmos_port_read(&mut self, cmos_index: usize) -> u8 { + match cmos_index { + CMOS_RTC_SECONDS => { + let second = unsafe { getSeconds(self.rtc_time as f64) }; + dbg_log!("RTC read second: {}", second); + self.encode_time(second) + }, + CMOS_RTC_MINUTES => { + let minute = unsafe { getMinutes(self.rtc_time as f64) }; + dbg_log!("RTC read minute: {}", minute); + self.encode_time(minute) + }, + CMOS_RTC_HOURS => { + let hour = unsafe { getHours(self.rtc_time as f64) }; + dbg_log!("RTC read hour: {}", hour); + // TODO: 12 hour mode + self.encode_time(hour) + }, + CMOS_RTC_DAY_WEEK => { + // getDay returns Sunday..=Saturday 0..=6 + // CMOS store Sunday..=Saturday 1..=7 + let day_week = unsafe { getDay(self.rtc_time as f64) } + 1; + dbg_log!("RTC read day of week: {}", day_week); + self.encode_time(day_week) + }, + CMOS_RTC_DAY_MONTH => { + let day_month = unsafe { getDate(self.rtc_time as f64) }; + dbg_log!("RTC read day of month: {}", day_month); + self.encode_time(day_month) + }, + CMOS_RTC_MONTH => { + // getDate returns from 0..=11 + // CMOS stores 1..=12 + let month = unsafe { getMonth(self.rtc_time as f64) } + 1; + dbg_log!("RTC read month: {}", month); + self.encode_time(month) + }, + CMOS_RTC_YEAR => { + let year: u8 = (unsafe { getFullYear(self.rtc_time as f64) } % 100) + .try_into() + .unwrap(); + dbg_log!("RTC read year: {}", year); + self.encode_time(year) + }, + + CMOS_CENTURY | CMOS_CENTURY2 => { + let century: u8 = (unsafe { getFullYear(self.rtc_time as f64) } / 100) + .try_into() + .unwrap(); + dbg_log!("RTC read century: {}", century); + self.encode_time(century) + }, + + CMOS_STATUS_A => { + if unsafe { microtick() } % 1000.0 >= 999.0 { + // Set update-in-progress for one millisecond every second (we + // may not have precision higher than that in browser + // environments) + return self.cmos_a | 0x80; + } + self.cmos_a + }, + CMOS_STATUS_B => self.cmos_b, + CMOS_STATUS_C => { + // It is important to know that upon a IRQ 8, Status Register C + // will contain a bitmask telling which interrupt happened. + // What is important is that if register C is not read after an + // IRQ 8, then the interrupt will not happen again. + unsafe { device_lower_irq(8) }; + + dbg_log!("RTC cmos reg C read"); + // Missing IRQF flag + //return cmos_b & 0x70; + let c = self.cmos_c; + self.cmos_c &= !0xF0; + c + }, + // CMOS battery charged + CMOS_STATUS_D => 1 << 7, + CMOS_DIAG_STATUS => { + dbg_log!("RTC cmos diagnostic status read"); + self.cmos_diag_status + }, + + index => { + dbg_log!("RTC cmos read from index {:#X}", index); + cmos_read(self, index.into()) + }, + } + } + + pub fn cmos_port_write(&mut self, cmos_index: usize, data_byte: u8) { + match cmos_index { + CMOS_STATUS_A => { + self.cmos_a = data_byte & 0x7F; + let freq_div = data_byte & 0xF; + dbg_log!("RTC cmos write reg_a: {:#X}", data_byte); + if freq_div != 0 { + let freq = 32768u16 >> (freq_div - 1); + self.periodic_interrupt_time = 1000.0 / freq as f64; + dbg_log!( + "RTC Periodic interrupt {}.{:03}Hz or {:.3}ms", + freq / 1000, + freq % 1000, + 1000.0 / f32::from(freq), + ); + } + }, + CMOS_STATUS_B => { + dbg_log!("RTC cmos write reg_b: {:#X}", data_byte); + self.cmos_b = data_byte; + if self.cmos_b & 0x80 != 0 { + // remove update interrupt flag + self.cmos_b &= 0xEF; + } + let now = unsafe { get_epoch_milis() } as f64; + if self.cmos_b & 0x40 != 0 { + self.next_interrupt = now; + } + + if self.cmos_b & 0x20 != 0 { + let second = self.decode_time(self.cmos_data[CMOS_RTC_SECONDS_ALARM]); + let minute = self.decode_time(self.cmos_data[CMOS_RTC_MINUTES_ALARM]); + let hour = self.decode_time(self.cmos_data[CMOS_RTC_HOURS_ALARM]); + dbg_log!( + "RTC alarm scheduled hh:mm:ss={:02}:{:02}:{:02}", + hour, + minute, + second, + ); + + let current_time = unsafe { get_epoch_milis() }; + let year = unsafe { getFullYear(current_time) }; + let month = unsafe { getMonth(current_time) }; + let day = unsafe { getDay(current_time) }; + + self.next_interrupt_alarm = + unsafe { newDate(year, month, day, hour, minute, second) }; + } + + if self.cmos_b & 0x10 != 0 { + dbg_log!("update interrupt"); + self.update_interrupt_time = now; + } + }, + CMOS_DIAG_STATUS => self.cmos_diag_status = data_byte, + + CMOS_RTC_SECONDS_ALARM | CMOS_RTC_MINUTES_ALARM | CMOS_RTC_HOURS_ALARM => { + self.cmos_write(cmos_index, data_byte); + }, + + index => { + dbg_log!("RTC cmos write {}: {:#X}", index, data_byte); + }, + } + + self.update_interrupt = (self.cmos_b & 0x10) != 0 && (self.cmos_a & 0xF) != 0; + self.periodic_interrupt = (self.cmos_b & 0x40) != 0 && (self.cmos_a & 0xF) != 0; + } + + pub fn cmos_read(&self, index: usize) -> u8 { + self.cmos_data.get(index).copied().unwrap_or_else(|| { + dbg_log!("RTC cmos read out-of-memory"); + 0 + }) + } + + pub fn cmos_write(&mut self, index: usize, value: u8) { + dbg_log!("RTC cmos write {:#X} <- {:#X}", index, value); + let Some(mem) = self.cmos_data.get_mut(index) + else { + dbg_log!("RTC cmos write out-of-memory"); + return; + }; + *mem = value + } +} + +const fn bcd_pack(mut n: u8) -> u8 { + let mut i = 0; + let mut result = 0; + let mut digit; + + while n != 0 { + digit = n % 10; + + result |= digit << (4 * i); + i += 1; + n = (n - digit) / 10; + } + + result +} + +const fn bcd_unpack(n: u8) -> u8 { + let low = n & 0xf; + let high = (n >> 4) & 0xf; + + dbg_assert!(low < 10); + dbg_assert!(high < 10); + + low + (10 * high) +} + +#[no_mangle] +pub unsafe extern "C" fn rtc_new() -> Box { + // NOTE javascript don't handle big structs very well, use + // Box here instead to avoid problems. + // If you remove the box, javascript will write garbage to it sometimes. + Box::new(RTC::new()) +} + +#[no_mangle] +pub extern "C" fn port_70_write(rtc: &mut RTC, value: usize) { + rtc.nmi_disabled = value & 0x80 != 0; + rtc.cmos_index = value & 0x7F; +} + +#[no_mangle] +pub extern "C" fn port_71_read(rtc: &mut RTC) -> u8 { rtc.port_read() } + +#[no_mangle] +pub extern "C" fn port_71_write(rtc: &mut RTC, value: u8) { rtc.port_write(value) } + +#[no_mangle] +pub extern "C" fn cmos_read(rtc: &RTC, index: usize) -> u8 { rtc.cmos_read(index) } + +#[no_mangle] +pub extern "C" fn cmos_write(rtc: &mut RTC, index: usize, value: u8) { + rtc.cmos_write(index, value) +} + +#[no_mangle] +pub extern "C" fn rtc_timer(rtc: &mut RTC) -> f64 { rtc.timer() }