diff --git a/arch/wasm/configs/defconfig b/arch/wasm/configs/defconfig index 1740f6c0f309bc..1986855379a03d 100644 --- a/arch/wasm/configs/defconfig +++ b/arch/wasm/configs/defconfig @@ -20,7 +20,6 @@ CONFIG_CC_OPTIMIZE_FOR_SIZE=y CONFIG_EMBEDDED=y CONFIG_VIRTIO_WASM=y # CONFIG_COMPAT_32BIT_TIME is not set -# CONFIG_BLOCK is not set # CONFIG_BINFMT_SCRIPT is not set # CONFIG_COREDUMP is not set CONFIG_SLOB=y @@ -30,6 +29,7 @@ CONFIG_SLOB=y # CONFIG_PREVENT_FIRMWARE_BUILD is not set # CONFIG_FW_LOADER is not set # CONFIG_ALLOW_DEV_COREDUMP is not set +CONFIG_VIRTIO_BLK=y # CONFIG_INPUT is not set # CONFIG_SERIO is not set # CONFIG_TTY is not set diff --git a/arch/wasm/drivers/virtio_wasm.c b/arch/wasm/drivers/virtio_wasm.c index 4f9e6c1fed6fd2..7e2124a9e6b95f 100644 --- a/arch/wasm/drivers/virtio_wasm.c +++ b/arch/wasm/drivers/virtio_wasm.c @@ -1,3 +1,4 @@ +#define DEBUG #define pr_fmt(fmt) "virtio-wasm: " fmt #include @@ -11,13 +12,17 @@ #include #include +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, set_interrupt_addrs)(u32 id, bool *is_config, +int wasm_import(virtio, configure_interrupt)(u32 id, u32 irq, bool *is_config, bool *is_vring); int wasm_import(virtio, notify)(u32 id, u32 index); @@ -37,6 +42,20 @@ struct virtio_wasm_device { bool interrupt_is_vring; }; +static void vw_get(struct virtio_device *vdev, unsigned offset, void *buf, + unsigned len) +{ + struct virtio_wasm_device *vw_dev = to_virtio_wasm_device(vdev); + wasm_virtio_get_config(vw_dev->host_id, offset, buf, len); +} + +static void vw_set(struct virtio_device *vdev, unsigned offset, const void *buf, + unsigned len) +{ + struct virtio_wasm_device *vw_dev = to_virtio_wasm_device(vdev); + wasm_virtio_set_config(vw_dev->host_id, offset, buf, len); +} + static bool vw_notify(struct virtqueue *vq) { struct virtio_wasm_device *vw_dev = to_virtio_wasm_device(vq->vdev); @@ -51,12 +70,31 @@ static u8 vw_get_status(struct virtio_device *vdev) static void vw_set_status(struct virtio_device *vdev, u8 status) { struct virtio_wasm_device *vw_dev = to_virtio_wasm_device(vdev); - vw_dev->status = status; + if (vw_dev->status != status) { + const char *name = dev_name(&vdev->dev); + vw_dev->status = status; + switch (status) { + case VIRTIO_CONFIG_S_ACKNOWLEDGE: + pr_debug("device %s: acknowledge\n", name); + break; + case VIRTIO_CONFIG_S_DRIVER: + pr_debug("device %s: driver\n", name); + break; + case VIRTIO_CONFIG_S_FAILED: + pr_debug("device %s: failed\n", name); + break; + case VIRTIO_CONFIG_S_DRIVER_OK: + pr_debug("device %s: driver ok\n", name); + break; + case VIRTIO_CONFIG_S_FEATURES_OK: + pr_debug("device %s: features ok\n", name); + break; + } + } } static void vw_reset(struct virtio_device *vdev) { - struct virtio_wasm_device *vw_dev = to_virtio_wasm_device(vdev); - vw_dev->status = 0; + vw_set_status(vdev, 0); } static void vw_del_vqs(struct virtio_device *vdev) @@ -187,8 +225,8 @@ static const char *vw_bus_name(struct virtio_device *vdev) } static const struct virtio_config_ops virtio_wasm_config_ops = { - // .get = vw_get, - // .set = vw_set, + .get = vw_get, + .set = vw_set, .get_status = vw_get_status, .set_status = vw_set_status, .reset = vw_reset, @@ -242,7 +280,7 @@ static int virtio_wasm_probe(struct platform_device *pdev) if (rc) goto error; - rc = wasm_virtio_set_interrupt_addrs(vw_dev->host_id, + rc = wasm_virtio_configure_interrupt(vw_dev->host_id, vw_dev->irq, &vw_dev->interrupt_is_config, &vw_dev->interrupt_is_vring); if (rc) diff --git a/drivers/base/dd.c b/drivers/base/dd.c index dbbe2cebb89171..41ad6ace4fd902 100644 --- a/drivers/base/dd.c +++ b/drivers/base/dd.c @@ -1,4 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 +#define DEBUG /* * drivers/base/dd.c - The core device/driver interactions. * diff --git a/drivers/virtio/virtio_ring.c b/drivers/virtio/virtio_ring.c index 7d320f799ca1e0..21bc6da3e5ae6d 100644 --- a/drivers/virtio/virtio_ring.c +++ b/drivers/virtio/virtio_ring.c @@ -3,6 +3,9 @@ * * Copyright 2007 Rusty Russell IBM Corporation */ +#define DEBUG +#define pr_fmt(fmt) "vring: " fmt + #include #include #include diff --git a/tools/wasm/src/index.ts b/tools/wasm/src/index.ts index ff7998075e9769..446d3d732fd3cc 100644 --- a/tools/wasm/src/index.ts +++ b/tools/wasm/src/index.ts @@ -71,9 +71,14 @@ export class Machine extends EventEmitter<{ }, rng: { compatible: "virtio,wasm", - "host-id": 0x1234, + "host-id": 0, "virtio-device-id": 4, // entropy }, + // blk: { + // compatible: "virtio,wasm", + // "host-id": 1, + // "virtio-device-id": 2, // block + // }, }; } @@ -134,12 +139,14 @@ export class Machine extends EventEmitter<{ boot_console_close, }), virtio: { + get_config: unavailable, + set_config: unavailable, get_features: unavailable, set_features: unavailable, set_vring_enable: unavailable, set_vring_num: unavailable, set_vring_addr: unavailable, - set_interrupt_addrs: unavailable, + configure_interrupt: unavailable, notify: unavailable, }, } satisfies Imports; diff --git a/tools/wasm/src/wasm.ts b/tools/wasm/src/wasm.ts index 135d30f690ece1..b5eed1c0c40979 100644 --- a/tools/wasm/src/wasm.ts +++ b/tools/wasm/src/wasm.ts @@ -23,8 +23,20 @@ export interface Imports { spawn_worker(fn: number, arg: number, comm: number, commLen: number): void; }; virtio: { + get_config( + dev: number, + offset: number, + buf_addr: number, + buf_len: number, + ): void; + set_config( + dev: number, + offset: number, + buf_addr: number, + buf_len: number, + ): void; get_features(dev: number, features_addr: number): number; - set_features(dev: number, features: 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( @@ -34,8 +46,9 @@ export interface Imports { used: number, avail: number, ): number; - set_interrupt_addrs( + configure_interrupt( dev: number, + irq: number, is_config_addr: number, is_vring_addr: number, ): number; diff --git a/tools/wasm/src/worker.ts b/tools/wasm/src/worker.ts index a913498b79e281..bac118812891f6 100644 --- a/tools/wasm/src/worker.ts +++ b/tools/wasm/src/worker.ts @@ -1,3 +1,4 @@ +import { Struct, U16LE, U32LE, U64LE, U8 } from "./bytes.ts"; import { type Imports, type Instance, kernel_imports } from "./wasm.ts"; export interface InitMessage { @@ -11,6 +12,93 @@ export type WorkerMessage = | { type: "boot_console_write"; message: ArrayBuffer } | { 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({ + addr: U64LE, + len: U32LE, + id: U16LE, + flags: U16LE, +}); + +class Virtqueue { + device: VirtioDevice; + size = 0; + desc_addr = 0; + used_addr = 0; + avail_addr = 0; + + constructor(device: VirtioDevice) { + this.device = device; + } + + enable() { + console.log("enable", this); + } + disable() { + throw new Error("not implemented"); + } + notify() { + console.log("notify", this); + } +} + +abstract class VirtioDevice { + abstract vqs: Virtqueue[]; + 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 => { + // this function is overwritten on device setup + void is_config, is_vring; + throw new Error("unreachable"); + }; +} + +class EntropyDevice extends VirtioDevice { + requestq: Virtqueue = new Virtqueue(this); + vqs = [this.requestq]; +} + +const devices: VirtioDevice[] = [new EntropyDevice()]; + self.onmessage = (event: MessageEvent) => { const { fn, arg, vmlinux, memory } = event.data; const dv = new DataView(memory.buffer); @@ -19,7 +107,9 @@ self.onmessage = (event: MessageEvent) => { env: { memory }, boot: { get_devicetree(_buf, _size) { - throw new Error("get_devicetree on worker thread"); + throw new Error( + "get_devicetree on worker thread", + ); }, }, kernel: kernel_imports({ @@ -27,48 +117,144 @@ self.onmessage = (event: MessageEvent) => { memory, spawn_worker(fn, arg, name) { self.postMessage( - { type: "spawn_worker", fn, arg, name } satisfies WorkerMessage, + { + type: "spawn_worker", + fn, + arg, + name, + } satisfies WorkerMessage, ); }, boot_console_write(message) { self.postMessage( - { type: "boot_console_write", message } satisfies WorkerMessage, + { + type: "boot_console_write", + message, + } satisfies WorkerMessage, ); }, boot_console_close() { self.postMessage( - { type: "boot_console_close" } satisfies WorkerMessage, + { + type: "boot_console_close", + } satisfies WorkerMessage, ); }, }), virtio: { + get_config(dev, offset, buf_addr, buf_len) { + const device = devices[dev]; + if (!device) return -EINVAL; + + const buf = new Uint8Array( + memory.buffer, + buf_addr, + buf_len, + ); + buf.set(device.config.subarray( + offset, + offset + buf_len, + )); + }, + set_config(dev, offset, buf_addr, buf_len) { + const device = devices[dev]; + if (!device) return -EINVAL; + + const buf = new Uint8Array( + memory.buffer, + buf_addr, + buf_len, + ); + device.config.set(buf, offset); + }, get_features(dev, features_addr) { - console.log("get_features", dev, features_addr); - dv.setBigUint64(features_addr, 0n); + const device = devices[dev]; + if (!device) return -EINVAL; + U64LE.set(dv, features_addr, device.features); return 0; }, set_features(dev, features) { - console.log("set_features", dev, features); + const device = devices[dev]; + if (!device) return -EINVAL; + device.features = features; return 0; }, set_vring_enable(dev, vq, enable) { - console.log("set_vring_enable", dev, vq, enable); - return -1; + 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) { - console.log("set_vring_num", dev, vq, num); - return -1; + const device = devices[dev]; + if (!device) return -EINVAL; + + const virtqueue = device.vqs[vq]; + if (!virtqueue) return -EINVAL; + + virtqueue.size = num; + + return 0; }, set_vring_addr(dev, vq, desc, used, avail) { - console.log("set_vring_addr", dev, vq, desc, used, avail); - return -1; + const device = devices[dev]; + if (!device) return -EINVAL; + + const virtqueue = device.vqs[vq]; + if (!virtqueue) return -EINVAL; + + virtqueue.desc_addr = desc; + virtqueue.used_addr = used; + virtqueue.avail_addr = avail; + + return 0; }, - set_interrupt_addrs(dev, is_config_addr, is_vring_addr) { - console.log("set_interrupt_addrs", dev, is_config_addr, is_vring_addr); + configure_interrupt( + dev, + irq, + is_config_addr, + is_vring_addr, + ) { + const device = devices[dev]; + if (!device) return -EINVAL; + + device.trigger_interrupt = ( + is_config, + is_vring, + ) => { + U8.set( + dv, + is_config_addr, + is_config ? 1 : 0, + ); + U8.set( + dv, + is_vring_addr, + is_vring ? 1 : 0, + ); + instance.exports.trigger_irq_for_cpu( + 0, + irq, + ); // TODO: balance? + }; + return 0; }, notify(dev, vq) { - console.log("notify", dev, vq); + const device = devices[dev]; + if (!device) return -EINVAL; + + const virtqueue = device.vqs[vq]; + if (!virtqueue) return -EINVAL; + + virtqueue.notify(); + return 0; }, },