diff --git a/arch/wasm/drivers/virtio_wasm.c b/arch/wasm/drivers/virtio_wasm.c index 7e2124a9e6b95f..ccfc86209a3722 100644 --- a/arch/wasm/drivers/virtio_wasm.c +++ b/arch/wasm/drivers/virtio_wasm.c @@ -16,14 +16,18 @@ void wasm_import(virtio, get_config)(u32 id, unsigned offset, void *buf, unsigned len); void wasm_import(virtio, set_config)(u32 id, unsigned offset, const void *buf, unsigned len); + int wasm_import(virtio, get_features)(u32 id, u64 *features); int wasm_import(virtio, set_features)(u32 id, u64 features); -int wasm_import(virtio, set_vring_enable)(u32 id, u32 index, bool enable); -int wasm_import(virtio, set_vring_num)(u32 id, u32 index, u32 num); -int wasm_import(virtio, set_vring_addr)(u32 id, u32 index, dma_addr_t desc, - dma_addr_t used, dma_addr_t avail); + int wasm_import(virtio, configure_interrupt)(u32 id, u32 irq, bool *is_config, - bool *is_vring); + bool *is_vring); + +int wasm_import(virtio, enable_vring)(u32 id, u32 index, u32 size, + dma_addr_t desc, dma_addr_t used, + dma_addr_t avail); +int wasm_import(virtio, disable_vring)(u32 id, u32 index); + int wasm_import(virtio, notify)(u32 id, u32 index); #define to_virtio_wasm_device(_plat_dev) \ @@ -104,8 +108,7 @@ static void vw_del_vqs(struct virtio_device *vdev) list_for_each_entry_safe(vq, n, &vdev->vqs, list) { vring_del_virtqueue(vq); - WARN(wasm_virtio_set_vring_enable(vw_dev->host_id, vq->index, - false) < 0, + WARN(wasm_virtio_disable_vring(vw_dev->host_id, vq->index) < 0, "failed to disable vq %i[%i]\n", vw_dev->host_id, vq->index); } @@ -153,18 +156,10 @@ static struct virtqueue *vw_setup_vq(struct virtio_device *vdev, unsigned index, vq->num_max = num; num = virtqueue_get_vring_size(vq); - rc = wasm_virtio_set_vring_num(vw_dev->host_id, vq->index, num); - if (rc) - goto error; - - rc = wasm_virtio_set_vring_addr(vw_dev->host_id, vq->index, - virtqueue_get_desc_addr(vq), - virtqueue_get_used_addr(vq), - virtqueue_get_avail_addr(vq)); - if (rc) - goto error; - - rc = wasm_virtio_set_vring_enable(vw_dev->host_id, vq->index, true); + rc = wasm_virtio_enable_vring(vw_dev->host_id, vq->index, num, + virtqueue_get_desc_addr(vq), + virtqueue_get_used_addr(vq), + virtqueue_get_avail_addr(vq)); if (rc) goto error; diff --git a/tools/wasm/src/bytes.ts b/tools/wasm/src/bytes.ts index 7616fa3a61694d..5cbca5ffdb1f44 100644 --- a/tools/wasm/src/bytes.ts +++ b/tools/wasm/src/bytes.ts @@ -8,31 +8,33 @@ export type Unwrap = T extends Type ? U : never; export function Struct( layout: { [K in keyof T]: Type }, ) { + let size = 0; + const TheStruct = class { - _dv: DataView; + #dv: DataView; constructor(dv: DataView) { - this._dv = dv; + this.#dv = dv; + } + static { + for ( + const [key, type] of Object.entries( + layout as Record>, + ) + ) { + const offset = size; + Object.defineProperty(this.prototype, key, { + get() { + return type.get(this.#dv, offset); + }, + set(value) { + type.set(this.#dv, offset, value); + }, + }); + size += type.size; + } } } as { new (dv: DataView): T }; - let size = 0; - for ( - const [key, type] of Object.entries( - layout as Record>, - ) - ) { - const offset = size; - Object.defineProperty(TheStruct.prototype, key, { - get() { - return type.get(this._dv, offset); - }, - set(value) { - type.set(this._dv, offset, value); - }, - }); - size += type.size; - } - const type: Type = { get(dv, offset) { if (offset !== 0) dv = new DataView(dv.buffer, dv.byteOffset + offset); @@ -48,6 +50,27 @@ export function Struct( return Object.assign(TheStruct, type); } +export function FixedArray( + { get, set, size }: Type, + length: number, +): Type { + return { + get(dv, offset) { + const arr = Array(length); + for (let i = 0; i < length; i++) { + arr[i] = get(dv, offset + size * i); + } + return arr; + }, + set(dv, offset, value) { + for (let i = 0; i < length; i++) { + set(dv, offset + size * i, value[i]!); + } + }, + size: size * length, + }; +} + export const U8: Type = { get(dv, offset) { return dv.getUint8(offset); diff --git a/tools/wasm/src/index.ts b/tools/wasm/src/index.ts index 446d3d732fd3cc..180ad4cdaadcfc 100644 --- a/tools/wasm/src/index.ts +++ b/tools/wasm/src/index.ts @@ -143,9 +143,8 @@ export class Machine extends EventEmitter<{ set_config: unavailable, get_features: unavailable, set_features: unavailable, - set_vring_enable: unavailable, - set_vring_num: unavailable, - set_vring_addr: unavailable, + enable_vring: unavailable, + disable_vring: unavailable, configure_interrupt: unavailable, notify: unavailable, }, diff --git a/tools/wasm/src/wasm.ts b/tools/wasm/src/wasm.ts index b5eed1c0c40979..157c32619e2776 100644 --- a/tools/wasm/src/wasm.ts +++ b/tools/wasm/src/wasm.ts @@ -35,23 +35,27 @@ export interface Imports { buf_addr: number, buf_len: number, ): void; + get_features(dev: number, features_addr: number): number; set_features(dev: number, features: bigint): number; - set_vring_enable(dev: number, vq: number, enable: number): number; - set_vring_num(dev: number, vq: number, num: number): number; - set_vring_addr( - dev: number, - vq: number, - desc: number, - used: number, - avail: number, - ): number; + configure_interrupt( dev: number, irq: number, is_config_addr: number, is_vring_addr: number, ): number; + + enable_vring( + dev: number, + vq: number, + size: number, + desc_addr: number, + used_addr: number, + avail_addr: number, + ): number; + disable_vring(dev: number, vq: number): number; + notify(dev: number, vq: number): number; }; } diff --git a/tools/wasm/src/worker.ts b/tools/wasm/src/worker.ts index bac118812891f6..6592697331f442 100644 --- a/tools/wasm/src/worker.ts +++ b/tools/wasm/src/worker.ts @@ -1,4 +1,12 @@ -import { Struct, U16LE, U32LE, U64LE, U8 } from "./bytes.ts"; +import { + FixedArray, + Struct, + U16LE, + U32LE, + U64LE, + U8, + type Unwrap, +} from "./bytes.ts"; import { type Imports, type Instance, kernel_imports } from "./wasm.ts"; export interface InitMessage { @@ -13,88 +21,176 @@ export type WorkerMessage = | { type: "boot_console_close" }; const EINVAL = 22; + const VIRTIO_F_VERSION_1 = 1n << 32n; const VIRTIO_F_RING_PACKED = 1n << 34n; const VIRTIO_F_EVENT_IDX = 1n << 29n; const VIRTIO_F_INDIRECT_DESC = 1n << 28n; -// With current transports, virtqueues are located in guest memory allocated by the driver. -// Each packed virtqueue consists of three parts: - -// Descriptor Ring - occupies the Descriptor Area - -// Where the Descriptor Ring in turn consists of descriptors, and where each descriptor can contain the following parts: - -// Buffer ID -// Element Address -// Element Length -// Flags - -// A buffer consists of zero or more device-readable physically-contiguous elements followed by zero or more -// physically-contiguous device-writable elements (each buffer has at least one element). - -// When the driver wants to send such a buffer to the device, it writes at least one available descriptor -// describing elements of the buffer into the Descriptor Ring. The descriptor(s) are associated with a -// buffer by means of a Buffer ID stored within the descriptor. - -// The driver then notifies the device. When the device has finished processing the buffer, -// it writes a used device descriptor including the Buffer ID into the Descriptor Ring -// (overwriting a driver descriptor previously made available), and sends a used event notification. - -// The Descriptor Ring is used in a circular manner: the driver writes descriptors into the ring in order. -// After reaching the end of the ring, the next descriptor is placed at the head of the ring. -// Once the ring is full of driver descriptors, the driver stops sending new requests and waits for the device -// to start processing descriptors and to write out some used descriptors before making new driver descriptors available. - -// Similarly, the device reads descriptors from the ring in order and detects that a driver descriptor has been made available. -// As processing of descriptors is completed, used descriptors are written by the device back into the ring. - -const Descriptor = Struct({ +const VIRTQ_DESC_F_NEXT = 1 << 0; +const VIRTQ_DESC_F_WRITE = 1 << 1; +const VIRTQ_DESC_F_INDIRECT = 1 << 2; +const VIRTQ_DESC_F_AVAIL = 1 << 7; +const VIRTQ_DESC_F_USED = 1 << 15; +// const VirtqDescriptor = Struct({ +// addr: U64LE, +// len: U32LE, +// flags: U16LE, +// next: U16LE, +// }); +// type VirtqDescriptor = Unwrap; + +const VirtqDescriptor = Struct({ addr: U64LE, len: U32LE, id: U16LE, flags: U16LE, }); +type VirtqDescriptor = Unwrap; + +const VIRTQ_AVAIL_F_NO_INTERRUPT = 1; +const VirtqAvail = (size: number) => + Struct({ + flags: U16LE, + idx: U16LE, + ring: FixedArray(U16LE, size), + used_event: U16LE, // only if VIRTIO_F_EVENT_IDX + }); +type VirtqAvail = Unwrap>; + +const VirtqUsedElem = Struct({ + id: U32LE, + len: U32LE, +}); +const VIRTQ_USED_F_NO_NOTIFY = 1; +const VirtqUsed = (size: number) => + Struct({ + flags: U16LE, + idx: U16LE, + ring: FixedArray(VirtqUsedElem, size), + avail_event: U16LE, // only if VIRTIO_F_EVENT_IDX + }); +type VirtqUsed = Unwrap>; class Virtqueue { - device: VirtioDevice; - size = 0; - desc_addr = 0; - used_addr = 0; - avail_addr = 0; - - constructor(device: VirtioDevice) { - this.device = device; + #dv: DataView; + + // ring stuff: + size: number; + desc: VirtqDescriptor[]; + used: VirtqUsed; + avail: VirtqAvail; + + // queue stuff: + count_used = 0; + used_wrap_count = true; + last_avail = 0; + + constructor( + dv: DataView, + size: number, + desc_addr: number, + used_addr: number, + avail_addr: number, + ) { + this.#dv = dv; + this.size = size; + this.desc = FixedArray(VirtqDescriptor, size).get(dv, desc_addr); + this.used = VirtqUsed(size).get(dv, used_addr); + this.avail = VirtqAvail(size).get(dv, avail_addr); } - enable() { - console.log("enable", this); - } - disable() { - throw new Error("not implemented"); - } - notify() { - console.log("notify", this); + take() { + if (this.count_used >= this.size) { + throw new Error("Virtqueue size exceeded"); + } + + const last_seen = this.desc[this.last_avail]!; + console.log("flags:", last_seen.flags.toString(16)); + let id; + let count = 0; + const descs = []; + if (last_seen.flags & VIRTQ_DESC_F_INDIRECT) { + ({ id } = last_seen); + const max = last_seen.len / VirtqDescriptor.size; + for (let i = 0; i < max; i++) descs.push(this.desc[i]!); + count = 1; + } else { + for ( + let i = 0; + this.desc[i] !== undefined && this.desc[i]!.flags & VIRTQ_DESC_F_NEXT; + i = (i + 1) % this.size + ) { + if (++count > this.size) throw new Error("looped"); + descs.push(this.desc[i]!); + } + if (descs.length) ({ id } = descs.at(-1)!); + } + + const readable = []; + const writable = []; + for (const desc of descs) { + if (desc.flags & VIRTQ_DESC_F_WRITE) writable.push(desc); + else readable.push(desc); + } + + this.count_used += count; + this.last_avail += count; + if (this.last_avail > this.size) { + this.last_avail -= this.size; + this.used_wrap_count = !this.used_wrap_count; + } + + return { id, count, readable, writable }; } } abstract class VirtioDevice { - abstract vqs: Virtqueue[]; + readonly nvqs: number; + constructor(nvqs: number) { + this.nvqs = nvqs; + } + config = new Uint8Array(0); features = VIRTIO_F_VERSION_1 | VIRTIO_F_RING_PACKED | VIRTIO_F_EVENT_IDX | VIRTIO_F_INDIRECT_DESC; - trigger_interrupt = (is_config: boolean, is_vring: boolean): void => { + trigger_interrupt = (kind: "config" | "vring"): void => { // this function is overwritten on device setup - void is_config, is_vring; + void kind; throw new Error("unreachable"); }; + + abstract enable(vq: number, queue: Virtqueue): void; + abstract disable(vq: number): void; + abstract notify(vq: number): void; } class EntropyDevice extends VirtioDevice { - requestq: Virtqueue = new Virtqueue(this); - vqs = [this.requestq]; + constructor() { + super(1); + } + + vqs: Virtqueue[] = []; + enable(vq: number, queue: Virtqueue) { + this.vqs[vq] = queue; + console.log("enable", vq, queue); + } + disable(vq: number) { + // const queue = this.vqs[vq]; + console.log("disable", vq); + } + notify(vq: number) { + const queue = this.vqs[vq]!; + console.log("notify", vq, queue); + + const elem = queue.take(); + console.log(elem); + // TODO fill with bytes, enqueue buffer + + this.trigger_interrupt("vring"); + } } const devices: VirtioDevice[] = [new EntropyDevice()]; @@ -167,6 +263,7 @@ self.onmessage = (event: MessageEvent) => { ); device.config.set(buf, offset); }, + get_features(dev, features_addr) { const device = devices[dev]; if (!device) return -EINVAL; @@ -179,42 +276,29 @@ self.onmessage = (event: MessageEvent) => { device.features = features; return 0; }, - set_vring_enable(dev, vq, enable) { - const device = devices[dev]; - if (!device) return -EINVAL; - - const virtqueue = device.vqs[vq]; - if (!virtqueue) return -EINVAL; - if (enable) virtqueue.enable(); - else virtqueue.disable(); - - return 0; - }, - set_vring_num(dev, vq, num) { + enable_vring(dev, vq, size, desc_addr, used_addr, avail_addr) { const device = devices[dev]; if (!device) return -EINVAL; + if (vq >= device.nvqs) return -EINVAL; - const virtqueue = device.vqs[vq]; - if (!virtqueue) return -EINVAL; - - virtqueue.size = num; + device.enable( + vq, + new Virtqueue(dv, size, desc_addr, used_addr, avail_addr), + ); return 0; }, - set_vring_addr(dev, vq, desc, used, avail) { + disable_vring(dev, vq) { const device = devices[dev]; if (!device) return -EINVAL; + if (vq >= device.nvqs) return -EINVAL; - const virtqueue = device.vqs[vq]; - if (!virtqueue) return -EINVAL; - - virtqueue.desc_addr = desc; - virtqueue.used_addr = used; - virtqueue.avail_addr = avail; + device.disable(vq); return 0; }, + configure_interrupt( dev, irq, @@ -225,35 +309,30 @@ self.onmessage = (event: MessageEvent) => { if (!device) return -EINVAL; device.trigger_interrupt = ( - is_config, - is_vring, + kind, ) => { U8.set( dv, is_config_addr, - is_config ? 1 : 0, + kind === "config" ? 1 : 0, ); U8.set( dv, is_vring_addr, - is_vring ? 1 : 0, + kind === "vring" ? 1 : 0, ); - instance.exports.trigger_irq_for_cpu( - 0, - irq, - ); // TODO: balance? + instance.exports.trigger_irq_for_cpu(0, irq); // TODO: balance? }; return 0; }, + notify(dev, vq) { const device = devices[dev]; if (!device) return -EINVAL; + if (vq >= device.nvqs) return -EINVAL; - const virtqueue = device.vqs[vq]; - if (!virtqueue) return -EINVAL; - - virtqueue.notify(); + device.notify(vq); return 0; },