Skip to content

Commit

Permalink
Implement Virtio Console device.
Browse files Browse the repository at this point in the history
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
basicer committed Nov 29, 2023
1 parent 5b2eee5 commit 751f346
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion debug.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "";
Expand Down
1 change: 1 addition & 0 deletions src/browser/starter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions src/cpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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];

Expand Down Expand Up @@ -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)
{
Expand Down
294 changes: 294 additions & 0 deletions src/virtio_console.js
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();
};

0 comments on commit 751f346

Please sign in to comment.