v86/src/ps2.js
2022-06-14 03:03:55 +02:00

814 lines
21 KiB
JavaScript

"use strict";
/** @const */
let PS2_LOG_VERBOSE = false;
/**
* @constructor
* @param {CPU} cpu
* @param {BusConnector} bus
*/
function PS2(cpu, bus)
{
/** @const @type {CPU} */
this.cpu = cpu;
/** @const @type {BusConnector} */
this.bus = bus;
/** @type {boolean} */
this.enable_mouse_stream = false;
/** @type {boolean} */
this.use_mouse = false;
/** @type {boolean} */
this.have_mouse = true;
/** @type {number} */
this.mouse_delta_x = 0;
/** @type {number} */
this.mouse_delta_y = 0;
/** @type {number} */
this.mouse_clicks = 0;
/** @type {boolean} */
this.have_keyboard = true;
/** @type {boolean} */
this.enable_keyboard_stream = false;
/** @type {boolean} */
this.next_is_mouse_command = false;
/** @type {boolean} */
this.next_read_sample = false;
/** @type {boolean} */
this.next_read_led = false;
/** @type {boolean} */
this.next_handle_scan_code_set = false;
/** @type {boolean} */
this.next_read_rate = false;
/** @type {boolean} */
this.next_read_resolution = false;
/**
* @type {ByteQueue}
*/
this.kbd_buffer = new ByteQueue(1024);
this.last_port60_byte = 0;
/** @type {number} */
this.sample_rate = 100;
/** @type {number} */
this.mouse_detect_state = 0;
/** @type {number} */
this.mouse_id = 0x00;
/** @type {boolean} */
this.mouse_reset_workaround = false;
/** @type {number} */
this.wheel_movement = 0;
/** @type {number} */
this.resolution = 4;
/** @type {boolean} */
this.scaling2 = false;
/** @type {number} */
this.last_mouse_packet = -1;
/**
* @type {ByteQueue}
*/
this.mouse_buffer = new ByteQueue(1024);
/**
* @type {boolean}
* Also known as DBBOUT OBF - Output Buffer Full flag
*/
this.next_byte_is_ready = false;
/** @type {boolean} */
this.next_byte_is_aux = false;
this.bus.register("keyboard-code", function(code)
{
this.kbd_send_code(code);
}, this);
this.bus.register("mouse-click", function(data)
{
this.mouse_send_click(data[0], data[1], data[2]);
}, this);
this.bus.register("mouse-delta", function(data)
{
this.mouse_send_delta(data[0], data[1]);
}, this);
this.bus.register("mouse-wheel", function(data)
{
this.wheel_movement -= data[0];
this.wheel_movement -= data[1] * 2; // X Wheel Movement
this.wheel_movement = Math.min(7, Math.max(-8, this.wheel_movement));
this.send_mouse_packet(0, 0);
}, this);
this.command_register = 1 | 4;
// TODO: What should be the initial value?
this.controller_output_port = 0;
this.read_output_register = false;
this.read_command_register = false;
this.read_controller_output_port = false;
cpu.io.register_read(0x60, this, this.port60_read);
cpu.io.register_read(0x64, this, this.port64_read);
cpu.io.register_write(0x60, this, this.port60_write);
cpu.io.register_write(0x64, this, this.port64_write);
}
PS2.prototype.get_state = function()
{
var state = [];
state[0] = this.enable_mouse_stream;
state[1] = this.use_mouse;
state[2] = this.have_mouse;
state[3] = this.mouse_delta_x;
state[4] = this.mouse_delta_y;
state[5] = this.mouse_clicks;
state[6] = this.have_keyboard;
state[7] = this.enable_keyboard_stream;
state[8] = this.next_is_mouse_command;
state[9] = this.next_read_sample;
state[10] = this.next_read_led;
state[11] = this.next_handle_scan_code_set;
state[12] = this.next_read_rate;
state[13] = this.next_read_resolution;
//state[14] = this.kbd_buffer;
state[15] = this.last_port60_byte;
state[16] = this.sample_rate;
state[17] = this.resolution;
state[18] = this.scaling2;
//state[19] = this.mouse_buffer;
state[20] = this.command_register;
state[21] = this.read_output_register;
state[22] = this.read_command_register;
state[23] = this.controller_output_port;
state[24] = this.read_controller_output_port;
state[25] = this.mouse_id;
state[26] = this.mouse_detect_state;
state[27] = this.mouse_reset_workaround;
return state;
};
PS2.prototype.set_state = function(state)
{
this.enable_mouse_stream = state[0];
this.use_mouse = state[1];
this.have_mouse = state[2];
this.mouse_delta_x = state[3];
this.mouse_delta_y = state[4];
this.mouse_clicks = state[5];
this.have_keyboard = state[6];
this.enable_keyboard_stream = state[7];
this.next_is_mouse_command = state[8];
this.next_read_sample = state[9];
this.next_read_led = state[10];
this.next_handle_scan_code_set = state[11];
this.next_read_rate = state[12];
this.next_read_resolution = state[13];
//this.kbd_buffer = state[14];
this.last_port60_byte = state[15];
this.sample_rate = state[16];
this.resolution = state[17];
this.scaling2 = state[18];
//this.mouse_buffer = state[19];
this.command_register = state[20];
this.read_output_register = state[21];
this.read_command_register = state[22];
this.controller_output_port = state[23];
this.read_controller_output_port = state[24];
this.mouse_id = state[25] || 0;
this.mouse_detect_state = state[26] || 0;
this.mouse_reset_workaround = state[27] || false;
this.next_byte_is_ready = false;
this.next_byte_is_aux = false;
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.bus.send("mouse-enable", this.use_mouse);
};
PS2.prototype.raise_irq = function()
{
if(this.next_byte_is_ready)
{
// Wait until previous byte is read
// http://halicery.com/Hardware/8042/8042_1503033_TXT.htm
return;
}
// Kbd has priority over aux
if(this.kbd_buffer.length)
{
this.kbd_irq();
}
else if(this.mouse_buffer.length)
{
this.mouse_irq();
}
};
PS2.prototype.mouse_irq = function()
{
this.next_byte_is_ready = true;
this.next_byte_is_aux = true;
if(this.command_register & 2)
{
dbg_log("Mouse irq", LOG_PS2);
// Pulse the irq line
// Note: can't lower immediately after rising, so lower before rising
// http://www.os2museum.com/wp/ibm-ps2-model-50-keyboard-controller/
this.cpu.device_lower_irq(12);
this.cpu.device_raise_irq(12);
}
};
PS2.prototype.kbd_irq = function()
{
this.next_byte_is_ready = true;
this.next_byte_is_aux = false;
if(this.command_register & 1)
{
dbg_log("Keyboard irq", LOG_PS2);
// Pulse the irq line
// Note: can't lower immediately after rising, so lower before rising
// http://www.os2museum.com/wp/ibm-ps2-model-50-keyboard-controller/
this.cpu.device_lower_irq(1);
this.cpu.device_raise_irq(1);
}
};
PS2.prototype.kbd_send_code = function(code)
{
if(this.enable_keyboard_stream)
{
dbg_log("adding kbd code: " + h(code), LOG_PS2);
this.kbd_buffer.push(code);
this.raise_irq();
}
};
PS2.prototype.mouse_send_delta = function(delta_x, delta_y)
{
if(!this.have_mouse || !this.use_mouse)
{
return;
}
// note: delta_x or delta_y can be floating point numbers
var factor = this.resolution * this.sample_rate / 80;
this.mouse_delta_x += delta_x * factor;
this.mouse_delta_y += delta_y * factor;
if(this.enable_mouse_stream)
{
var change_x = this.mouse_delta_x | 0,
change_y = this.mouse_delta_y | 0;
if(change_x || change_y)
{
var now = Date.now();
//if(now - this.last_mouse_packet < 1000 / this.sample_rate)
//{
// // TODO: set timeout
// return;
//}
this.mouse_delta_x -= change_x;
this.mouse_delta_y -= change_y;
this.send_mouse_packet(change_x, change_y);
}
}
};
PS2.prototype.mouse_send_click = function(left, middle, right)
{
if(!this.have_mouse || !this.use_mouse)
{
return;
}
this.mouse_clicks = left | right << 1 | middle << 2;
if(this.enable_mouse_stream)
{
this.send_mouse_packet(0, 0);
}
};
PS2.prototype.send_mouse_packet = function(dx, dy)
{
var info_byte =
(dy < 0) << 5 |
(dx < 0) << 4 |
1 << 3 |
this.mouse_clicks,
delta_x = dx,
delta_y = dy;
this.last_mouse_packet = Date.now();
//if(this.scaling2)
//{
// // only in automatic packets, not 0xEB requests
// delta_x = this.apply_scaling2(delta_x);
// delta_y = this.apply_scaling2(delta_y);
//}
this.mouse_buffer.push(info_byte);
this.mouse_buffer.push(delta_x);
this.mouse_buffer.push(delta_y);
if(this.mouse_id === 0x04)
{
this.mouse_buffer.push(
0 << 5 | // TODO: 5th button
0 << 4 | // TODO: 4th button
this.wheel_movement & 0x0F
);
this.wheel_movement = 0;
}
else if(this.mouse_id === 0x03)
{
this.mouse_buffer.push(this.wheel_movement & 0xFF); // Byte 4 - Z Movement
this.wheel_movement = 0;
}
if(PS2_LOG_VERBOSE)
{
dbg_log("adding mouse packets: " + [info_byte, dx, dy], LOG_PS2);
}
this.raise_irq();
};
PS2.prototype.apply_scaling2 = function(n)
{
// http://www.computer-engineering.org/ps2mouse/#Inputs.2C_Resolution.2C_and_Scaling
var abs = Math.abs(n),
sign = n >> 31;
switch(abs)
{
case 0:
case 1:
case 3:
return n;
case 2:
return sign;
case 4:
return 6 * sign;
case 5:
return 9 * sign;
default:
return n << 1;
}
};
PS2.prototype.port60_read = function()
{
//dbg_log("port 60 read: " + (buffer[0] || "(none)"));
this.next_byte_is_ready = false;
if(!this.kbd_buffer.length && !this.mouse_buffer.length)
{
// should not happen
dbg_log("Port 60 read: Empty", LOG_PS2);
return this.last_port60_byte;
}
if(this.next_byte_is_aux)
{
this.cpu.device_lower_irq(12);
this.last_port60_byte = this.mouse_buffer.shift();
dbg_log("Port 60 read (mouse): " + h(this.last_port60_byte), LOG_PS2);
}
else
{
this.cpu.device_lower_irq(1);
this.last_port60_byte = this.kbd_buffer.shift();
dbg_log("Port 60 read (kbd) : " + h(this.last_port60_byte), LOG_PS2);
}
if(this.kbd_buffer.length || this.mouse_buffer.length)
{
this.raise_irq();
}
return this.last_port60_byte;
};
PS2.prototype.port64_read = function()
{
// status port
var status_byte = 0x10;
if(this.next_byte_is_ready)
{
status_byte |= 0x1;
}
if(this.next_byte_is_aux)
{
status_byte |= 0x20;
}
dbg_log("port 64 read: " + h(status_byte), LOG_PS2);
return status_byte;
};
PS2.prototype.port60_write = function(write_byte)
{
dbg_log("port 60 write: " + h(write_byte), LOG_PS2);
if(this.read_command_register)
{
this.command_register = write_byte;
this.read_command_register = false;
// not sure, causes "spurious ack" in Linux
//this.kbd_buffer.push(0xFA);
//this.kbd_irq();
dbg_log("Keyboard command register = " + h(this.command_register), LOG_PS2);
}
else if(this.read_output_register)
{
this.read_output_register = false;
this.mouse_buffer.clear();
this.mouse_buffer.push(write_byte);
this.mouse_irq();
}
else if(this.next_read_sample)
{
this.next_read_sample = false;
this.mouse_buffer.clear();
this.mouse_buffer.push(0xFA);
this.sample_rate = write_byte;
switch(this.mouse_detect_state)
{
case -1:
if(write_byte === 60)
{
// Detect Windows NT and turn on workaround the bug
// 200->100->80->60
this.mouse_reset_workaround = true;
this.mouse_detect_state = 0;
}
else
{
this.mouse_reset_workaround = false;
this.mouse_detect_state = (write_byte === 200) ? 1 : 0;
}
break;
case 0:
if(write_byte === 200) this.mouse_detect_state = 1;
break;
case 1:
if(write_byte === 100) this.mouse_detect_state = 2;
else if(write_byte === 200) this.mouse_detect_state = 3;
else this.mouse_detect_state = 0;
break;
case 2:
// Host sends sample rate 200->100->80 to activate Intellimouse wheel
if(write_byte === 80) this.mouse_id = 0x03;
this.mouse_detect_state = -1;
break;
case 3:
// Host sends sample rate 200->200->80 to activate Intellimouse 4th, 5th buttons
if(write_byte === 80) this.mouse_id = 0x04;
this.mouse_detect_state = -1;
break;
}
dbg_log("mouse sample rate: " + h(write_byte) + ", mouse id: " + h(this.mouse_id), LOG_PS2);
if(!this.sample_rate)
{
dbg_log("invalid sample rate, reset to 100", LOG_PS2);
this.sample_rate = 100;
}
this.mouse_irq();
}
else if(this.next_read_resolution)
{
this.next_read_resolution = false;
this.mouse_buffer.clear();
this.mouse_buffer.push(0xFA);
if(write_byte > 3)
{
this.resolution = 4;
dbg_log("invalid resolution, resetting to 4", LOG_PS2);
}
else
{
this.resolution = 1 << write_byte;
dbg_log("resolution: " + this.resolution, LOG_PS2);
}
this.mouse_irq();
}
else if(this.next_read_led)
{
// nope
this.next_read_led = false;
this.kbd_buffer.push(0xFA);
this.kbd_irq();
}
else if(this.next_handle_scan_code_set)
{
this.next_handle_scan_code_set = false;
this.kbd_buffer.push(0xFA);
this.kbd_irq();
if(write_byte)
{
// set scan code set
}
else
{
this.kbd_buffer.push(2);
}
}
else if(this.next_read_rate)
{
// nope
this.next_read_rate = false;
this.kbd_buffer.push(0xFA);
this.kbd_irq();
}
else if(this.next_is_mouse_command)
{
this.next_is_mouse_command = false;
dbg_log("Port 60 data register write: " + h(write_byte), LOG_PS2);
if(!this.have_mouse)
{
return;
}
// send ack
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.mouse_buffer.push(0xFA);
switch(write_byte)
{
case 0xE6:
// set scaling to 1:1
dbg_log("Scaling 1:1", LOG_PS2);
this.scaling2 = false;
break;
case 0xE7:
// set scaling to 2:1
dbg_log("Scaling 2:1", LOG_PS2);
this.scaling2 = true;
break;
case 0xE8:
// set mouse resolution
this.next_read_resolution = true;
break;
case 0xE9:
// status request - send one packet
this.send_mouse_packet(0, 0);
break;
case 0xEB:
// request single packet
dbg_log("unimplemented request single packet", LOG_PS2);
this.send_mouse_packet(0, 0);
break;
case 0xF2:
// MouseID Byte
dbg_log("required id: " + h(this.mouse_id), LOG_PS2);
this.mouse_buffer.push(this.mouse_id);
this.mouse_clicks = this.mouse_delta_x = this.mouse_delta_y = 0;
// this.send_mouse_packet(0, 0);
this.raise_irq();
break;
case 0xF3:
// sample rate
this.next_read_sample = true;
break;
case 0xF4:
// enable streaming
this.enable_mouse_stream = true;
this.use_mouse = true;
this.bus.send("mouse-enable", true);
this.mouse_clicks = this.mouse_delta_x = this.mouse_delta_y = 0;
break;
case 0xF5:
// disable streaming
this.enable_mouse_stream = false;
break;
case 0xF6:
// set defaults
this.enable_mouse_stream = false;
this.sample_rate = 100;
this.scaling2 = false;
this.resolution = 4;
break;
case 0xFF:
// reset, send completion code
dbg_log("Mouse reset", LOG_PS2);
this.mouse_buffer.push(0xAA);
this.mouse_buffer.push(0);
this.use_mouse = true;
this.bus.send("mouse-enable", true);
this.enable_mouse_stream = false;
this.sample_rate = 100;
this.scaling2 = false;
this.resolution = 4;
if(!this.mouse_reset_workaround)
{
this.mouse_id = 0x00;
}
this.mouse_clicks = this.mouse_delta_x = this.mouse_delta_y = 0;
break;
default:
dbg_log("Unimplemented mouse command: " + h(write_byte), LOG_PS2);
}
this.mouse_irq();
}
else if(this.read_controller_output_port)
{
this.read_controller_output_port = false;
this.controller_output_port = write_byte;
// If we ever want to implement A20 masking, here is where
// we should turn the masking off if the second bit is on
}
else
{
dbg_log("Port 60 data register write: " + h(write_byte), LOG_PS2);
// send ack
this.mouse_buffer.clear();
this.kbd_buffer.clear();
this.kbd_buffer.push(0xFA);
switch(write_byte)
{
case 0xED:
this.next_read_led = true;
break;
case 0xF0:
// get/set scan code set
this.next_handle_scan_code_set = true;
break;
case 0xF2:
// identify
this.kbd_buffer.push(0xAB);
this.kbd_buffer.push(83);
break;
case 0xF3:
// Set typematic rate and delay
this.next_read_rate = true;
break;
case 0xF4:
// enable scanning
dbg_log("kbd enable scanning", LOG_PS2);
this.enable_keyboard_stream = true;
break;
case 0xF5:
// disable scanning
dbg_log("kbd disable scanning", LOG_PS2);
this.enable_keyboard_stream = false;
break;
case 0xF6:
// reset defaults
//this.enable_keyboard_stream = false;
break;
case 0xFF:
this.kbd_buffer.clear();
this.kbd_buffer.push(0xFA);
this.kbd_buffer.push(0xAA);
this.kbd_buffer.push(0);
break;
default:
dbg_log("Unimplemented keyboard command: " + h(write_byte), LOG_PS2);
}
this.kbd_irq();
}
};
PS2.prototype.port64_write = function(write_byte)
{
dbg_log("port 64 write: " + h(write_byte), LOG_PS2);
switch(write_byte)
{
case 0x20:
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.kbd_buffer.push(this.command_register);
this.kbd_irq();
break;
case 0x60:
this.read_command_register = true;
break;
case 0xD1:
this.read_controller_output_port = true;
break;
case 0xD3:
this.read_output_register = true;
break;
case 0xD4:
this.next_is_mouse_command = true;
break;
case 0xA7:
// Disable second port
dbg_log("Disable second port", LOG_PS2);
this.command_register |= 0x20;
break;
case 0xA8:
// Enable second port
dbg_log("Enable second port", LOG_PS2);
this.command_register &= ~0x20;
break;
case 0xA9:
// test second ps/2 port
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.kbd_buffer.push(0);
this.kbd_irq();
break;
case 0xAA:
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.kbd_buffer.push(0x55);
this.kbd_irq();
break;
case 0xAB:
// Test first PS/2 port
this.kbd_buffer.clear();
this.mouse_buffer.clear();
this.kbd_buffer.push(0);
this.kbd_irq();
break;
case 0xAD:
// Disable Keyboard
dbg_log("Disable Keyboard", LOG_PS2);
this.command_register |= 0x10;
break;
case 0xAE:
// Enable Keyboard
dbg_log("Enable Keyboard", LOG_PS2);
this.command_register &= ~0x10;
break;
case 0xFE:
dbg_log("CPU reboot via PS2");
this.cpu.reboot_internal();
break;
default:
dbg_log("port 64: Unimplemented command byte: " + h(write_byte), LOG_PS2);
}
};