-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Virtio Console device. (#949)
Implements a virtio console device. This allows reading and writing buffers in bulk as well as a channel to communicate terminal size updates.
- Loading branch information
Showing
5 changed files
with
307 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
|
||
"use strict"; | ||
|
||
// 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 | ||
*/ | ||
function VirtioConsole(cpu, bus) | ||
{ | ||
/** @const @type {BusConnector} */ | ||
this.bus = bus; | ||
this.rows = 25; | ||
this.cols = 80; | ||
this.ports = 4; | ||
|
||
let 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); | ||
|
||
|
||
let parts = marshall.Unmarshall(["w", "h", "h"], buffer, { offset : 0 }); | ||
let port = parts[0]; | ||
let event = parts[1]; | ||
let 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) { | ||
let queue = port == 0 ? 0 : port * 2 + 2; | ||
this.bus.register("virtio-" + port + "-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-" + port + "-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(port); | ||
} | ||
}, 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 ( 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() | ||
{ | ||
let 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) | ||
{ | ||
let queue = this.virtio.queues[2]; | ||
const bufchain = queue.pop_request(); | ||
|
||
let 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(); | ||
}; | ||
|