123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813 |
- "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);
- }
- };
|