| "use strict"; |
| |
| /* |
| * Serial ports |
| * http://wiki.osdev.org/UART |
| * https://github.com/s-macke/jor1k/blob/master/js/worker/dev/uart.js |
| * https://www.freebsd.org/doc/en/articles/serial-uart/ |
| */ |
| |
| /** @const */ |
| var DLAB = 0x80; |
| |
| |
| /** @const */ var UART_IER_MSI = 0x08; /* Modem Status Changed int. */ |
| /** @const */ var UART_IER_THRI = 0x02; /* Enable Transmitter holding register int. */ |
| /** @const */ var UART_IER_RDI = 0x01; /* Enable receiver data interrupt */ |
| |
| /** @const */var UART_IIR_MSI = 0x00; /* Modem status interrupt (Low priority) */ |
| /** @const */var UART_IIR_NO_INT = 0x01; |
| /** @const */var UART_IIR_THRI = 0x02; /* Transmitter holding register empty */ |
| /** @const */var UART_IIR_RDI = 0x04; /* Receiver data interrupt */ |
| /** @const */var UART_IIR_RLSI = 0x06; /* Receiver line status interrupt (High p.) */ |
| /** @const */var UART_IIR_CTI = 0x0c; /* Character timeout */ |
| |
| /** @const */ var UART_LSR_DATA_READY = 0x1; // data available |
| /** @const */ var UART_LSR_TX_EMPTY = 0x20; // TX (THR) buffer is empty |
| /** @const */ var UART_LSR_TRANSMITTER_EMPTY = 0x40; // TX empty and line is idle |
| |
| |
| /** |
| * @constructor |
| * @param {CPU} cpu |
| * @param {number} port |
| * @param {BusConnector} bus |
| */ |
| function UART(cpu, port, bus) |
| { |
| /** @const @type {BusConnector} */ |
| this.bus = bus; |
| |
| /** @const @type {CPU} */ |
| this.cpu = cpu; |
| |
| this.ints = 0; |
| |
| this.baud_rate = 0; |
| |
| this.line_control = 0; |
| |
| // line status register |
| this.lsr = UART_LSR_TRANSMITTER_EMPTY | UART_LSR_TX_EMPTY; |
| |
| this.fifo_control = 0; |
| |
| // interrupts enable |
| this.ier = 0; |
| |
| // interrupt identification register |
| this.iir = UART_IIR_NO_INT; |
| |
| this.modem_control = 0; |
| this.modem_status = 0; |
| |
| this.scratch_register = 0; |
| |
| this.irq = 0; |
| |
| this.input = new ByteQueue(4096); |
| |
| this.current_line = []; |
| |
| if(port === 0x3E8 || port === 0x3F8) |
| { |
| this.irq = 4; |
| } |
| else if(port === 0x3E8 || port === 0x3E8) |
| { |
| this.irq = 3; |
| } |
| else |
| { |
| dbg_log("Invalid port: " + h(port), LOG_SERIAL); |
| return; |
| } |
| |
| this.bus.register("serial0-input", function(data) |
| { |
| this.data_received(data); |
| }, this); |
| |
| var io = cpu.io; |
| |
| function write_data(out_byte) |
| { |
| if(this.line_control & DLAB) |
| { |
| this.baud_rate = this.baud_rate & ~0xFF | out_byte; |
| return; |
| } |
| |
| dbg_log("data: " + h(out_byte), LOG_SERIAL); |
| |
| this.ThrowInterrupt(UART_IIR_THRI); |
| |
| if(out_byte === 0xFF) |
| { |
| return; |
| } |
| |
| var char = String.fromCharCode(out_byte); |
| |
| this.bus.send("serial0-output-char", char); |
| |
| this.current_line.push(out_byte); |
| |
| if(char === "\n") |
| { |
| dbg_log("SERIAL: " + String.fromCharCode.apply("", this.current_line)); |
| this.bus.send("serial0-output-line", String.fromCharCode.apply("", this.current_line)); |
| this.current_line = []; |
| } |
| } |
| |
| io.register_write(port, this, function(out_byte) |
| { |
| write_data.call(this, out_byte); |
| }, function(out_word) |
| { |
| write_data.call(this, out_word & 0xFF); |
| write_data.call(this, out_word >> 8); |
| }); |
| |
| io.register_write(port | 1, this, function(out_byte) |
| { |
| if(this.line_control & DLAB) |
| { |
| this.baud_rate = this.baud_rate & 0xFF | out_byte << 8; |
| dbg_log("baud rate: " + h(this.baud_rate), LOG_SERIAL); |
| } |
| else |
| { |
| this.ier = out_byte & 0xF; |
| dbg_log("interrupt enable: " + h(out_byte), LOG_SERIAL); |
| this.CheckInterrupt(); |
| } |
| }); |
| |
| io.register_read(port, this, function() |
| { |
| if(this.line_control & DLAB) |
| { |
| return this.baud_rate & 0xFF; |
| } |
| else |
| { |
| var data = this.input.shift(); |
| |
| if(data === -1) |
| { |
| dbg_log("Read input empty", LOG_SERIAL); |
| } |
| else |
| { |
| dbg_log("Read input: " + h(data), LOG_SERIAL); |
| } |
| |
| if(this.input.length === 0) |
| { |
| this.lsr &= ~UART_LSR_DATA_READY; |
| this.ClearInterrupt(UART_IIR_CTI); |
| } |
| |
| return data; |
| } |
| }); |
| |
| io.register_read(port | 1, this, function() |
| { |
| if(this.line_control & DLAB) |
| { |
| return this.baud_rate >> 8; |
| } |
| else |
| { |
| return this.ier & 0xF; |
| } |
| }); |
| |
| io.register_read(port | 2, this, function() |
| { |
| var ret = this.iir & 0xF | 0xC0; |
| dbg_log("read interrupt identification: " + h(this.iir), LOG_SERIAL); |
| |
| if (this.iir == UART_IIR_THRI) { |
| this.ClearInterrupt(UART_IIR_THRI); |
| } |
| |
| return ret; |
| }); |
| io.register_write(port | 2, this, function(out_byte) |
| { |
| dbg_log("fifo control: " + h(out_byte), LOG_SERIAL); |
| this.fifo_control = out_byte; |
| }); |
| |
| io.register_read(port | 3, this, function() |
| { |
| dbg_log("read line control: " + h(this.line_control), LOG_SERIAL); |
| return this.line_control; |
| }); |
| io.register_write(port | 3, this, function(out_byte) |
| { |
| dbg_log("line control: " + h(out_byte), LOG_SERIAL); |
| this.line_control = out_byte; |
| }); |
| |
| |
| io.register_read(port | 4, this, function() |
| { |
| return this.modem_control; |
| }); |
| io.register_write(port | 4, this, function(out_byte) |
| { |
| dbg_log("modem control: " + h(out_byte), LOG_SERIAL); |
| this.modem_control = out_byte; |
| }); |
| |
| io.register_read(port | 5, this, function() |
| { |
| dbg_log("read line status: " + h(this.lsr), LOG_SERIAL); |
| return this.lsr; |
| }); |
| io.register_write(port | 5, this, function(out_byte) |
| { |
| dbg_log("Factory test write", LOG_SERIAL); |
| }); |
| |
| io.register_read(port | 6, this, function() |
| { |
| dbg_log("read modem status: " + h(this.modem_status), LOG_SERIAL); |
| return this.modem_status; |
| }); |
| io.register_write(port | 6, this, function(out_byte) |
| { |
| dbg_log("Unkown register write (base+6)", LOG_SERIAL); |
| }); |
| |
| io.register_read(port | 7, this, function() |
| { |
| return this.scratch_register; |
| }); |
| io.register_write(port | 7, this, function(out_byte) |
| { |
| this.scratch_register = out_byte; |
| }); |
| } |
| |
| UART.prototype.get_state = function() |
| { |
| var state = []; |
| |
| state[0] = this.ints; |
| state[1] = this.baud_rate; |
| state[2] = this.line_control; |
| state[3] = this.lsr; |
| state[4] = this.fifo_control; |
| state[5] = this.ier; |
| state[6] = this.iir; |
| state[7] = this.modem_control; |
| state[8] = this.modem_status; |
| state[9] = this.scratch_register; |
| state[10] = this.irq; |
| |
| return state; |
| }; |
| |
| UART.prototype.set_state = function(state) |
| { |
| this.ints = state[0]; |
| this.baud_rate = state[1]; |
| this.line_control = state[2]; |
| this.lsr = state[3]; |
| this.fifo_control = state[4]; |
| this.ier = state[5]; |
| this.iir = state[6]; |
| this.modem_control = state[7]; |
| this.modem_status = state[8]; |
| this.scratch_register = state[9]; |
| this.irq = state[10]; |
| }; |
| |
| UART.prototype.CheckInterrupt = function() { |
| if ((this.ints & (1 << UART_IIR_CTI)) && (this.ier & UART_IER_RDI)) { |
| this.iir = UART_IIR_CTI; |
| this.cpu.device_raise_irq(this.irq); |
| } else |
| if ((this.ints & (1 << UART_IIR_THRI)) && (this.ier & UART_IER_THRI)) { |
| this.iir = UART_IIR_THRI; |
| this.cpu.device_raise_irq(this.irq); |
| } else |
| if ((this.ints & (1 << UART_IIR_MSI)) && (this.ier & UART_IER_MSI)) { |
| this.iir = UART_IIR_MSI; |
| this.cpu.device_raise_irq(this.irq); |
| } else { |
| this.iir = UART_IIR_NO_INT; |
| this.cpu.device_lower_irq(this.irq); |
| } |
| }; |
| |
| UART.prototype.ThrowInterrupt = function(line) { |
| this.ints |= (1 << line); |
| this.CheckInterrupt(); |
| } |
| |
| UART.prototype.ClearInterrupt = function(line) { |
| this.ints &= ~(1 << line); |
| this.CheckInterrupt(); |
| }; |
| |
| /** |
| * @param {number} data |
| */ |
| UART.prototype.data_received = function(data) |
| { |
| dbg_log("input: " + h(data), LOG_SERIAL); |
| this.input.push(data); |
| |
| this.lsr |= UART_LSR_DATA_READY; |
| this.ThrowInterrupt(UART_IIR_CTI); |
| }; |