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(); | |
}; | |