From 82e23acf2b36765453536f47a01ccaad6d12e194 Mon Sep 17 00:00:00 2001 From: Rob Blanckaert Date: Sat, 6 Jul 2024 15:12:34 -0700 Subject: [PATCH] virtio_net device --- Makefile | 5 +- debug.html | 2 +- nodejs-loader.mjs | 2 +- src/browser/starter.js | 1 + src/cpu.js | 14 +- src/virtio.js | 6 +- src/virtio_net.js | 246 +++++++++++++++++++++++++++++++++ tests/devices/fetch_network.js | 21 +++ 8 files changed, 288 insertions(+), 9 deletions(-) create mode 100644 src/virtio_net.js diff --git a/Makefile b/Makefile index f079198c8d..892b1fe07e 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,8 @@ 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 rtc.js uart.js \ acpi.js apic.js ioapic.js \ - state.js ne2k.js sb16.js virtio.js virtio_console.js bus.js log.js \ - cpu.js debug.js \ + state.js ne2k.js sb16.js virtio.js virtio_console.js virtio_net.js \ + bus.js log.js cpu.js debug.js \ elf.js kernel.js LIB_FILES=9p.js filesystem.js jor1k.js marshall.js BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \ @@ -306,6 +306,7 @@ devices-test: all-debug ./tests/devices/virtio_9p.js ./tests/devices/virtio_console.js ./tests/devices/fetch_network.js + USE_VIRTIO=1 ./tests/devices/fetch_network.js ./tests/devices/wisp_network.js rust-test: $(RUST_FILES) diff --git a/debug.html b/debug.html index 9bc0c51953..c2054197ec 100644 --- a/debug.html +++ b/debug.html @@ -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 rtc.js uart.js acpi.js apic.js ioapic.js sb16.js " + - "ne2k.js state.js virtio.js virtio_console.js bus.js elf.js kernel.js"; + "ne2k.js state.js virtio.js virtio_console.js virtio_net.js bus.js elf.js kernel.js"; var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js speaker.js serial.js network.js fake_network.js fetch_network.js wisp_network.js starter.js worker_bus.js print_stats.js filestorage.js"; var LIB_FILES = ""; diff --git a/nodejs-loader.mjs b/nodejs-loader.mjs index ae184c777d..e4ddc465e2 100644 --- a/nodejs-loader.mjs +++ b/nodejs-loader.mjs @@ -38,7 +38,7 @@ let files = [ "src/sb16.js", "src/virtio.js", "src/virtio_console.js", - //"src/virtio_net.js", + "src/virtio_net.js", //"src/virtio_balloon.js", "src/bus.js", diff --git a/src/browser/starter.js b/src/browser/starter.js index 763b363172..30d84fe55f 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -301,6 +301,7 @@ V86.prototype.continue_init = async function(emulator, options) settings.mac_address_translation = options.mac_address_translation; settings.cpuid_level = options.cpuid_level; settings.virtio_console = options.virtio_console; + settings.virtio_net = options.virtio_net; const relay_url = options.network_relay_url || options.net_device && options.net_device.relay_url; if(relay_url) diff --git a/src/cpu.js b/src/cpu.js index b10c990743..c81759fd42 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -418,6 +418,7 @@ CPU.prototype.get_state = function() state[80] = this.devices.uart2; state[81] = this.devices.uart3; state[82] = this.devices.virtio_console; + state[83] = this.devices.virtio_net; return state; }; @@ -549,6 +550,7 @@ CPU.prototype.set_state = function(state) 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.devices.virtio_net && this.devices.virtio_net.set_state(state[83]); this.fw_value = state[62]; @@ -687,11 +689,15 @@ CPU.prototype.reboot_internal = function() if(this.devices.virtio_9p) { - this.devices.virtio_9p.reset(); + this.devices.virtio_9p.Reset(); } if(this.devices.virtio_console) { - this.devices.virtio_console.reset(); + this.devices.virtio_console.Reset(); + } + if(this.devices.virtio_net) + { + this.devices.virtio_net.Reset(); } this.load_bios(); @@ -973,6 +979,10 @@ CPU.prototype.init = function(settings, device_bus) { this.devices.net = new Ne2k(this, device_bus, settings.preserve_mac_from_state_image, settings.mac_address_translation); } + else if(settings.net_device.type === "virtio") + { + this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image); + } if(settings.fs9p) { diff --git a/src/virtio.js b/src/virtio.js index bee2af49ba..7c3f11d5d5 100644 --- a/src/virtio.js +++ b/src/virtio.js @@ -553,7 +553,7 @@ VirtIO.prototype.create_common_capability = function(options) read: () => 0, write: data => { - dbg_log("Warning: High dword of 64 bit queue_desc ignored", LOG_VIRTIO); + if(data !== 0) dbg_log("Warning: High dword of 64 bit queue_desc ignored:" + data, LOG_VIRTIO); }, }, { @@ -571,7 +571,7 @@ VirtIO.prototype.create_common_capability = function(options) read: () => 0, write: data => { - dbg_log("Warning: High dword of 64 bit queue_avail ignored", LOG_VIRTIO); + if(data !== 0) dbg_log("Warning: High dword of 64 bit queue_avail ignored:" + data, LOG_VIRTIO); }, }, { @@ -589,7 +589,7 @@ VirtIO.prototype.create_common_capability = function(options) read: () => 0, write: data => { - dbg_log("Warning: High dword of 64 bit queue_used ignored", LOG_VIRTIO); + if(data !== 0) dbg_log("Warning: High dword of 64 bit queue_used ignored:" + data, LOG_VIRTIO); }, }, ], diff --git a/src/virtio_net.js b/src/virtio_net.js new file mode 100644 index 0000000000..b8e5a4f79c --- /dev/null +++ b/src/virtio_net.js @@ -0,0 +1,246 @@ +"use strict"; + +// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html#x1-2900003 + + +const VIRTIO_NET_F_MAC = 5; +const VIRTIO_NET_F_CTRL_VQ = 17; +const VIRTIO_NET_F_STATUS = 16; +const VIRTIO_NET_F_MQ = 22; +const VIRTIO_NET_F_CTRL_MAC_ADDR = 23; +const VIRTIO_NET_F_MTU = 3; + +const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET = 0; +const VIRTIO_NET_CTRL_MAC_ADDR_SET = 1; + +/** + * @constructor + * @param {CPU} cpu + * @param {BusConnector} bus + * @param {Boolean} preserve_mac_from_state_image + */ +function VirtioNet(cpu, bus, preserve_mac_from_state_image) +{ + /** @const @type {BusConnector} */ + this.bus = bus; + this.id = cpu.devices.net ? 1 : 0; + this.pairs = 1; + this.status = 1; + this.preserve_mac_from_state_image = preserve_mac_from_state_image; + this.mac = new Uint8Array([ + 0x00, 0x22, 0x15, + Math.random() * 255 | 0, + Math.random() * 255 | 0, + Math.random() * 255 | 0, + ]); + + this.bus.send("net" + this.id + "-mac", format_mac(this.mac)); + + const queues = []; + + for(let i = 0; i < this.pairs; ++i) + { + queues.push({size_supported: 32, notify_offset: 0}); + queues.push({size_supported: 32, notify_offset: 1}); + } + queues.push({ + size_supported: 16, + notify_offset: 2, + }); + + /** @type {VirtIO} */ + this.virtio = new VirtIO(cpu, + { + name: "virtio-net", + pci_id: 0x0A << 3, + device_id: 0x1041, + subsystem_device_id: 1, + common: + { + initial_port: 0xC800, + queues: queues, + features: + [ + VIRTIO_NET_F_MAC, + VIRTIO_NET_F_STATUS, + VIRTIO_NET_F_MQ, + VIRTIO_NET_F_MTU, + VIRTIO_NET_F_CTRL_VQ, + VIRTIO_NET_F_CTRL_MAC_ADDR, + VIRTIO_F_VERSION_1, + ], + on_driver_ok: () => {}, + }, + notification: + { + initial_port: 0xC900, + single_handler: false, + handlers: + [ + (queue_id) => + { + // TODO: Full buffer looks like an empty buffer so prevent it from filling + // The kernel gives us a prefilled one, so throw the first bufchain so + // it doesnt look filled. + + const queue = this.virtio.queues[queue_id]; + + const desc_idx = queue.avail_get_entry(queue.avail_last_idx); + const bufchain = new VirtQueueBufferChain(queue, desc_idx); + queue.avail_last_idx = queue.avail_last_idx + 1 & queue.mask; + this.virtio.queues[0].push_reply(bufchain); + this.virtio.queues[0].flush_replies(); + }, + (queue_id) => + { + 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); + this.bus.send("net" + this.id + "-send", buffer.subarray(12)); + this.virtio.queues[queue_id].push_reply(bufchain); + } + this.virtio.queues[queue_id].flush_replies(); + }, + (queue_id) => + { + if(queue_id !== this.pairs * 2) + { + 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(["b", "b"], buffer, { offset : 0 }); + const xclass = parts[0]; + const command = parts[1]; + + + //this.Ack(queue_id, bufchain); + + switch(xclass << 8 | command) { + case 4 << 8 | VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: + const data = marshall.Unmarshall(["h"], buffer, { offset : 2 }); + dbg_assert(data[0] === 1); + this.Send(queue_id, bufchain, new Uint8Array([0])); + break; + case 1 << 8 | VIRTIO_NET_CTRL_MAC_ADDR_SET: + this.mac = buffer.subarray(2, 8); + this.Send(queue_id, bufchain, new Uint8Array([0])); + this.bus.send("net" + this.id + "-mac", format_mac(this.mac)); + break; + default: + dbg_assert(false," VirtioConsole received unknown command: " + xclass + ":" + command); + this.Send(queue_id, bufchain, new Uint8Array([1])); + return; + + } + } + }, + ], + }, + isr_status: + { + initial_port: 0xC700, + }, + device_specific: + { + initial_port: 0xC600, + struct: + [0,1,2,3,4,5].map((v,k) => ({ + bytes: 1, + name: "mac_" + k, + read: () => this.mac[k], + write: data => { /* read only */ }, + })).concat( + [ + { + bytes: 2, + name: "status", + read: () => this.status, + write: data => { /* read only */ }, + }, + { + bytes: 2, + name: "max_pairs", + read: () => this.pairs, + write: data => { /* read only */ }, + }, + { + bytes: 2, + name: "mtu", + read: () => 1500, + write: data => {}, + } + ]) + }, + }); + + this.bus.register("net" + this.id + "-receive", data => { + const with_header = new Uint8Array(12 + data.byteLength); + const view = new DataView(with_header.buffer, with_header.byteOffset, with_header.byteLength); + view.setInt16(10, 1); + with_header.set(data, 12); + + const queue = this.virtio.queues[0]; + if(queue.has_request()) { + const bufchain = queue.pop_request(); + bufchain.set_next_blob(with_header); + this.virtio.queues[0].push_reply(bufchain); + this.virtio.queues[0].flush_replies(); + } else { + console.log("No buffer to write into!"); + } + }, this); + +} + + +VirtioNet.prototype.get_state = function() +{ + const state = []; + state[0] = this.virtio; + state[1] = this.id; + + if(this.preserve_mac_from_state_image) + { + this.mac = state[2]; + this.bus.send("net" + this.id + "-mac", format_mac(this.mac)); + } + + return state; +}; + +VirtioNet.prototype.set_state = function(state) +{ + this.virtio.set_state(state[0]); +}; + +VirtioNet.prototype.Reset = function() { + +}; + +VirtioNet.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(); +}; + +VirtioNet.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(); +}; diff --git a/tests/devices/fetch_network.js b/tests/devices/fetch_network.js index c3f7cf8575..25888c12a4 100755 --- a/tests/devices/fetch_network.js +++ b/tests/devices/fetch_network.js @@ -4,6 +4,7 @@ process.on("unhandledRejection", exn => { throw exn; }); const TEST_RELEASE_BUILD = +process.env.TEST_RELEASE_BUILD; +const USE_VIRTIO = !!process.env.USE_VIRTIO; const V86 = require(`../../build/${TEST_RELEASE_BUILD ? "libv86" : "libv86-debug"}.js`).V86; @@ -25,6 +26,24 @@ const tests = assert(/lease of 192.168.86.100 obtained/.test(capture), "lease of 192.168.86.100 obtained"); }, }, + { + name: "lspci", + timeout: 60, + start: () => + { + emulator.serial0_send("lspci -k\n"); + emulator.serial0_send("echo -e done\\\\tlspci\n"); + }, + end_trigger: "done\tlspci", + end: (capture) => + { + if(!USE_VIRTIO) { + assert(/ne2k/.test(capture), "ne2k missing from lspci"); + } else { + assert(!/ne2k/.test(capture), "ne2k in lspci"); + } + }, + }, { name: "ifconfig", start: () => @@ -116,6 +135,8 @@ const emulator = new V86({ memory_size: 64 * 1024 * 1024, disable_jit: +process.env.DISABLE_JIT, network_relay_url: "fetch", + virtio_net: USE_VIRTIO, + enable_ne2k: !USE_VIRTIO, log_level: SHOW_LOGS ? 0x400000 : 0, });