diff --git a/Makefile b/Makefile index 5f6b93aa4d..79ec0ece54 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 utf8.js BROWSER_FILES=screen.js keyboard.js mouse.js speaker.js serial.js \ diff --git a/debug.html b/debug.html index 82f6dac576..7e37131054 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 starter.js worker_bus.js print_stats.js filestorage.js"; var LIB_FILES = ""; diff --git a/src/browser/starter.js b/src/browser/starter.js index 4bdc8d4a0f..b7c9e10151 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -281,6 +281,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; if(options.network_adapter) { diff --git a/src/cpu.js b/src/cpu.js index 5ba8457049..bd37feee3c 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(); @@ -984,6 +990,11 @@ CPU.prototype.init = function(settings, device_bus) this.devices.virtio_console = new VirtioConsole(this, device_bus); } + if(settings.virtio_net) + { + this.devices.virtio_net = new VirtioNet(this, device_bus, settings.preserve_mac_from_state_image); + } + if(true) { this.devices.sb16 = new SB16(this, device_bus); diff --git a/src/virtio.js b/src/virtio.js index fbc85f2752..3c384ca75c 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..c19aca4fae --- /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 = 1; + 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 bufchain = queue.pop_request(); + 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]; + + + console.log("Control Event", xclass, command, buffer); + //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(); +}; +