"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.bus.send("eth-transmit-end", [buffer.length - 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 => { this.bus.send("eth-receive-end", [data.length]); 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() { this.virtio.reset(); }; 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(); };