Added comments and clarification on the PIC and PIT emulation code.

This commit is contained in:
Anselm Levskaya 2013-09-15 20:17:04 -07:00
parent 0f6b11c78e
commit 541ce3c478
4 changed files with 200 additions and 120 deletions

30
CMOS.js
View File

@ -4,22 +4,34 @@
Original is Copyright (c) 2011-2012 Fabrice Bellard Original is Copyright (c) 2011-2012 Fabrice Bellard
Redistribution or commercial use is prohibited without the author's permission. Redistribution or commercial use is prohibited without the author's permission.
Clock Emulator CMOS Ram Memory, actually just the RTC Clock Emulator
Useful references:
------------------
http://www.bioscentral.com/misc/cmosmap.htm
http://wiki.osdev.org/CMOS
*/ */
function formatter(a) { return ((a / 10) << 4) | (a % 10);}
/*
In this implementation, bytes are stored in the RTC in BCD format
binary -> bcd: bcd = ((bin / 10) << 4) | (bin % 10)
bcd -> binary: bin = ((bcd / 16) * 10) + (bcd & 0xf)
*/
function bin_to_bcd(a) { return ((a / 10) << 4) | (a % 10);}
function CMOS(PC) { function CMOS(PC) {
var time_array, d; var time_array, d;
time_array = new Uint8Array(128); time_array = new Uint8Array(128);
this.cmos_data = time_array; this.cmos_data = time_array;
this.cmos_index = 0; this.cmos_index = 0;
d = new Date(); d = new Date();
time_array[0] = formatter(d.getUTCSeconds()); time_array[0] = bin_to_bcd(d.getUTCSeconds());
time_array[2] = formatter(d.getUTCMinutes()); time_array[2] = bin_to_bcd(d.getUTCMinutes());
time_array[4] = formatter(d.getUTCHours()); time_array[4] = bin_to_bcd(d.getUTCHours());
time_array[6] = formatter(d.getUTCDay()); time_array[6] = bin_to_bcd(d.getUTCDay());
time_array[7] = formatter(d.getUTCDate()); time_array[7] = bin_to_bcd(d.getUTCDate());
time_array[8] = formatter(d.getUTCMonth() + 1); time_array[8] = bin_to_bcd(d.getUTCMonth() + 1);
time_array[9] = formatter(d.getUTCFullYear() % 100); time_array[9] = bin_to_bcd(d.getUTCFullYear() % 100);
time_array[10] = 0x26; time_array[10] = 0x26;
time_array[11] = 0x02; time_array[11] = 0x02;
time_array[12] = 0x00; time_array[12] = 0x00;

View File

@ -7,25 +7,25 @@ Redistribution or commercial use is prohibited without the author's permission.
Main PC Emulator Routine Main PC Emulator Routine
*/ */
// used as callback wrappers for emulated PIT and PIC chips
function set_hard_irq_wrapper(irq) { this.hard_irq = irq;} function set_hard_irq_wrapper(irq) { this.hard_irq = irq;}
function return_cycle_count() { return this.cycle_count; } function return_cycle_count() { return this.cycle_count; }
function PCEmulator(uh) { function PCEmulator(params) {
var cpu; var cpu;
cpu = new CPU_X86(); cpu = new CPU_X86();
this.cpu = cpu; this.cpu = cpu;
cpu.phys_mem_resize(uh.mem_size); cpu.phys_mem_resize(params.mem_size);
this.init_ioports(); this.init_ioports();
this.register_ioport_write(0x80, 1, 1, this.ioport80_write); this.register_ioport_write(0x80, 1, 1, this.ioport80_write);
this.pic = new PIC_Controller(this, 0x20, 0xa0, set_hard_irq_wrapper.bind(cpu)); this.pic = new PIC_Controller(this, 0x20, 0xa0, set_hard_irq_wrapper.bind(cpu));
this.pit = new PIT(this, this.pic.set_irq.bind(this.pic, 0), return_cycle_count.bind(cpu)); this.pit = new PIT(this, this.pic.set_irq.bind(this.pic, 0), return_cycle_count.bind(cpu));
this.cmos = new CMOS(this); this.cmos = new CMOS(this);
this.serial = new Serial(this, 0x3f8, this.pic.set_irq.bind(this.pic, 4), uh.serial_write); this.serial = new Serial(this, 0x3f8, this.pic.set_irq.bind(this.pic, 4), params.serial_write);
this.kbd = new KBD(this, this.reset.bind(this)); this.kbd = new KBD(this, this.reset.bind(this));
this.reset_request = 0; this.reset_request = 0;
if (uh.clipboard_get && uh.clipboard_set) { if (params.clipboard_get && params.clipboard_set) {
this.jsclipboard = new clipboard_device(this, 0x3c0, uh.clipboard_get, uh.clipboard_set, uh.get_boot_time); this.jsclipboard = new clipboard_device(this, 0x3c0, params.clipboard_get, params.clipboard_set, params.get_boot_time);
} }
cpu.ld8_port = this.ld8_port.bind(this); cpu.ld8_port = this.ld8_port.bind(this);
cpu.ld16_port = this.ld16_port.bind(this); cpu.ld16_port = this.ld16_port.bind(this);
@ -36,7 +36,7 @@ function PCEmulator(uh) {
cpu.get_hard_intno = this.pic.get_hard_intno.bind(this.pic); cpu.get_hard_intno = this.pic.get_hard_intno.bind(this.pic);
} }
PCEmulator.prototype.load_binary = function(Gg, ha) { return this.cpu.load_binary(Gg, ha); }; PCEmulator.prototype.load_binary = function(url, mem8_loc) { return this.cpu.load_binary(url, mem8_loc); };
PCEmulator.prototype.start = function() { setTimeout(this.timer_func.bind(this), 10); }; PCEmulator.prototype.start = function() { setTimeout(this.timer_func.bind(this), 10); };

212
PIC.js
View File

@ -4,8 +4,68 @@ JSLinux-deobfuscated - An annotated version of the original JSLinux.
Original is Copyright (c) 2011-2012 Fabrice Bellard Original is Copyright (c) 2011-2012 Fabrice Bellard
Redistribution or commercial use is prohibited without the author's permission. Redistribution or commercial use is prohibited without the author's permission.
8259 PIC (Programmable Interrupt Controller) Emulation Code 8259A PIC (Programmable Interrupt Controller) Emulation Code
The 8259 combines multiple interrupt input sources into a single
interrupt output to the host microprocessor, extending the interrupt
levels available in a system beyond the one or two levels found on the
processor chip.
There are three registers, an Interrupt Mask Register (IMR), an
Interrupt Request Register (IRR), and an In-Service Register
(ISR):
IRR - a mask of the current interrupts that are pending acknowledgement
ISR - a mask of the interrupts that are pending an EOI
IMR - a mask of interrupts that should not be sent an acknowledgement
End Of Interrupt (EOI) operations support specific EOI, non-specific
EOI, and auto-EOI. A specific EOI specifies the IRQ level it is
acknowledging in the ISR. A non-specific EOI resets the IRQ level in
the ISR. Auto-EOI resets the IRQ level in the ISR immediately after
the interrupt is acknowledged.
After the IBM XT, it was decided that 8 IRQs was not enough.
The backwards-compatible solution was simply to chain two 8259As together,
the master and slave PIC.
Useful References
-----------------
https://en.wikipedia.org/wiki/Programmable_Interrupt_Controller
https://en.wikipedia.org/wiki/Intel_8259
http://www.thesatya.com/8259.html
*/ */
/*
Common PC arrangements of IRQ lines:
------------------------------------
PC/AT and later systems had two 8259 controllers, master and
slave. IRQ0 through IRQ7 are the master 8259's interrupt lines, while
IRQ8 through IRQ15 are the slave 8259's interrupt lines. The labels on
the pins on an 8259 are IR0 through IR7. IRQ0 through IRQ15 are the
names of the ISA bus's lines to which the 8259s are attached.
Master 8259
IRQ0 Intel 8253 or Intel 8254 Programmable Interval Timer, aka the system timer
IRQ1 Intel 8042 keyboard controller
IRQ2 not assigned in PC/XT; cascaded to slave 8259 INT line in PC/AT
IRQ3 8250 UART serial ports 2 and 4
IRQ4 8250 UART serial ports 1 and 3
IRQ5 hard disk controller in PC/XT; Intel 8255 parallel ports 2 and 3 in PC/AT
IRQ6 Intel 82072A floppy disk controller
IRQ7 Intel 8255 parallel port 1 / spurious interrupt
Slave 8259 (PC/AT and later only)
IRQ8 real-time clock (RTC)
IRQ9 no common assignment, but 8-bit cards' IRQ2 line is routed to this interrupt.
IRQ10 no common assignment
IRQ11 no common assignment
IRQ12 Intel 8042 PS/2 mouse controller
IRQ13 math coprocessor
IRQ14 hard disk controller 1
IRQ15 hard disk controller 2
*/
function PIC(PC, port_num) { function PIC(PC, port_num) {
PC.register_ioport_write(port_num, 2, 1, this.ioport_write.bind(this)); PC.register_ioport_write(port_num, 2, 1, this.ioport_write.bind(this));
PC.register_ioport_read(port_num, 2, 1, this.ioport_read.bind(this)); PC.register_ioport_read(port_num, 2, 1, this.ioport_read.bind(this));
@ -13,9 +73,9 @@ function PIC(PC, port_num) {
} }
PIC.prototype.reset = function() { PIC.prototype.reset = function() {
this.last_irr = 0; this.last_irr = 0;
this.irr = 0; this.irr = 0; //Interrupt Request Register
this.imr = 0; this.imr = 0; //Interrupt Mask Register
this.isr = 0; this.isr = 0; //In-Service Register
this.priority_add = 0; this.priority_add = 0;
this.irq_base = 0; this.irq_base = 0;
this.read_reg_select = 0; this.read_reg_select = 0;
@ -24,54 +84,66 @@ PIC.prototype.reset = function() {
this.auto_eoi = 0; this.auto_eoi = 0;
this.rotate_on_autoeoi = 0; this.rotate_on_autoeoi = 0;
this.init4 = 0; this.init4 = 0;
this.elcr = 0; this.elcr = 0; // Edge/Level Control Register
this.elcr_mask = 0; this.elcr_mask = 0;
}; };
PIC.prototype.set_irq1 = function(Rg, Qf) { PIC.prototype.set_irq1 = function(irq, Qf) {
var wc; var ir_register;
wc = 1 << Rg; ir_register = 1 << irq;
if (Qf) { if (Qf) {
if ((this.last_irr & wc) == 0) if ((this.last_irr & ir_register) == 0)
this.irr |= wc; this.irr |= ir_register;
this.last_irr |= wc; this.last_irr |= ir_register;
} else { } else {
this.last_irr &= ~wc; this.last_irr &= ~ir_register;
} }
}; };
PIC.prototype.get_priority = function(wc) { /*
var Sg; The priority assignments for IRQ0-7 seem to be maintained in a
if (wc == 0) cyclic order modulo 8 by the 8259A. On bootup, it default to:
Priority: 0 1 2 3 4 5 6 7
IRQ: 7 6 5 4 3 2 1 0
but can be rotated automatically or programmatically to a state e.g.:
Priority: 5 6 7 0 1 2 3 4
IRQ: 7 6 5 4 3 2 1 0
*/
PIC.prototype.get_priority = function(ir_register) {
var priority;
if (ir_register == 0)
return -1; return -1;
Sg = 7; priority = 7;
while ((wc & (1 << ((Sg + this.priority_add) & 7))) == 0) while ((ir_register & (1 << ((priority + this.priority_add) & 7))) == 0)
Sg--; priority--;
return Sg; return priority;
}; };
PIC.prototype.get_irq = function() { PIC.prototype.get_irq = function() {
var wc, Tg, Sg; var ir_register, in_service_priority, priority;
wc = this.irr & ~this.imr; ir_register = this.irr & ~this.imr;
Sg = this.get_priority(wc); priority = this.get_priority(ir_register);
if (Sg < 0) if (priority < 0)
return -1; return -1;
Tg = this.get_priority(this.isr); in_service_priority = this.get_priority(this.isr);
if (Sg > Tg) { if (priority > in_service_priority) {
return Sg; return priority;
} else { } else {
return -1; return -1;
} }
}; };
PIC.prototype.intack = function(Rg) { PIC.prototype.intack = function(irq) {
if (this.auto_eoi) { if (this.auto_eoi) {
if (this.rotate_on_auto_eoi) if (this.rotate_on_auto_eoi)
this.priority_add = (Rg + 1) & 7; this.priority_add = (irq + 1) & 7;
} else { } else {
this.isr |= (1 << Rg); this.isr |= (1 << irq);
} }
if (!(this.elcr & (1 << Rg))) if (!(this.elcr & (1 << irq)))
this.irr &= ~(1 << Rg); this.irr &= ~(1 << irq);
}; };
PIC.prototype.ioport_write = function(mem8_loc, x) { PIC.prototype.ioport_write = function(mem8_loc, x) {
var Sg; var priority;
mem8_loc &= 1; mem8_loc &= 1;
if (mem8_loc == 0) { if (mem8_loc == 0) {
if (x & 0x10) { if (x & 0x10) {
@ -105,9 +177,9 @@ PIC.prototype.ioport_write = function(mem8_loc, x) {
break; break;
case 0x20: case 0x20:
case 0xa0: case 0xa0:
Sg = this.get_priority(this.isr); priority = this.get_priority(this.isr);
if (Sg >= 0) { if (priority >= 0) {
this.isr &= ~(1 << ((Sg + this.priority_add) & 7)); this.isr &= ~(1 << ((priority + this.priority_add) & 7));
} }
if (x == 0xa0) if (x == 0xa0)
this.priority_add = (this.priority_add + 1) & 7; this.priority_add = (this.priority_add + 1) & 7;
@ -120,8 +192,8 @@ PIC.prototype.ioport_write = function(mem8_loc, x) {
case 0x65: case 0x65:
case 0x66: case 0x66:
case 0x67: case 0x67:
Sg = x & 7; priority = x & 7;
this.isr &= ~(1 << Sg); this.isr &= ~(1 << priority);
break; break;
case 0xc0: case 0xc0:
case 0xc1: case 0xc1:
@ -141,9 +213,9 @@ PIC.prototype.ioport_write = function(mem8_loc, x) {
case 0xe5: case 0xe5:
case 0xe6: case 0xe6:
case 0xe7: case 0xe7:
Sg = x & 7; priority = x & 7;
this.isr &= ~(1 << Sg); this.isr &= ~(1 << priority);
this.priority_add = (Sg + 1) & 7; this.priority_add = (priority + 1) & 7;
break; break;
} }
} }
@ -172,69 +244,69 @@ PIC.prototype.ioport_write = function(mem8_loc, x) {
} }
}; };
PIC.prototype.ioport_read = function(Ug) { PIC.prototype.ioport_read = function(Ug) {
var mem8_loc, Pg; var mem8_loc, return_register;
mem8_loc = Ug & 1; mem8_loc = Ug & 1;
if (mem8_loc == 0) { if (mem8_loc == 0) {
if (this.read_reg_select) if (this.read_reg_select)
Pg = this.isr; return_register = this.isr;
else else
Pg = this.irr; return_register = this.irr;
} else { } else {
Pg = this.imr; return_register = this.imr;
} }
return Pg; return return_register;
}; };
function PIC_Controller(PC, Wg, Ug, Xg) { function PIC_Controller(PC, master_PIC_port, slave_PIC_port, cpu_set_irq_callback) {
this.pics = new Array(); this.pics = new Array();
this.pics[0] = new PIC(PC, Wg); this.pics[0] = new PIC(PC, master_PIC_port);
this.pics[1] = new PIC(PC, Ug); this.pics[1] = new PIC(PC, slave_PIC_port);
this.pics[0].elcr_mask = 0xf8; this.pics[0].elcr_mask = 0xf8;
this.pics[1].elcr_mask = 0xde; this.pics[1].elcr_mask = 0xde;
this.irq_requested = 0; this.irq_requested = 0;
this.cpu_set_irq = Xg; this.cpu_set_irq = cpu_set_irq_callback;
this.pics[0].update_irq = this.update_irq.bind(this); this.pics[0].update_irq = this.update_irq.bind(this);
this.pics[1].update_irq = this.update_irq.bind(this); this.pics[1].update_irq = this.update_irq.bind(this);
} }
PIC_Controller.prototype.update_irq = function() { PIC_Controller.prototype.update_irq = function() {
var Yg, Rg; var slave_irq, irq;
Yg = this.pics[1].get_irq(); slave_irq = this.pics[1].get_irq();
if (Yg >= 0) { if (slave_irq >= 0) {
this.pics[0].set_irq1(2, 1); this.pics[0].set_irq1(2, 1);
this.pics[0].set_irq1(2, 0); this.pics[0].set_irq1(2, 0);
} }
Rg = this.pics[0].get_irq(); irq = this.pics[0].get_irq();
if (Rg >= 0) { if (irq >= 0) {
this.cpu_set_irq(1); this.cpu_set_irq(1);
} else { } else {
this.cpu_set_irq(0); this.cpu_set_irq(0);
} }
}; };
PIC_Controller.prototype.set_irq = function(Rg, Qf) { PIC_Controller.prototype.set_irq = function(irq, Qf) {
this.pics[Rg >> 3].set_irq1(Rg & 7, Qf); this.pics[irq >> 3].set_irq1(irq & 7, Qf);
this.update_irq(); this.update_irq();
}; };
PIC_Controller.prototype.get_hard_intno = function() { PIC_Controller.prototype.get_hard_intno = function() {
var Rg, Yg, intno; var irq, slave_irq, intno;
Rg = this.pics[0].get_irq(); irq = this.pics[0].get_irq();
if (Rg >= 0) { if (irq >= 0) {
this.pics[0].intack(Rg); this.pics[0].intack(irq);
if (Rg == 2) { if (irq == 2) { //IRQ 2 cascaded to slave 8259 INT line in PC/AT
Yg = this.pics[1].get_irq(); slave_irq = this.pics[1].get_irq();
if (Yg >= 0) { if (slave_irq >= 0) {
this.pics[1].intack(Yg); this.pics[1].intack(slave_irq);
} else { } else {
Yg = 7; slave_irq = 7;
} }
intno = this.pics[1].irq_base + Yg; intno = this.pics[1].irq_base + slave_irq;
Rg = Yg + 8; irq = slave_irq + 8;
} else { } else {
intno = this.pics[0].irq_base + Rg; intno = this.pics[0].irq_base + irq;
} }
} else { } else {
Rg = 7; irq = 7;
intno = this.pics[0].irq_base + Rg; intno = this.pics[0].irq_base + irq;
} }
this.update_irq(); this.update_irq();
return intno; return intno;

46
PIT.js
View File

@ -5,19 +5,23 @@ Original is Copyright (c) 2011-2012 Fabrice Bellard
Redistribution or commercial use is prohibited without the author's permission. Redistribution or commercial use is prohibited without the author's permission.
8254 Programmble Interrupt Timer Emulator 8254 Programmble Interrupt Timer Emulator
Useful References
-----------------
https://en.wikipedia.org/wiki/Intel_8253
*/ */
function PIT(PC, ah, bh) { function PIT(PC, set_irq_callback, cycle_count_callback) {
var s, i; var s, i;
this.pit_channels = new Array(); this.pit_channels = new Array();
for (i = 0; i < 3; i++) { for (i = 0; i < 3; i++) {
s = new IRQCH(bh); s = new IRQCH(cycle_count_callback);
this.pit_channels[i] = s; this.pit_channels[i] = s;
s.mode = 3; s.mode = 3;
s.gate = (i != 2) >> 0; s.gate = (i != 2) >> 0;
s.pit_load_count(0); s.pit_load_count(0);
} }
this.speaker_data_on = 0; this.speaker_data_on = 0;
this.set_irq = ah; this.set_irq = set_irq_callback;
// Ports: // Ports:
// 0x40: Channel 0 data port // 0x40: Channel 0 data port
// 0x61: Control // 0x61: Control
@ -27,9 +31,7 @@ function PIT(PC, ah, bh) {
PC.register_ioport_write(0x61, 1, 1, this.speaker_ioport_write.bind(this)); PC.register_ioport_write(0x61, 1, 1, this.speaker_ioport_write.bind(this));
} }
function IRQCH(cycle_count_callback) {
function IRQCH(bh) {
this.count = 0; this.count = 0;
this.latched_count = 0; this.latched_count = 0;
this.rw_state = 0; this.rw_state = 0;
@ -37,7 +39,7 @@ function IRQCH(bh) {
this.bcd = 0; this.bcd = 0;
this.gate = 0; this.gate = 0;
this.count_load_time = 0; this.count_load_time = 0;
this.get_ticks = bh; this.get_ticks = cycle_count_callback;
this.pit_time_unit = 1193182 / 2000000; this.pit_time_unit = 1193182 / 2000000;
} }
IRQCH.prototype.get_time = function() { IRQCH.prototype.get_time = function() {
@ -64,29 +66,23 @@ IRQCH.prototype.pit_get_out = function() {
d = this.get_time() - this.count_load_time; d = this.get_time() - this.count_load_time;
switch (this.mode) { switch (this.mode) {
default: default:
// Interrupt on terminal count case 0: // Interrupt on terminal count
case 0:
eh = (d >= this.count) >> 0; eh = (d >= this.count) >> 0;
break; break;
// One shot case 1: // One shot
case 1:
eh = (d < this.count) >> 0; eh = (d < this.count) >> 0;
break; break;
// Frequency divider case 2: // Frequency divider
case 2:
if ((d % this.count) == 0 && d != 0) if ((d % this.count) == 0 && d != 0)
eh = 1; eh = 1;
else else
eh = 0; eh = 0;
break; break;
// Square wave case 3: // Square wave
case 3:
eh = ((d % this.count) < (this.count >> 1)) >> 0; eh = ((d % this.count) < (this.count >> 1)) >> 0;
break; break;
// SW strobe case 4: // SW strobe
case 4: case 5: // HW strobe
// HW strobe
case 5:
eh = (d == this.count) >> 0; eh = (d == this.count) >> 0;
break; break;
} }
@ -97,21 +93,21 @@ IRQCH.prototype.get_next_transition_time = function() {
d = this.get_time() - this.count_load_time; d = this.get_time() - this.count_load_time;
switch (this.mode) { switch (this.mode) {
default: default:
case 0: case 0: // Interrupt on terminal count
case 1: case 1: // One shot
if (d < this.count) if (d < this.count)
fh = this.count; fh = this.count;
else else
return -1; return -1;
break; break;
case 2: case 2: // Frequency divider
base = (d / this.count) * this.count; base = (d / this.count) * this.count;
if ((d - base) == 0 && d != 0) if ((d - base) == 0 && d != 0)
fh = base + this.count; fh = base + this.count;
else else
fh = base + this.count + 1; fh = base + this.count + 1;
break; break;
case 3: case 3: // Square wave
base = (d / this.count) * this.count; base = (d / this.count) * this.count;
gh = ((this.count + 1) >> 1); gh = ((this.count + 1) >> 1);
if ((d - base) < gh) if ((d - base) < gh)
@ -119,8 +115,8 @@ IRQCH.prototype.get_next_transition_time = function() {
else else
fh = base + this.count; fh = base + this.count;
break; break;
case 4: case 4: // SW strobe
case 5: case 5: // HW strobe
if (d < this.count) if (d < this.count)
fh = this.count; fh = this.count;
else if (d == this.count) else if (d == this.count)