Spaces:
Runtime error
Runtime error
| import { dbg_assert } from "./log.js"; | |
| import { VirtIO, VIRTIO_F_VERSION_1 } from "./virtio.js"; | |
| import * as marshall from "../lib/marshall.js"; | |
| // For Types Only | |
| import { CPU } from "./cpu.js"; | |
| import { BusConnector } from "./bus.js"; | |
| // https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 | |
| const VIRTIO_CONSOLE_DEVICE_READY = 0; | |
| const VIRTIO_CONSOLE_DEVICE_ADD = 1; | |
| const VIRTIO_CONSOLE_DEVICE_REMOVE = 2; | |
| const VIRTIO_CONSOLE_PORT_READY = 3; | |
| const VIRTIO_CONSOLE_CONSOLE_PORT = 4; | |
| const VIRTIO_CONSOLE_RESIZE = 5; | |
| const VIRTIO_CONSOLE_PORT_OPEN = 6; | |
| const VIRTIO_CONSOLE_PORT_NAME = 7; | |
| const VIRTIO_CONSOLE_F_SIZE = 0; | |
| const VIRTIO_CONSOLE_F_MULTIPORT = 1; | |
| const VIRTIO_CONSOLE_F_EMERG_WRITE = 2; | |
| /** | |
| * @constructor | |
| * | |
| * @param {CPU} cpu | |
| */ | |
| export function VirtioConsole(cpu, bus) | |
| { | |
| /** @const @type {BusConnector} */ | |
| this.bus = bus; | |
| this.rows = 25; | |
| this.cols = 80; | |
| this.ports = 4; | |
| const 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: 0x0C << 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) => | |
| { | |
| }, | |
| (queue_id) => | |
| { | |
| const queue = this.virtio.queues[queue_id]; | |
| const 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("virtio-console" + 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; | |
| } | |
| const 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; | |
| } | |
| const 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); | |
| const parts = marshall.Unmarshall(["w", "h", "h"], buffer, { offset : 0 }); | |
| const port = parts[0]; | |
| const event = parts[1]; | |
| const value = parts[2]; | |
| this.Ack(queue_id, bufchain); | |
| switch(event) { | |
| case VIRTIO_CONSOLE_DEVICE_READY: | |
| for(let 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; | |
| } | |
| } | |
| }, | |
| ], | |
| }, | |
| 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!"); | |
| }, | |
| }, | |
| ] | |
| }, | |
| }); | |
| for(let port = 0; port < this.ports; ++port) { | |
| const queue_id = port === 0 ? 0 : port * 2 + 2; | |
| this.bus.register("virtio-console" + port + "-input-bytes", function(data) { | |
| const queue = this.virtio.queues[queue_id]; | |
| if(queue.has_request()) { | |
| const bufchain = queue.pop_request(); | |
| this.Send(queue_id, bufchain, new Uint8Array(data)); | |
| } else { | |
| //TODO: Buffer | |
| } | |
| }, this); | |
| this.bus.register("virtio-console" + port + "-resize", function(size) { | |
| if(port === 0) { | |
| this.cols = size[0]; | |
| this.rows = size[1]; | |
| } | |
| if(this.virtio.queues[2].is_configured() && this.virtio.queues[2].has_request()) { | |
| this.SendWindowSize(port, size[0], size[1]); | |
| } | |
| }, this); | |
| } | |
| } | |
| VirtioConsole.prototype.SendWindowSize = function(port, cols = undefined, rows = undefined) | |
| { | |
| rows = rows || this.rows; | |
| cols = cols || this.cols; | |
| const bufchain = this.virtio.queues[2].pop_request(); | |
| const buf = new Uint8Array(12); | |
| marshall.Marshall(["w", "h", "h", "h", "h"], [port, VIRTIO_CONSOLE_RESIZE, 0, rows, cols], buf, 0); | |
| this.Send(2, bufchain, buf); | |
| }; | |
| VirtioConsole.prototype.SendName = function(port, name) | |
| { | |
| const bufchain = this.virtio.queues[2].pop_request(); | |
| const namex = new TextEncoder().encode(name); | |
| const buf = new Uint8Array(8 + namex.length + 1); | |
| marshall.Marshall(["w", "h", "h"], [port, VIRTIO_CONSOLE_PORT_NAME, 1], buf, 0); | |
| for( let 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() | |
| { | |
| const 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() { | |
| this.virtio.reset(); | |
| }; | |
| VirtioConsole.prototype.SendEvent = function(port, event, value) | |
| { | |
| const queue = this.virtio.queues[2]; | |
| const bufchain = queue.pop_request(); | |
| const 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(); | |
| }; | |