"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.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) { // TODO: Mouse Wheel // http://www.computer-engineering.org/ps2mouse/ }, this); this.command_register = 1 | 4; this.read_output_register = false; this.read_command_register = 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; 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.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(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; dbg_log("mouse sample rate: " + h(write_byte), 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 this.mouse_buffer.push(0); this.mouse_buffer.push(0); this.mouse_clicks = this.mouse_delta_x = this.mouse_delta_y = 0; 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; 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 { 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 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); } };