-
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.
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
303 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,294 @@ | ||
|
||
"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!"); | ||
}, | ||
}, | ||
] | ||
}, | ||
}); | ||
|
||
for (var 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 ( 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(); | ||
}; | ||
|