import { v86 } from './main.js' import { LOG_RTC } from './const.js' import { h } from './lib.js' import { dbg_assert, dbg_log } from './log.js' import { IO } from './io.js' // Minimal interface for CPU fields RTC uses interface RTCCpu { io: IO device_raise_irq(irq: number): void device_lower_irq(irq: number): void } export const CMOS_RTC_SECONDS = 0x00 export const CMOS_RTC_SECONDS_ALARM = 0x01 export const CMOS_RTC_MINUTES = 0x02 export const CMOS_RTC_MINUTES_ALARM = 0x03 export const CMOS_RTC_HOURS = 0x04 export const CMOS_RTC_HOURS_ALARM = 0x05 export const CMOS_RTC_DAY_WEEK = 0x06 export const CMOS_RTC_DAY_MONTH = 0x07 export const CMOS_RTC_MONTH = 0x08 export const CMOS_RTC_YEAR = 0x09 export const CMOS_STATUS_A = 0x0a export const CMOS_STATUS_B = 0x0b export const CMOS_STATUS_C = 0x0c export const CMOS_STATUS_D = 0x0d export const CMOS_DIAG_STATUS = 0x0e export const CMOS_RESET_CODE = 0x0f export const CMOS_FLOPPY_DRIVE_TYPE = 0x10 export const CMOS_DISK_DATA = 0x12 export const CMOS_EQUIPMENT_INFO = 0x14 export const CMOS_MEM_BASE_LOW = 0x15 export const CMOS_MEM_BASE_HIGH = 0x16 export const CMOS_MEM_OLD_EXT_LOW = 0x17 export const CMOS_MEM_OLD_EXT_HIGH = 0x18 export const CMOS_DISK_DRIVE1_TYPE = 0x19 export const CMOS_DISK_DRIVE2_TYPE = 0x1a export const CMOS_DISK_DRIVE1_CYL = 0x1b export const CMOS_DISK_DRIVE2_CYL = 0x24 export const CMOS_MEM_EXTMEM_LOW = 0x30 export const CMOS_MEM_EXTMEM_HIGH = 0x31 export const CMOS_CENTURY = 0x32 export const CMOS_MEM_EXTMEM2_LOW = 0x34 export const CMOS_MEM_EXTMEM2_HIGH = 0x35 export const CMOS_CENTURY2 = 0x37 export const CMOS_BIOS_BOOTFLAG1 = 0x38 export const CMOS_BIOS_DISKTRANSFLAG = 0x39 export const CMOS_BIOS_BOOTFLAG2 = 0x3d export const CMOS_MEM_HIGHMEM_LOW = 0x5b export const CMOS_MEM_HIGHMEM_MID = 0x5c export const CMOS_MEM_HIGHMEM_HIGH = 0x5d export const CMOS_BIOS_SMP_COUNT = 0x5f // see CPU.prototype.fill_cmos export const BOOT_ORDER_CD_FIRST = 0x123 export const BOOT_ORDER_HD_FIRST = 0x312 export const BOOT_ORDER_FD_FIRST = 0x321 type RTCState = [ number, Uint8Array, number, number, number, number, boolean, number, number, number, number, number, boolean, number, number, ] /** * RTC (real time clock) and CMOS */ export class RTC { name: string cpu: RTCCpu cmos_index: number cmos_data: Uint8Array rtc_time: number last_update: number next_interrupt: number next_interrupt_alarm: number periodic_interrupt: boolean periodic_interrupt_time: number cmos_a: number cmos_b: number cmos_c: number cmos_diag_status: number nmi_disabled: number update_interrupt: boolean update_interrupt_time: number constructor(cpu: RTCCpu) { this.name = 'rtc' this.cpu = cpu 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, (out_byte: number): void => { this.cmos_index = out_byte & 0x7f this.nmi_disabled = out_byte >> 7 }) cpu.io.register_write(0x71, this, this.cmos_port_write.bind(this)) cpu.io.register_read(0x71, this, this.cmos_port_read.bind(this)) } get_state(): RTCState { const state: RTCState = [ this.cmos_index, this.cmos_data, this.rtc_time, this.last_update, this.next_interrupt, this.next_interrupt_alarm, this.periodic_interrupt, this.periodic_interrupt_time, this.cmos_a, this.cmos_b, this.cmos_c, this.nmi_disabled, this.update_interrupt, this.update_interrupt_time, this.cmos_diag_status, ] return state } set_state(state: RTCState): void { 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 } timer(time: number, _legacy_mode: boolean): number { 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 } bcd_pack(n: number): number { let i = 0, result = 0, digit: number while (n) { digit = n % 10 result |= digit << (4 * i) i++ n = (n - digit) / 10 } return result } bcd_unpack(n: number): number { 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 } encode_time(t: number): number { if (this.cmos_b & 4) { // binary mode return t } else { return this.bcd_pack(t) } } decode_time(t: number): number { if (this.cmos_b & 4) { // binary mode return t } else { return this.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) cmos_port_read(): number { const 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; const 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] } } cmos_port_write(data_byte: number): void { 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 } cmos_read(index: number): number { dbg_assert(index < 128) return this.cmos_data[index] } cmos_write(index: number, value: number): void { dbg_log('cmos ' + h(index) + ' <- ' + h(value), LOG_RTC) dbg_assert(index < 128) this.cmos_data[index] = value } }