diff --git a/Makefile b/Makefile index f0aef1384f..fb222fabbb 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ CARGO_FLAGS=$(CARGO_FLAGS_SAFE) -C target-feature=+bulk-memory -C target-feature CORE_FILES=const.js config.js io.js main.js lib.js buffer.js ide.js pci.js floppy.js \ memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js \ acpi.js apic.js ioapic.js \ - state.js ne2k.js sb16.js virtio.js bus.js log.js \ + state.js ne2k.js sb16.js virtio.js virtio_console.js bus.js log.js \ cpu.js debug.js \ elf.js kernel.js LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js diff --git a/debug.html b/debug.html index ab9efa13e6..6e97d8fef8 100644 --- a/debug.html +++ b/debug.html @@ -11,7 +11,7 @@ "const.js config.js log.js lib.js buffer.js cpu.js debug.js " + "io.js main.js ide.js pci.js floppy.js " + "memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js sb16.js " + - "ne2k.js state.js virtio.js bus.js elf.js kernel.js"; + "ne2k.js state.js virtio.js virtio_console.js bus.js elf.js kernel.js"; var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js speaker.js serial.js network.js starter.js worker_bus.js print_stats.js filestorage.js"; var LIB_FILES = ""; diff --git a/src/browser/starter.js b/src/browser/starter.js index f9dfcd5ffc..0c0da0615f 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -279,6 +279,7 @@ V86.prototype.continue_init = async function(emulator, options) settings.preserve_mac_from_state_image = options.preserve_mac_from_state_image; settings.mac_address_translation = options.mac_address_translation; settings.cpuid_level = options.cpuid_level; + settings.virtio_console = options.virtio_console; if(options.network_adapter) { diff --git a/src/cpu.js b/src/cpu.js index 30c50c4f5a..e1f3f85220 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -400,6 +400,7 @@ CPU.prototype.get_state = function() state[79] = this.devices.uart1; state[80] = this.devices.uart2; state[81] = this.devices.uart3; + state[82] = this.devices.virtio_console; return state; }; @@ -474,6 +475,7 @@ CPU.prototype.set_state = function(state) this.devices.uart1 && this.devices.uart1.set_state(state[79]); this.devices.uart2 && this.devices.uart2.set_state(state[80]); this.devices.uart3 && this.devices.uart3.set_state(state[81]); + this.devices.virtio_console && this.devices.virtio_console.set_state(state[82]); this.fw_value = state[62]; @@ -889,6 +891,10 @@ CPU.prototype.init = function(settings, device_bus) { this.devices.virtio_9p = new Virtio9p(settings.fs9p, this, device_bus); } + if(settings.virtio_console) + { + this.devices.virtio_console = new VirtioConsole(this, device_bus); + } if(true) { diff --git a/src/virtio_console.js b/src/virtio_console.js new file mode 100644 index 0000000000..b71e7105d9 --- /dev/null +++ b/src/virtio_console.js @@ -0,0 +1,293 @@ + +"use strict"; + +var VIRTIO_CONSOLE_DEVICE_READY = 0; +var VIRTIO_CONSOLE_DEVICE_ADD = 1; +var VIRTIO_CONSOLE_DEVICE_REMOVE = 2; +var VIRTIO_CONSOLE_PORT_READY = 3; +var VIRTIO_CONSOLE_CONSOLE_PORT = 4; +var VIRTIO_CONSOLE_RESIZE = 5; +var VIRTIO_CONSOLE_PORT_OPEN = 6; +var VIRTIO_CONSOLE_PORT_NAME = 7; + +var VIRTIO_CONSOLE_F_SIZE = 0; +var VIRTIO_CONSOLE_F_MULTIPORT = 1; +var VIRTIO_CONSOLE_F_EMERG_WRITE = 2; + +/** + * @constructor + * + * @param {CPU} cpu + */ +function VirtioConsole(cpu, bus) { + /** @const @type {BusConnector} */ + this.bus = bus; + this.rows = 25; + this.cols = 80; + this.ports = 4; + + var queues = [ + { + size_supported: 16, + notify_offset: 0, + }, + { + size_supported: 16, + notify_offset: 1, + }, + { + size_supported: 16, + notify_offset: 2, + }, + { + size_supported: 16, + notify_offset: 3, + }, + ]; + + for (let i = 1; i < this.ports; ++i) + { + queues.push({size_supported: 16, notify_offset: 0}); + queues.push({size_supported: 8, notify_offset: 1}); + } + + /** @type {VirtIO} */ + this.virtio = new VirtIO(cpu, + { + name: "virtio-console", + pci_id: 0x07 << 3, + device_id: 0x1043, + subsystem_device_id: 3, + common: + { + initial_port: 0xB800, + queues: queues, + features: + [ + VIRTIO_CONSOLE_F_SIZE, + VIRTIO_CONSOLE_F_MULTIPORT, + VIRTIO_F_VERSION_1, + ], + on_driver_ok: () => {}, + }, + notification: + { + initial_port: 0xB900, + single_handler: false, + handlers: + [ + (queue_id) => + { + let queue = this.virtio.queues[queue_id]; + + // TODO: Full buffer looks like an empty buffer so prevent it from filling + while (queue.count_requests() > queue.size - 2) queue.pop_request(); + }, + (queue_id) => + { + let queue = this.virtio.queues[queue_id]; + let port = queue_id > 3 ? (queue_id-3 >> 1) : 0; + while(queue.has_request()) + { + const bufchain = queue.pop_request(); + const buffer = new Uint8Array(bufchain.length_readable); + bufchain.get_next_blob(buffer); + this.bus.send("virito-" + port + "-output-bytes", buffer); + this.Ack(queue_id, bufchain); + } + }, + (queue_id) => + { + if(queue_id != 2) + { + dbg_assert(false, "VirtioConsole Notified for wrong queue: " + queue_id + + " (expected queue_id of 2)"); + return; + } + let queue = this.virtio.queues[queue_id]; + // Full buffer looks like an empty buffer so prevent it from filling + while (queue.count_requests() > queue.size - 2) queue.pop_request(); + }, + (queue_id) => + { + if(queue_id != 3) + { + dbg_assert(false, "VirtioConsole Notified for wrong queue: " + queue_id + + " (expected queue_id of 3)"); + return; + } + let queue = this.virtio.queues[queue_id]; + + while(queue.has_request()) + { + const bufchain = queue.pop_request(); + const buffer = new Uint8Array(bufchain.length_readable); + bufchain.get_next_blob(buffer); + + + var parts = marshall.Unmarshall(["w", "h", "h"], buffer, { offset : 0 }); + var port = parts [0]; + var event = parts[1]; + var value = parts[2]; + + + this.Ack(queue_id, bufchain); + + switch(event) { + case VIRTIO_CONSOLE_DEVICE_READY: + for (var i = 0; i < this.ports; ++i) { + this.SendEvent(i, VIRTIO_CONSOLE_DEVICE_ADD, 0); + } + break; + case VIRTIO_CONSOLE_PORT_READY: + this.Ack(queue_id, bufchain); + this.SendEvent(port, VIRTIO_CONSOLE_CONSOLE_PORT, 1); + this.SendName(port, 'virtio-' + port); + this.SendEvent(port, VIRTIO_CONSOLE_PORT_OPEN, 1); + + break; + case VIRTIO_CONSOLE_PORT_OPEN: + this.Ack(queue_id, bufchain); + if (port == 0) { + this.SendWindowSize(port); + } + break; + default: + dbg_assert(false," VirtioConsole received unknown event: " + event[1]); + return; + + } + } + //queue.notify_me_after(0); + // Don't flush replies here: async replies are not completed yet. + }, + ], + }, + isr_status: + { + initial_port: 0xB700, + }, + device_specific: + { + initial_port: 0xB600, + struct: + [ + { + bytes: 2, + name: "cols", + read: () => this.cols, + write: data => { /* read only */ }, + }, + { + bytes: 2, + name: "rows", + read: () => this.rows, + write: data => { /* read only */ }, + }, + { + bytes: 4, + name: "max_nr_ports", + read: () => this.ports, + write: data => { /* read only */ }, + }, + { + bytes: 4, + name: "emerg_wr", + read: () => 0, + write: data => { + dbg_assert(false, "Emergency write!"); + }, + }, + ] + }, + }); + + this.bus.register("virtio-1-input-bytes", function(data) + { + let queue = this.virtio.queues[0]; + if (queue.has_request()) { + const bufchain = queue.pop_request(); + this.Send(0, bufchain, new Uint8Array(data)); + } else { + //TODO: Buffer + } + }, this); + + this.bus.register("virtio-1-resize", function(size) + { + this.cols = size[0]; + this.rows = size[1]; + + if (this.virtio.queues[2].is_configured() && this.virtio.queues[2].has_request()) { + this.SendWindowSize(0); + } + }, this); +} + +VirtioConsole.prototype.SendWindowSize = function(port) +{ + const bufchain = this.virtio.queues[2].pop_request(); + let buf = new Uint8Array(12); + marshall.Marshall(["w", "h", "h", "h", "h"], [port, VIRTIO_CONSOLE_RESIZE, 0, this.rows, this.cols], buf, 0); + this.Send(2, bufchain, buf); +}; + +VirtioConsole.prototype.SendName = function(port, name) +{ + const bufchain = this.virtio.queues[2].pop_request(); + let namex = new TextEncoder().encode(name); + let buf = new Uint8Array(8 + namex.length + 1); + marshall.Marshall(["w", "h", "h"], [port, VIRTIO_CONSOLE_PORT_NAME, 1], buf, 0); + for ( var i = 0; i < namex.length; ++i ) { + buf[i+8] = namex[i]; + } + buf[8 + namex.length] = 0; + this.Send(2, bufchain, buf); +}; + + +VirtioConsole.prototype.get_state = function() +{ + var state = []; + + state[0] = this.virtio; + state[1] = this.rows; + state[2] = this.cols; + state[3] = this.ports; + + return state; +}; + +VirtioConsole.prototype.set_state = function(state) +{ + this.virtio.set_state(state[0]); + this.rows = state[1]; + this.cols = state[2]; + this.ports = state[3]; +}; + +VirtioConsole.prototype.Reset = function() { + +}; + +VirtioConsole.prototype.SendEvent = function(port, event, value) { + var queue = this.virtio.queues[2]; + const bufchain = queue.pop_request(); + + var buf = new Uint8Array(8); + marshall.Marshall(["w","h","h"], [port, event, value], buf, 0); + this.Send(2, bufchain, buf); +}; + +VirtioConsole.prototype.Send = function (queue_id, bufchain, blob) { + bufchain.set_next_blob(blob); + this.virtio.queues[queue_id].push_reply(bufchain); + this.virtio.queues[queue_id].flush_replies(); +}; + +VirtioConsole.prototype.Ack = function (queue_id, bufchain) { + bufchain.set_next_blob(new Uint8Array(0)); + this.virtio.queues[queue_id].push_reply(bufchain); + this.virtio.queues[queue_id].flush_replies(); +}; +