diff --git a/build-la.sh b/build-la.sh new file mode 100755 index 0000000..e80b25a --- /dev/null +++ b/build-la.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Build hvisor-tool for LoongArch64. +# Usage: bash build-la.sh [extra make args...] + +set -e + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +KDIR=${1:-/media/boneinscri/Data/linux-v6.19/linux} +shift 2>/dev/null || true + +if [ ! -d "$KDIR" ]; then + echo "Error: kernel directory not found: $KDIR" + exit 1 +fi + +if [ ! -f "$KDIR/Makefile" ]; then + echo "Error: $KDIR does not look like a kernel source tree (no Makefile)" + exit 1 +fi + +echo "Building hvisor-tool for loongarch64" +echo " KDIR = $KDIR" + +make -C "$SCRIPT_DIR" \ + ARCH=loongarch \ + KDIR="$KDIR" \ + CROSS_COMPILE=loongarch64-linux-gnu- \ + LOG=LOG_INFO \ + "$@" diff --git a/driver/Makefile b/driver/Makefile index 30dcb3b..4269f5e 100644 --- a/driver/Makefile +++ b/driver/Makefile @@ -12,7 +12,7 @@ ifndef CROSS_COMPILE else ifeq ($(ARCH), riscv) CROSS_COMPILE := riscv64-unknown-linux-gnu- else ifeq ($(ARCH), loongarch) - CROSS_COMPILE := loongarch64-unknown-linux-gnu- + CROSS_COMPILE := loongarch64-linux-gnu- else ifeq ($(ARCH), x86_64) CROSS_COMPILE := x86_64-linux-gnu- endif diff --git a/driver/hvisor.c b/driver/hvisor.c index 245c23d..93a0968 100644 --- a/driver/hvisor.c +++ b/driver/hvisor.c @@ -11,16 +11,23 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include #include +#ifdef LOONGARCH64 +#include +#include +#include +#endif #include #include #include @@ -31,18 +38,83 @@ #include "hvisor.h" #include "zone_config.h" +/* MAX_ORDER was renamed to MAX_PAGE_ORDER in kernel 6.3, + * and its semantics changed in 6.1: before 6.1 MAX_ORDER was the number + * of orders (max valid order = MAX_ORDER-1), from 6.1 onwards it is the + * max valid order itself. */ +#ifndef MAX_PAGE_ORDER +/* old kernel: MAX_ORDER is count, subtract 1 to get max valid order */ +#define MAX_PAGE_ORDER (MAX_ORDER - 1) +#endif + struct virtio_bridge *virtio_bridge; int virtio_irq = -1; static struct task_struct *task = NULL; struct eventfd_ctx *virtio_irq_ctx = NULL; +#ifdef LOONGARCH64 +/* per-CPU cookie required by request_percpu_irq for the IPI line */ +static DEFINE_PER_CPU(int, hvisor_percpu_dev); +static bool hvisor_irq_is_percpu = false; + +/* kthread for polling virtio_bridge->need_wakeup when no usable IRQ exists. + * Hypervisor sets need_wakeup != 0 to notify root zone; we clear it and + * signal the userspace virtio daemon via eventfd. + * Uses a waitqueue so the thread sleeps properly instead of busy-looping. */ +static struct task_struct *hvisor_poll_thread = NULL; +static DECLARE_WAIT_QUEUE_HEAD(hvisor_poll_wq); +static atomic_t hvisor_poll_pending = ATOMIC_INIT(0); + +/* hrtimer fires every 1ms to wake the poll thread */ +static struct hrtimer hvisor_poll_timer; + +static enum hrtimer_restart hvisor_poll_timer_fn(struct hrtimer *timer) { + atomic_set(&hvisor_poll_pending, 1); + wake_up(&hvisor_poll_wq); + hrtimer_forward_now(timer, ms_to_ktime(1)); + return HRTIMER_RESTART; +} + +static int hvisor_poll_fn(void *unused) { + while (!kthread_should_stop()) { + /* Sleep until someone calls wake_up(&hvisor_poll_wq) or the thread + * is asked to stop. */ + wait_event_interruptible(hvisor_poll_wq, + atomic_read(&hvisor_poll_pending) || + kthread_should_stop()); + + if (kthread_should_stop()) + break; + + atomic_set(&hvisor_poll_pending, 0); + + if (virtio_bridge && READ_ONCE(virtio_bridge->need_wakeup)) { + WRITE_ONCE(virtio_bridge->need_wakeup, 0); + if (virtio_irq_ctx) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0) + eventfd_signal(virtio_irq_ctx); +#else + eventfd_signal(virtio_irq_ctx, 1); +#endif + } + } + } + return 0; +} +#endif // initial virtio el2 shared region static int hvisor_init_virtio(void) { int err; + +#ifdef LOONGARCH64 +// do nothing +#elif if (virtio_irq == -1) { pr_err("virtio device is not available\n"); return ENOTTY; } +#endif + virtio_bridge = (struct virtio_bridge *)__get_free_pages(GFP_KERNEL, 0); if (virtio_bridge == NULL) return -ENOMEM; @@ -52,6 +124,14 @@ static int hvisor_init_virtio(void) { err = hvisor_call(HVISOR_HC_INIT_VIRTIO, __pa(virtio_bridge), 0); if (err) return err; +#ifdef LOONGARCH64 + /* Start the polling thread after virtio_bridge is ready */ + if (hvisor_poll_thread) { + wake_up_process(hvisor_poll_thread); + /* Start the 1ms hrtimer that periodically wakes the poll thread */ + hrtimer_start(&hvisor_poll_timer, ms_to_ktime(1), HRTIMER_MODE_REL); + } +#endif return 0; } @@ -230,16 +310,146 @@ static int hvisor_zone_list(zone_list_args_t __user *arg) { pr_err("hvisor.ko: failed to get zone list\n"); goto out; } + // copy result back to user space if (copy_to_user(args.zones, zones, ret * sizeof(zone_info_t))) { pr_err("hvisor.ko: failed to copy to user\n"); goto out; } + out: kfree(zones); return ret; } +#ifdef LOONGARCH64 +/* Track all pages allocated via hvisor_m_alloc so they can be freed + * automatically when the daemon exits (file release). */ +struct hvisor_alloc_entry { + unsigned long vaddr; + unsigned int order; + struct file *owner; /* the fd that allocated this block */ + struct list_head list; +}; +static LIST_HEAD(hvisor_alloc_list); +static DEFINE_SPINLOCK(hvisor_alloc_lock); + +// order <= MAX_ORDER-1, size <= (PAGE_SIZE << (MAX_ORDER-1)) +// actual max depends on kernel buddy system configuration (MAX_ORDER) +static unsigned long hvisor_m_alloc(struct file *file, + kmalloc_info_t __user *arg) { + kmalloc_info_t kmalloc_info; + + if (copy_from_user(&kmalloc_info, arg, sizeof(kmalloc_info))) { + pr_err("hvisor: failed to copy from user\n"); + return -EFAULT; + } + + if (kmalloc_info.size == 0) { + pr_err("hvisor: invalid allocation size 0\n"); + return -EINVAL; + } + + __u64 reduced_size = kmalloc_info.size; + + void *area; + /* Use ilog2 (floor to power-of-2) instead of get_order (ceil), + * then convert size -> order by subtracting PAGE_SHIFT. + * This avoids over-allocating when size is not a power-of-2. + * e.g. 0x2f000000 (752MB): get_order gives order for 1GB (wasteful), + * ilog2 gives order for 512MB (exact largest fitting block). */ + unsigned int order = min_t( + unsigned int, + ilog2(reduced_size) > PAGE_SHIFT ? ilog2(reduced_size) - PAGE_SHIFT : 0, + MAX_PAGE_ORDER); + + // try allocate from big area to small area + area = (void *)__get_free_pages(GFP_KERNEL, order); + while (area == NULL) { + if (order == 0) { + pr_err("hvisor: failed to allocate memory, size %llx\n", + kmalloc_info.size); + return -ENOMEM; + } + order--; + area = (void *)__get_free_pages(GFP_KERNEL, order); + } + + reduced_size = PAGE_SIZE << order; + + SetPageReserved(virt_to_page(area)); + memset(area, 0, reduced_size); + + /* Record this allocation for automatic cleanup on release */ + struct hvisor_alloc_entry *entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (entry) { + entry->vaddr = (unsigned long)area; + entry->order = order; + entry->owner = file; + spin_lock(&hvisor_alloc_lock); + list_add(&entry->list, &hvisor_alloc_list); + spin_unlock(&hvisor_alloc_lock); + } + + if (reduced_size < kmalloc_info.size) { + kmalloc_info.size -= reduced_size; + } else { + kmalloc_info.size = 0; + } + + kmalloc_info.pa = __pa(area); + + // copy result back to user space + if (copy_to_user(arg, &kmalloc_info, sizeof(kmalloc_info))) { + pr_err("hvisor: failed to copy to user\n"); + ClearPageReserved(virt_to_page(area)); + free_pages((unsigned long)area, order); + return -EFAULT; + } + + // pr_info("allocate memory: reduced_size %llx, order %u, area %px, size + // %llx, pa : %llx\n", + // reduced_size, order, area, kmalloc_info.size, __pa(area)); + + return 0; +} +static int hvisor_m_free(kmalloc_info_t __user *arg) { + // TODO: check this for Memory Region of Non root Zone! + kmalloc_info_t kmalloc_info; + + if (copy_from_user(&kmalloc_info, arg, sizeof(kmalloc_info))) { + pr_err("hvisor: failed to copy from user\n"); + return -EFAULT; + } + + void *area = (void *)__va(kmalloc_info.pa); + unsigned int order = get_order(kmalloc_info.size); + + /* Remove from tracking list */ + struct hvisor_alloc_entry *entry, *tmp; + spin_lock(&hvisor_alloc_lock); + list_for_each_entry_safe(entry, tmp, &hvisor_alloc_list, list) { + if (entry->vaddr == (unsigned long)area) { + order = entry->order; + list_del(&entry->list); + kfree(entry); + break; + } + } + spin_unlock(&hvisor_alloc_lock); + + // Clear the PageReserved bit + ClearPageReserved(virt_to_page(area)); + + // Free the allocated pages + free_pages((unsigned long)area, order); + + // pr_info("freed memory: area %px, size %llx, pa : %llx\n", + // area, kmalloc_info.size, kmalloc_info.pa); + return 0; +} +#endif + static long hvisor_ioctl(struct file *file, unsigned int ioctl, unsigned long arg) { int err = 0; @@ -281,6 +491,14 @@ static long hvisor_ioctl(struct file *file, unsigned int ioctl, case HVISOR_CLEAR_INJECT_IRQ: err = hvisor_call(HVISOR_HC_CLEAR_INJECT_IRQ, 0, 0); break; + // for dynamic memory allocation from Heap of Root Linux + // --boneinscri 2026.04 + case HVISOR_ZONE_M_ALLOC: + err = hvisor_m_alloc(file, (kmalloc_info_t __user *)arg); + break; + case HVISOR_ZONE_M_FREE: + err = hvisor_m_free((kmalloc_info_t __user *)arg); + break; #endif default: err = -EINVAL; @@ -321,11 +539,41 @@ static int hvisor_map(struct file *filp, struct vm_area_struct *vma) { return 0; } +#ifdef LOONGARCH64 +/* Called when the last file descriptor to /dev/hvisor is closed (daemon exit). + * Frees all pages that were allocated via hvisor_m_alloc but never freed. */ +static int hvisor_release(struct inode *inode, struct file *file) { + struct hvisor_alloc_entry *entry, *tmp; + LIST_HEAD(to_free); + + spin_lock(&hvisor_alloc_lock); + list_for_each_entry_safe(entry, tmp, &hvisor_alloc_list, list) { + if (entry->owner == file) { + list_move(&entry->list, &to_free); + } + } + spin_unlock(&hvisor_alloc_lock); + + list_for_each_entry_safe(entry, tmp, &to_free, list) { + pr_info("hvisor: release cleanup: freeing vaddr %lx order %u\n", + entry->vaddr, entry->order); + ClearPageReserved(virt_to_page((void *)entry->vaddr)); + free_pages(entry->vaddr, entry->order); + list_del(&entry->list); + kfree(entry); + } + return 0; +} +#endif + static const struct file_operations hvisor_fops = { .owner = THIS_MODULE, .unlocked_ioctl = hvisor_ioctl, .compat_ioctl = hvisor_ioctl, .mmap = hvisor_map, +#ifdef LOONGARCH64 + .release = hvisor_release, +#endif }; static struct miscdevice hvisor_misc_dev = { @@ -385,24 +633,120 @@ static int __init hvisor_init(void) { // The irq number must be retrieved from dtb node, because it is different // from GIC's IRQ number. node = of_find_node_by_path("/hvisor_virtio_device"); +#ifdef LOONGARCH64 if (!node) { - pr_err("Critical: Missing device tree node!\n"); - pr_err(" Please add the following to your device tree:\n"); - pr_err(" hvisor_virtio_device {\n"); - pr_err(" compatible = \"hvisor\";\n"); - pr_err(" interrupts = <0x00 0x20 0x01>;\n"); - pr_err(" };\n"); - return -ENODEV; - } + // LoongArch64 booting via ACPI (no DTB). + // Strategy: try to map SWI0 (hwirq 0) on the CPUINTC domain and + // register a per-CPU IRQ handler. Hypervisor triggers SWI0 by + // writing ESTAT.SIP0 (bit 0) in the guest CSR. + // + // The CPUINTC domain is created with irq_domain_alloc_named_fwnode( + // "CPUINTC") so we find it via irq_find_matching_fwnode(). + // loongarch_cpu_intc_map() has no hwirq range restriction, so + // hwirq 0 (SWI0) and hwirq 1 (SWI1) are fully supported. + // init_IRQ() already enables ECFGF_SIP0 in CSR.ECFG, so the + // interrupt line is live as soon as we register a handler. + struct fwnode_handle *fwnode; + struct irq_domain *cpuintc_domain = NULL; + + fwnode = irq_domain_alloc_named_fwnode("CPUINTC"); + if (fwnode) { + cpuintc_domain = irq_find_matching_fwnode(fwnode, DOMAIN_BUS_ANY); + irq_domain_free_fwnode(fwnode); + } - virtio_irq = of_irq_get(node, 0); - err = request_irq(virtio_irq, virtio_irq_handler, - IRQF_SHARED | IRQF_TRIGGER_RISING, "hvisor_virtio_device", - &hvisor_misc_dev); - if (err) - goto err_out; + if (!cpuintc_domain) { + // Fallback: scan existing mappings to find the CPUINTC domain + int i; + for (i = 1; i < NR_IRQS; i++) { + struct irq_data *d = irq_get_irq_data(i); + if (!d || !d->domain || !d->domain->fwnode) + continue; + if (strstr(fwnode_get_name(d->domain->fwnode), "CPUINTC")) { + cpuintc_domain = d->domain; + break; + } + } + } + + if (cpuintc_domain) { + virtio_irq = irq_create_mapping(cpuintc_domain, 0); + pr_info("hvisor: SWI0 mapped to Linux IRQ %d\n", virtio_irq); + if (virtio_irq > 0) { + irq_set_handler(virtio_irq, handle_simple_irq); + err = request_irq(virtio_irq, virtio_irq_handler, 0, + "hvisor_virtio_device", &hvisor_misc_dev); + if (!err) { + pr_info("hvisor: ACPI boot, using SWI0 Linux IRQ %d\n", + virtio_irq); + goto init_done; + } + pr_warn("hvisor: request_irq for SWI0 failed (%d), " + "falling back to kthread poll\n", + err); + virtio_irq = -1; + } + } + of_node_put(node); + goto init_done; + // Fallback: kthread + hrtimer polling when IRQ registration fails + // pr_warn("hvisor: cannot register SWI0 IRQ, using kthread + // poll\n"); hvisor_poll_thread = kthread_create(hvisor_poll_fn, + // NULL, + // "hvisor_poll"); + // if (IS_ERR(hvisor_poll_thread)) { + // pr_err("hvisor: failed to create poll thread: %ld\n", + // PTR_ERR(hvisor_poll_thread)); + // hvisor_poll_thread = NULL; + // misc_deregister(&hvisor_misc_dev); + // return -ENOMEM; + // } + // #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) + // hrtimer_setup(&hvisor_poll_timer, hvisor_poll_timer_fn, + // CLOCK_MONOTONIC, HRTIMER_MODE_REL); + // #else + // hrtimer_init(&hvisor_poll_timer, CLOCK_MONOTONIC, + // HRTIMER_MODE_REL); hvisor_poll_timer.function = + // hvisor_poll_timer_fn; + // #endif + // goto init_done; + } else +#endif /* LOONGARCH64 */ + if (!node) { + pr_err("Critical: Missing device tree node!\n"); + pr_err(" Please add the following to your device tree:\n"); + pr_err(" hvisor_virtio_device {\n"); + pr_err(" compatible = \"hvisor\";\n"); + pr_err(" interrupts = <0x00 0x20 0x01>;\n"); + pr_err(" };\n"); + misc_deregister(&hvisor_misc_dev); + return -ENODEV; + } else { + virtio_irq = of_irq_get(node, 0); + of_node_put(node); + } - of_node_put(node); +#ifdef LOONGARCH64 + if (hvisor_irq_is_percpu) { + err = request_percpu_irq(virtio_irq, virtio_irq_handler, + "hvisor_virtio_device", &hvisor_percpu_dev); + if (err) + goto err_out; + enable_percpu_irq(virtio_irq, IRQ_TYPE_NONE); + } else +#endif + { + err = request_irq(virtio_irq, virtio_irq_handler, +#ifdef LOONGARCH64 + IRQF_SHARED, "hvisor_virtio_device", +#else + IRQF_SHARED | IRQF_TRIGGER_RISING, + "hvisor_virtio_device", +#endif + &hvisor_misc_dev); + if (err) + goto err_out; + } #else // we don't use device tree in x86_64, so we have to get IRQ using hypercall u32 *irq = kmalloc(sizeof(u32), GFP_KERNEL); @@ -415,6 +759,9 @@ static int __init hvisor_init(void) { kfree(irq); #endif /* X86_64 */ +#ifdef LOONGARCH64 +init_done: +#endif pr_info("hvisor init done!!!\n"); return 0; err_out: @@ -429,6 +776,13 @@ static int __init hvisor_init(void) { ** Module Exit function */ static void __exit hvisor_exit(void) { +#ifdef LOONGARCH64 + if (hvisor_poll_thread) { + hrtimer_cancel(&hvisor_poll_timer); + kthread_stop(hvisor_poll_thread); + hvisor_poll_thread = NULL; + } +#endif if (virtio_irq != -1) free_irq(virtio_irq, &hvisor_misc_dev); if (virtio_irq_ctx) diff --git a/examples/3a6000-loongarch64/daemon.sh b/examples/3a6000-loongarch64/daemon.sh new file mode 100644 index 0000000..2b9d88a --- /dev/null +++ b/examples/3a6000-loongarch64/daemon.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Usage: daemon.sh [virtio_cfg.json] +# If no argument given, defaults to virtio_cfg.json + +mkdir -p /dev/pts +mount -t devpts devpts /dev/pts + +cfg="${1:-virtio_cfg.json}" +echo "Starting virtio with config: $cfg" +nohup hvisor virtio start "$cfg" & \ No newline at end of file diff --git a/examples/3a6000-loongarch64/start_zone/start_zone1.sh b/examples/3a6000-loongarch64/start_zone/start_zone1.sh new file mode 100644 index 0000000..2fe1356 --- /dev/null +++ b/examples/3a6000-loongarch64/start_zone/start_zone1.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Start zone1 (Linux) using dynamic memory allocation. +# Usage: bash start_zone1.sh +# zone1_ram.json is generated by hvisor-virtio daemon after HVISOR_ZONE_M_ALLOC. + +set -e + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +EXAMPLES_DIR="$SCRIPT_DIR/.." + +ZONE_CONFIG="$EXAMPLES_DIR/zone1_linux.json" +RAM_JSON="$EXAMPLES_DIR/zone1_ram.json" # generated by virtio daemon + +if [ ! -f "$ZONE_CONFIG" ]; then + echo "Error: zone config not found: $ZONE_CONFIG" + exit 1 +fi + +if [ ! -f "$RAM_JSON" ]; then + echo "Error: zone1_ram.json not found: $RAM_JSON" + echo "Make sure hvisor-virtio daemon has run and allocated memory first." + exit 1 +fi + +echo "Starting zone1..." +echo " config : $ZONE_CONFIG" +echo " ram : $RAM_JSON" + +# hvisor resolves kernel_filepath relative to cwd. +# zone1_linux.json uses "../Guest/vmlinux.bin", so we must run from +# the examples directory where that relative path resolves correctly. +cd "$EXAMPLES_DIR" +hvisor zone start_dyna "$ZONE_CONFIG" "$RAM_JSON" diff --git a/examples/3a6000-loongarch64/test/virtio_cfg1.json b/examples/3a6000-loongarch64/test/virtio_cfg1.json new file mode 100644 index 0000000..be4cc89 --- /dev/null +++ b/examples/3a6000-loongarch64/test/virtio_cfg1.json @@ -0,0 +1,20 @@ +{ + "zones": [ + { + "id": 1, + "name": "linux", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x200000", + "size": "0xec00000" + }, + { + "type": "normal", + "zonex_ipa": "0x100000000", + "size": "0x20000000" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/test/virtio_cfg2.json b/examples/3a6000-loongarch64/test/virtio_cfg2.json new file mode 100644 index 0000000..4ea33e7 --- /dev/null +++ b/examples/3a6000-loongarch64/test/virtio_cfg2.json @@ -0,0 +1,15 @@ +{ + "zones": [ + { + "id": 1, + "name" : "npucore", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x80000000", + "size": "0x2f000000" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/virtio_cfg.json b/examples/3a6000-loongarch64/virtio_cfg.json new file mode 100644 index 0000000..5f5831f --- /dev/null +++ b/examples/3a6000-loongarch64/virtio_cfg.json @@ -0,0 +1,101 @@ +{ + "zones": [ + { + "id": 1, + "name": "linux", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x00000000", + "size": "0x10000000" + }, + { + "type": "normal", + "zonex_ipa": "0x90000000", + "size": "0x10000000" + } + ], + "devices": [ + { + "type": "console", + "addr": "0x30001000", + "len": "0x200", + "irq": 4, + "status": "enable" + } + ] + }, + { + "id": 2, + "name": "sel4", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x00000000", + "size": "0x10000000" + }, + { + "type": "normal", + "zonex_ipa": "0x90000000", + "size": "0x10000000" + } + ] + }, + { + "id": 3, + "name": "linux", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x00000000", + "size": "0x10000000" + }, + { + "type": "normal", + "zonex_ipa": "0x90000000", + "size": "0x10000000" + } + ], + "devices": [ + { + "type": "console", + "addr": "0x30001000", + "len": "0x200", + "irq": 4, + "status": "enable" + } + ] + }, + { + "id": 4, + "name": "rt-thread", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x200000", + "size": "0x5000000" + } + ], + "devices": [ + { + "type": "console", + "addr": "0x30001000", + "len": "0x1000", + "irq": 16, + "status": "enable" + } + ] + }, + { + "id": 5, + "name" : "npucore", + "memory_region": [ + { + "type": "normal", + "zonex_ipa": "0x80000000", + "size": "0x2f000000" + } + ] + } + ] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/zone1_linux.json b/examples/3a6000-loongarch64/zone1_linux.json new file mode 100644 index 0000000..3ebd2aa --- /dev/null +++ b/examples/3a6000-loongarch64/zone1_linux.json @@ -0,0 +1,84 @@ +{ + "arch": "loongarch64", + "name": "linux1", + "zone_id": 1, + "boot_method": "acpi", + "cpus": [ + 4, 5 + ], + "memory_regions": [ + { + "type": "io", + "physical_start": "0x1fe00000", + "virtual_start": "0x1fe00000", + "size": "0x1000" + }, + { + "type": "io", + "physical_start": "0x10000000", + "virtual_start": "0x10000000", + "size": "0x2000" + }, + { + "type": "io", + "physical_start": "0x10010000", + "virtual_start": "0x10010000", + "size": "0x10000" + }, + { + "type": "io", + "physical_start": "0x100d0000", + "virtual_start": "0x100d0000", + "size": "0x1000" + }, + { + "note": "for efi boot (special)", + "type": "ram", + "physical_start": "0x90000000", + "virtual_start": "0x90000000", + "size": "0x6a000000" + }, + { + "note": "for efi boot (special)", + "type": "ram", + "physical_start": "0xfa000000", + "virtual_start": "0xfa000000", + "size": "0x5000000" + }, + { + "note": "for efi boot (special)", + "type": "ram", + "physical_start": "0x800000000", + "virtual_start": "0x800000000", + "size": "0x80000000" + } + ], + "interrupts": [ + 100 + ], + "ivc_configs": [], + "kernel_args": "BOOT_IMAGE=loglevel=7 verbose earlycon=uart,mmio,0x1fe001e0,115200n8 console=ttyS0,115200", + "kernel_filepath": "../Guest/vmlinux.bin", + "dtb_filepath": "./test.bin", + "kernel_load_paddr": "0x00200000", + "dtb_load_paddr": "0xc0000000", + "entry_point": "0x9000000000faf000", + "arch_config": { + "dummy": "0x1234" + }, + "pci_config": { + "ecam_base": "0xfe00000000", + "ecam_size": "0x20000000", + "io_base": "0x18408000", + "io_size": "0x8000", + "pci_io_base": "0x00008000", + "mem32_base": "0x0", + "mem32_size": "0x0", + "pci_mem32_base": "0x0", + "mem64_base": "0x60000000", + "mem64_size": "0x20000000", + "pci_mem64_base": "0x60000000" + }, + "num_pci_devs": 1, + "alloc_pci_devs": [1537] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/zone2_sel4.json b/examples/3a6000-loongarch64/zone2_sel4.json new file mode 100644 index 0000000..e69de29 diff --git a/examples/3a6000-loongarch64/zone3_linux.json b/examples/3a6000-loongarch64/zone3_linux.json new file mode 100644 index 0000000..83b9570 --- /dev/null +++ b/examples/3a6000-loongarch64/zone3_linux.json @@ -0,0 +1,64 @@ +{ + "arch": "loongarch64", + "name": "linux1", + "zone_id": 1, + "boot_method": "normal", + "cpus": [ + 100 + ], + "memory_regions": [ + { + "type": "io", + "physical_start": "0x1fe00000", + "virtual_start": "0x1fe00000", + "size": "0x1000" + }, + { + "type": "io", + "physical_start": "0x10000000", + "virtual_start": "0x10000000", + "size": "0x2000" + }, + { + "type": "io", + "physical_start": "0x10010000", + "virtual_start": "0x10010000", + "size": "0x20000" + }, + { + "type": "io", + "physical_start": "0x100d0000", + "virtual_start": "0x100d0000", + "size": "0x1000" + }, + { + "type": "io", + "physical_start": "0xfe00000000", + "virtual_start": "0xfe00000000", + "size": "0x20000000" + }, + { + "type": "io", + "physical_start": "0x18408000", + "virtual_start": "0x18408000", + "size": "0x8000" + } + ], + "interrupts": [ + 100 + ], + "ivc_configs": [], + "kernel_args": "BOOT_IMAGE=loglevel=7 verbose earlycon=uart,mmio,0x1fe001e0,115200n8 console=ttyS0,115200", + "kernel_filepath": "./Guest/vmlinux-dtb.bin", + "dtb_filepath": "./test.bin", + "kernel_load_paddr": "0x00200000", + "dtb_load_paddr": "0xc0000000", + "entry_point": "0x9000000000faf000", + "arch_config": { + "dummy": "0x1234" + }, + "pci_config": { + }, + "num_pci_devs": 0, + "alloc_pci_devs": [] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/zone4_rtthread.json b/examples/3a6000-loongarch64/zone4_rtthread.json new file mode 100644 index 0000000..0e94b18 --- /dev/null +++ b/examples/3a6000-loongarch64/zone4_rtthread.json @@ -0,0 +1,39 @@ +{ + "arch": "loongarch64", + "name": "rthread", + "zone_id": 2, + "boot_method": "normal", + "cpus": [ + 6 + ], + "memory_regions": [ + { + "type": "io", + "physical_start": "0x1fe00000", + "virtual_start": "0x1fe00000", + "size": "0x2000" + }, + { + "type": "io", + "physical_start": "0x10000000", + "virtual_start": "0x10000000", + "size": "0x20000" + } + ], + "interrupts": [ + ], + "ivc_configs": [], + "kernel_args": "NOT_USED_YET", + "kernel_filepath": "./Guest/rtthread.bin", + "dtb_filepath": "./test.bin", + "kernel_load_paddr": "0x200000", + "dtb_load_paddr": "0xc0000000", + "entry_point": "9000000000200000", + "arch_config": { + "dummy": "0x1234" + }, + "pci_config": { + }, + "num_pci_devs": 0, + "alloc_pci_devs": [] +} \ No newline at end of file diff --git a/examples/3a6000-loongarch64/zone5_npucore.json b/examples/3a6000-loongarch64/zone5_npucore.json new file mode 100644 index 0000000..e69de29 diff --git a/include/hvisor.h b/include/hvisor.h index cecedb9..9d0a5c0 100644 --- a/include/hvisor.h +++ b/include/hvisor.h @@ -97,15 +97,34 @@ struct hvisor_load_image_args { #define HVISOR_CLEAR_INJECT_IRQ _IO(1, 6) // used for ioctl #define HVISOR_HC_CLEAR_INJECT_IRQ 20 // hvcall code in hvisor +#define HVISOR_ZONE_M_ALLOC _IOW(1, 7, kmalloc_info_t *) +#define HVISOR_ZONE_M_FREE _IOW(1, 8, kmalloc_info_t *) + +// for HVISOR_ZONE_M_ALLOC and HVISOR_ZONE_M_FREE +// boneinscri -- 2026.04 +struct kmalloc_info { + __u64 pa; // Physical address for HVISOR_ZONE_M_ALLOC/FREE + __u64 size; +}; +typedef struct kmalloc_info kmalloc_info_t; + #endif /* LOONGARCH64 */ #ifdef LOONGARCH64 static inline __u64 hvisor_call(__u64 code, __u64 arg0, __u64 arg1) { register __u64 a0 asm("a0") = code; register __u64 a1 asm("a1") = arg0; register __u64 a2 asm("a2") = arg1; - // asm volatile ("hvcl"); // not supported by loongarch gcc now + asm volatile( + "hvcl 0" + : "+r"(a0) + : "r"(a1), "r"(a2) + : "memory"); // it is supported by loongarch64-linux-gnu-gcc (13.2.0) + // boneinscri -- 2026.04 + // download: wget + // https://github.com/LoongsonLab/oscomp-toolchains-for-oskernel/releases/download/gcc-13.2.0-loongarch64/gcc-13.2.0-loongarch64-linux-gnu.tgz + // hvcl 0 is 0x002b8000 - __asm__(".word 0x002b8000" : "+r"(a0), "+r"(a1), "+r"(a2)); + // __asm__(".word 0x002b8000" : "+r"(a0), "+r"(a1), "+r"(a2)); return a0; } #endif /* LOONGARCH64 */ diff --git a/tools/hvisor.c b/tools/hvisor.c index bfe241c..e96604b 100644 --- a/tools/hvisor.c +++ b/tools/hvisor.c @@ -8,6 +8,7 @@  * Authors:  *      Guowei Li <2401213322@stu.pku.edu.cn>  */ +#include #include #include #include @@ -89,9 +90,36 @@ void *read_file(const char *filename, uint64_t *filesize) { return buf; } +// boneinscri 2026.04 +char *open_json_file(const char *json_config_path) { + FILE *file = fopen(json_config_path, "r"); + if (file == NULL) { + printf("Error opening json file: %s\n", json_config_path); + fprintf(stderr, "Error opening json file: %s\n", json_config_path); + exit(1); + } + fseek(file, 0, SEEK_END); + long file_size = ftell(file); + fseek(file, 0, SEEK_SET); + char *buffer = malloc(file_size + 1); + if (fread(buffer, 1, file_size, file) == 0) { + printf("Error reading json file: %s\n", json_config_path); + fprintf(stderr, "Error reading json file: %s\n", json_config_path); + goto err_out; + } + fclose(file); + buffer[file_size] = '\0'; + + return buffer; +err_out: + free(buffer); + return NULL; +} + int open_dev() { int fd = open("/dev/hvisor", O_RDWR); if (fd < 0) { + perror("open /dev/hvisor"); log_error("Failed to open /dev/hvisor!"); exit(1); } @@ -126,7 +154,8 @@ static __u64 load_buffer_to_memory(const void *buf, __u64 size, return map_size; } -static __u64 load_str_to_memory(const char *str, __u64 load_paddr) { +static __attribute__((unused)) __u64 load_str_to_memory(const char *str, + __u64 load_paddr) { /* Include trailing '\0' so guest side can safely parse cmdline. */ __u64 size = strlen(str) + 1; return load_buffer_to_memory(str, size, load_paddr); @@ -238,11 +267,13 @@ static int parse_modules(const cJSON *const modules_json) { goto err_out; \ } -static int parse_arch_config(cJSON *root, zone_config_t *config) { +static __attribute__((unused)) int parse_arch_config(cJSON *root, + zone_config_t *config) { cJSON *arch_config_json = SAFE_CJSON_GET_OBJECT_ITEM(root, "arch_config"); CHECK_JSON_NULL(arch_config_json, "arch_config"); - arch_zone_config_t *arch_config = &config->arch_config; + arch_zone_config_t *arch_config __attribute__((unused)) = + &config->arch_config; #ifdef ARM64 cJSON *gic_version_json = SAFE_CJSON_GET_OBJECT_ITEM(arch_config_json, "gic_version"); @@ -639,6 +670,465 @@ static int parse_pci_config(cJSON *root, zone_config_t *config) { return -1; } +// boneinscri 2026.04 +#ifdef LOONGARCH64 +static __u64 load_chunked_image_to_memory(const char *path, __u64 load_ipa, + zone_config_t *config, + cJSON *ram_json) { + + __u64 size, page_size, total_map_size; + int fd = open_dev(); + void *image_content; + + image_content = read_file(path, &size); + // printf("load_chunked_image_to_memory, read file done, image_content: + // %p\n", + // image_content); + + page_size = sysconf(_SC_PAGESIZE); + total_map_size = (size + page_size - 1) & ~(page_size - 1); + __u64 total_map_size_remain = total_map_size; + + int num_memory_regions_ram = cJSON_GetArraySize(ram_json); + int num_memory_regions_init = config->num_memory_regions; + + // update num_memory_regions + config->num_memory_regions += num_memory_regions_ram; + if (config->num_memory_regions > CONFIG_MAX_MEMORY_REGIONS) { + fprintf(stderr, + "Error: too many memory regions in config, " + "config->num_memory_regions: %d\n", + config->num_memory_regions); + while (1) { + }; + } else { + printf("load_chunked_image_to_memory, num_memory_regions: %d\n", + config->num_memory_regions); + } + + __u64 kernel_load_paddr_ipa = + config->kernel_load_paddr; // fake load address, update it later + + // printf("load_chunked_image_to_memory, ready, total_map_size: %llx, + // kernel_load_paddr_ipa: %llx\n", + // total_map_size, kernel_load_paddr_ipa); + + int cross_load_addr_cnt = 0; + + for (int i = 0; i < num_memory_regions_ram; i++) { + // printf("load_chunked_image_to_memory....., i: %d\n", i); + + cJSON *region = cJSON_GetArrayItem(ram_json, i); + int config_region_idx = num_memory_regions_init + i; + + memory_region_t *mem_region = + &config->memory_regions[config_region_idx]; + + // step1: update config->memory_regions + mem_region->virtual_start = + strtoull(cJSON_GetObjectItem(region, "ipa")->valuestring, NULL, 16); + mem_region->physical_start = + strtoull(cJSON_GetObjectItem(region, "hpa")->valuestring, NULL, 16); + mem_region->size = strtoull( + cJSON_GetObjectItem(region, "size")->valuestring, NULL, 16); + + // patch + char *type_str = cJSON_GetObjectItem(region, "type")->valuestring; + if (type_str == NULL) { + printf( + "load_chunked_image_to_memory, type_str is null, check it\n"); + while (1) { + } + } + if (strcmp(type_str, "virtio") == 0) { + // virtio, skip it + mem_region->type = MEM_TYPE_VIRTIO; + continue; + } else if (strcmp(type_str, "io") == 0) { + // special io, skip it (like shm-msg) + mem_region->type = MEM_TYPE_IO; + continue; + } else if (strcmp(type_str, "ram") == 0) { + // ram + mem_region->type = MEM_TYPE_RAM; + } else { + printf("load_chunked_image_to_memory, unknown memory type, check " + "it, %s\n", + type_str); + while (1) { + } + } + + // step2: copy image to memory + __u64 load_chunk_size = mem_region->size; + __u64 load_chunk_hpa = mem_region->physical_start; + __u64 load_chunk_ipa = mem_region->virtual_start; + __u64 map_size_chunk = + (load_chunk_size + page_size - 1) & ~(page_size - 1); + + // printf("load_chunk_ipa: 0x%llx, load_chunk_hpa: 0x%llx, + // load_chunk_size: 0x%llx\n", + // load_chunk_ipa, load_chunk_hpa, load_chunk_size); + map_size_chunk = + MIN(map_size_chunk, total_map_size_remain); // important + + // step2.5: update config->kernel_load_paddr + if (kernel_load_paddr_ipa >= load_chunk_ipa && + kernel_load_paddr_ipa < load_chunk_ipa + load_chunk_size) { + __u64 offset = kernel_load_paddr_ipa - load_chunk_ipa; + config->kernel_load_paddr = load_chunk_hpa + offset; + // printf("config->kernel_load_paddr: 0x%llx\n", + // config->kernel_load_paddr); + + cross_load_addr_cnt++; + if (cross_load_addr_cnt > 1) { + printf("load_chunked_image_to_memory, memory region doubled, " + "check it\n"); + while (1) { + } + } + } + + if (total_map_size_remain == 0) { + // it's enough + continue; + } + + if (load_chunk_ipa + load_chunk_size < kernel_load_paddr_ipa) { + // jump this chunk, because it's before kernel load address + while (1) { + } + printf("load_chunked_image_to_memory, jump this chunk, don't need " + "memmove, load_chunk_ipa: 0x%llx, kernel_load_paddr_ipa: " + "0x%llx\n", + load_chunk_ipa, kernel_load_paddr_ipa); + continue; + } else { + if (load_chunk_ipa < kernel_load_paddr_ipa) { + // load_chunk_ipa ... |kernel_load_paddr_ipa| ... load_chunk_ipa + // + load_chunk_size + + // this!!! + __u64 needless_size = kernel_load_paddr_ipa - load_chunk_ipa; + map_size_chunk -= needless_size; + load_chunk_hpa += needless_size; + load_chunk_ipa = kernel_load_paddr_ipa; + } else { + // do nothing + } + } + + // printf( + // "ready to mmap, map_size_chunk: 0x%llx, load_chunk_hpa: 0x%llx, + // total_map_size_remain: %llx\n", map_size_chunk, load_chunk_hpa, + // total_map_size_remain); + + void *virt_addr_chunk = (__u64)mmap( + NULL, map_size_chunk, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_SHARED, fd, load_chunk_hpa); // use hvisor_mmap handler + + if (virt_addr_chunk == MAP_FAILED) { + perror("load_chunked_image_to_memory, mapping memory error"); + exit(1); + } + + // if (load_chunk_ipa < kernel_load_paddr_ipa) { + // fprintf(stderr, "Error: load_chunk_ipa 0x%llx is less than + // load_paddr_ipa 0x%llx\n", + // load_chunk_ipa, kernel_load_paddr_ipa); + // exit(1); + // } + __u64 load_addr_offset = load_chunk_ipa - kernel_load_paddr_ipa; + + // printf("ready to memove, virt_addr_chunk: 0x%p, " + // "image_addr: 0x%p, load_addr_offset: 0x%llx\n", + // virt_addr_chunk, image_content + load_addr_offset, + // load_addr_offset); + + memmove(virt_addr_chunk, image_content + load_addr_offset, + map_size_chunk); // this? + + total_map_size_remain -= map_size_chunk; + + // printf("virtual_addr_chunk: 0x%p, load_addr_offset: 0x%llx, + // load_chunk_ipa: 0x%llx, load_chunk_hpa: 0x%llx, load_chunk_size: + // 0x%llx, map_size_chunk: 0x%llx, total_map_size_remain: 0x%llx\n", + // virt_addr_chunk, load_addr_offset, load_chunk_ipa, + // load_chunk_hpa, load_chunk_size, map_size_chunk, + // total_map_size_remain); + + munmap(virt_addr_chunk, map_size_chunk); + } + + if (total_map_size_remain != 0) { + printf("something error happened, total_map_size_remain: 0x%llx, " + "total_map_size: 0x%llx, check it\n", + total_map_size_remain, total_map_size); + exit(1); + } + + assert(kernel_load_paddr_ipa != config->kernel_load_paddr); + + free(image_content); + + close(fd); + + printf("load_chunked_image_to_memory, image_size : %llx\n", size); + return total_map_size; +} +#endif + +static int zone_start_from_json_dynamic(const char *json_config_path, + const char *ram_json_path, + zone_config_t *config) { + char *buffer_main = NULL; + char *buffer_ram = NULL; + cJSON *root_main = NULL; + cJSON *root_ram = NULL; + + buffer_main = open_json_file(json_config_path); + if (buffer_main == NULL) { + goto err_out; + } + buffer_ram = open_json_file(ram_json_path); + if (buffer_ram == NULL) { + goto err_out; + } + + // parse ram JSON + root_ram = cJSON_Parse(buffer_ram); + cJSON *ram_regions_json = cJSON_GetObjectItem(root_ram, "memory_regions"); + CHECK_JSON_NULL_ERR_OUT(ram_regions_json, "memory_regions") + + // parse JSON + root_main = cJSON_Parse(buffer_main); + cJSON *zone_id_json = cJSON_GetObjectItem(root_main, "zone_id"); + cJSON *cpus_json = cJSON_GetObjectItem(root_main, "cpus"); + cJSON *name_json = cJSON_GetObjectItem(root_main, "name"); + cJSON *boot_method_json = cJSON_GetObjectItem(root_main, "boot_method"); + cJSON *memory_regions_json = + cJSON_GetObjectItem(root_main, "memory_regions"); + cJSON *kernel_filepath_json = + cJSON_GetObjectItem(root_main, "kernel_filepath"); + cJSON *kernel_args_json = cJSON_GetObjectItem(root_main, "kernel_args"); + cJSON *dtb_filepath_json = cJSON_GetObjectItem(root_main, "dtb_filepath"); + cJSON *kernel_load_paddr_json = + cJSON_GetObjectItem(root_main, "kernel_load_paddr"); + cJSON *dtb_load_paddr_json = + cJSON_GetObjectItem(root_main, "dtb_load_paddr"); + cJSON *entry_point_json = cJSON_GetObjectItem(root_main, "entry_point"); + cJSON *interrupts_json = cJSON_GetObjectItem(root_main, "interrupts"); + cJSON *ivc_configs_json = cJSON_GetObjectItem(root_main, "ivc_configs"); + + CHECK_JSON_NULL_ERR_OUT(zone_id_json, "zone_id") + CHECK_JSON_NULL_ERR_OUT(cpus_json, "cpus") + CHECK_JSON_NULL_ERR_OUT(name_json, "name") + CHECK_JSON_NULL_ERR_OUT(boot_method_json, "boot_method") + CHECK_JSON_NULL_ERR_OUT(memory_regions_json, "memory_regions") + CHECK_JSON_NULL_ERR_OUT(kernel_filepath_json, "kernel_filepath") + CHECK_JSON_NULL_ERR_OUT(kernel_args_json, "kernel_args") + CHECK_JSON_NULL_ERR_OUT(dtb_filepath_json, "dtb_filepath") + CHECK_JSON_NULL_ERR_OUT(kernel_load_paddr_json, "kernel_load_paddr") + CHECK_JSON_NULL_ERR_OUT(dtb_load_paddr_json, "dtb_load_paddr") + CHECK_JSON_NULL_ERR_OUT(entry_point_json, "entry_point") + CHECK_JSON_NULL_ERR_OUT(interrupts_json, "interrupts") + CHECK_JSON_NULL_ERR_OUT(ivc_configs_json, "ivc_configs") + + config->zone_id = zone_id_json->valueint; + + int num_cpus = cJSON_GetArraySize(cpus_json); + + for (int i = 0; i < num_cpus; i++) { + config->cpus |= (1 << cJSON_GetArrayItem(cpus_json, i)->valueint); + } + + int num_memory_regions = cJSON_GetArraySize(memory_regions_json); + int num_interrupts = cJSON_GetArraySize(interrupts_json); + + if (num_memory_regions > CONFIG_MAX_MEMORY_REGIONS || + num_interrupts > CONFIG_MAX_INTERRUPTS) { + log_error("Exceeded maximum allowed regions/interrupts."); + goto err_out; + } + + // Iterate through each memory region of the zone + // Including memory and MMIO regions of the zone + config->num_memory_regions = num_memory_regions; + for (int i = 0; i < num_memory_regions; i++) { + cJSON *region = cJSON_GetArrayItem(memory_regions_json, i); + memory_region_t *mem_region = &config->memory_regions[i]; + + const char *type_str = cJSON_GetObjectItem(region, "type")->valuestring; + if (strcmp(type_str, "ram") == 0) { + // do ram region parsing below + // continue; + mem_region->type = MEM_TYPE_RAM; + } else if (strcmp(type_str, "io") == 0) { + // io device + mem_region->type = MEM_TYPE_IO; + } else if (strcmp(type_str, "virtio") == 0) { + // virtio device + mem_region->type = MEM_TYPE_VIRTIO; + } else { + printf("Unknown memory region type: %s\n", type_str); + mem_region->type = -1; // invalid type + } + + mem_region->physical_start = + strtoull(cJSON_GetObjectItem(region, "physical_start")->valuestring, + NULL, 16); + mem_region->virtual_start = + strtoull(cJSON_GetObjectItem(region, "virtual_start")->valuestring, + NULL, 16); + + mem_region->size = strtoull( + cJSON_GetObjectItem(region, "size")->valuestring, NULL, 16); + } + + printf("memory regions, get done\n"); + + memset(config->interrupts_bitmap, 0, sizeof(config->interrupts_bitmap)); + for (int i = 0; i < num_interrupts; i++) { + const cJSON *const item = SAFE_CJSON_GET_ARRAY_ITEM(interrupts_json, i); + + size_t irq; + if (parse_json_size(item, &irq) != 0) { + log_error("Failed to parse irq %d", i); + goto err_out; + } + + if (irq >= CONFIG_MAX_INTERRUPTS) { + log_error("irq %zu is out of range", irq); + goto err_out; + } + + // irq is valid, set the bit in the bitmap + const size_t word_index = irq / CONFIG_INTERRUPTS_BITMAP_BITS_PER_WORD; + const size_t bit_index = irq % CONFIG_INTERRUPTS_BITMAP_BITS_PER_WORD; + config->interrupts_bitmap[word_index] |= ((BitmapWord)1) << bit_index; + log_info("irq %zu is valid, set the bit in the bitmap", irq); + } + printf("interrupts, get done\n"); + + // ivc + int num_ivc_configs = cJSON_GetArraySize(ivc_configs_json); + config->num_ivc_configs = num_ivc_configs; + for (int i = 0; i < num_ivc_configs; i++) { + cJSON *ivc_config_json = cJSON_GetArrayItem(ivc_configs_json, i); + ivc_config_t *ivc_config = &config->ivc_configs[i]; + ivc_config->ivc_id = + cJSON_GetObjectItem(ivc_config_json, "ivc_id")->valueint; + ivc_config->peer_id = + cJSON_GetObjectItem(ivc_config_json, "peer_id")->valueint; + ivc_config->shared_mem_ipa = strtoull( + cJSON_GetObjectItem(ivc_config_json, "shared_mem_ipa")->valuestring, + NULL, 16); + ivc_config->control_table_ipa = + strtoull(cJSON_GetObjectItem(ivc_config_json, "control_table_ipa") + ->valuestring, + NULL, 16); + ivc_config->rw_sec_size = strtoull( + cJSON_GetObjectItem(ivc_config_json, "rw_sec_size")->valuestring, + NULL, 16); + ivc_config->out_sec_size = strtoull( + cJSON_GetObjectItem(ivc_config_json, "out_sec_size")->valuestring, + NULL, 16); + ivc_config->interrupt_num = + cJSON_GetObjectItem(ivc_config_json, "interrupt_num")->valueint; + ivc_config->max_peers = + cJSON_GetObjectItem(ivc_config_json, "max_peers")->valueint; + printf("ivc_config %d: ivc_id %d, peer_id %d, shared_mem_ipa %llx, " + "interrupt_num %d, max_peers %d\n", + i, ivc_config->ivc_id, ivc_config->peer_id, + ivc_config->shared_mem_ipa, ivc_config->interrupt_num, + ivc_config->max_peers); + } + config->entry_point = strtoull(entry_point_json->valuestring, NULL, 16); + + config->kernel_load_paddr = + strtoull(kernel_load_paddr_json->valuestring, NULL, 16); + // TODO: modify to real physical addr according to the memory region later + + config->dtb_load_paddr = + strtoull(dtb_load_paddr_json->valuestring, NULL, 16); + + printf("ready to call load_chunked_image_to_memory\n"); + config->kernel_size = load_chunked_image_to_memory( + kernel_filepath_json->valuestring, config->kernel_load_paddr, config, + ram_regions_json); + + if (config->kernel_load_paddr == 0) { + printf("kernel load paddr is 0, check it\n"); + while (1) + ; + } + + int fd = open_dev(); + if (fd < 0) { + perror("zone_start: open hvisor failed"); + goto err_out; + } + int err = 0; +#ifdef LOONGARCH64 + if (!strcmp(boot_method_json->valuestring, "acpi")) { + // get cmdline + + } else { + // assert it is normal boot method + } +#endif + + printf("kernel_load_paddr: 0x%llx\n", config->kernel_load_paddr); + + printf("Kernel size: %llx, DTB size: %llx\n", config->kernel_size, + config->dtb_size); + + // check name length + if (strlen(name_json->valuestring) > CONFIG_NAME_MAXLEN) { + fprintf(stderr, "Zone name too long: %s\n", name_json->valuestring); + goto err_out; + } + strncpy(config->name, name_json->valuestring, CONFIG_NAME_MAXLEN); + + printf("Zone name: %s\n", config->name); + +#ifndef LOONGARCH64 + // Parse architecture-specific configurations (interrupts for each platform) + if (parse_arch_config(root_main, config)) + goto err_out; +#endif + parse_pci_config(root_main, config); + + if (root_main) + cJSON_Delete(root_main); + if (buffer_main) + free(buffer_main); + + if (root_ram) + cJSON_Delete(root_ram); + if (buffer_ram) + free(buffer_ram); + + err = ioctl(fd, HVISOR_ZONE_START, config); + + if (err) + perror("zone_start: ioctl failed"); + + close(fd); + + return 0; +err_out: + if (root_main) + cJSON_Delete(root_main); + if (buffer_main) + free(buffer_main); + if (root_ram) + cJSON_Delete(root_ram); + if (buffer_ram) + free(buffer_ram); + return -1; +} + static int zone_start_from_json(const char *json_config_path, zone_config_t *config) { cJSON *root = NULL; @@ -940,6 +1430,44 @@ static int zone_start(int argc, char *argv[]) { return zone_start_from_json(json_config_path, &config); } +// ./hvisor zone start_dyna +static int zone_start_dyna(int argc, char *argv[]) { + char *json_config_path = NULL; + char *ram_json_path = NULL; + zone_config_t config; + int fd, ret; + uint64_t hvisor_config_version; + + if (argc != 5) { + help(1); + } + json_config_path = argv[3]; + ram_json_path = argv[4]; + + memset(&config, 0, sizeof(zone_config_t)); + + fd = open_dev(); + ret = ioctl(fd, HVISOR_CONFIG_CHECK, &hvisor_config_version); + close(fd); + + if (ret) { + log_error("ioctl: hvisor config check failed, ret %d", ret); + return -1; + } + + if (hvisor_config_version != CONFIG_MAGIC_VERSION) { + log_error("zone start_dyna failed because config versions mismatch, " + "hvisor-tool is 0x%x, hvisor is 0x%x", + CONFIG_MAGIC_VERSION, hvisor_config_version); + return -1; + } else { + log_info("zone config check pass"); + } + + return zone_start_from_json_dynamic(json_config_path, ram_json_path, + &config); +} + // ./hvisor zone shutdown -id 1 static int zone_shutdown(int argc, char *argv[]) { if (argc != 2 || strcmp(argv[0], "-id") != 0) { @@ -959,7 +1487,7 @@ static void print_cpu_list(__u64 cpu_mask, char *outbuf, size_t bufsize) { int found_cpu = 0; char *buf = outbuf; - for (int i = 0; i < MAX_CPUS && buf - outbuf < bufsize; i++) { + for (int i = 0; i < MAX_CPUS && (size_t)(buf - outbuf) < bufsize; i++) { if ((cpu_mask & (1ULL << i)) != 0) { if (found_cpu) { *buf++ = ','; @@ -976,22 +1504,25 @@ static void print_cpu_list(__u64 cpu_mask, char *outbuf, size_t bufsize) { } // ./hvisor zone list -static int zone_list(int argc, char *argv[]) { +static int zone_list(int argc, char *argv[] __attribute__((unused))) { + if (argc != 0) { help(1); } __u64 cnt = CONFIG_MAX_ZONES; zone_info_t *zones = malloc(sizeof(zone_info_t) * cnt); zone_list_args_t args = {cnt, zones}; - // printf("zone_list: cnt %llu, zones %p\n", cnt, zones); + printf("zone_list: cnt %llu, zones %p\n", cnt, zones); int fd = open_dev(); + printf("zone_list, step1\n"); int ret = ioctl(fd, HVISOR_ZONE_LIST, &args); + printf("[trace] zone_list: ret = %d\n", ret); + if (ret < 0) perror("zone_list: ioctl failed"); printf("| %11s | %10s | %9s | %10s |\n", "zone_id", "cpus", "name", "status"); - for (int i = 0; i < ret; i++) { char cpu_list_str[256]; // Assuming this buffer size is enough memset(cpu_list_str, 0, sizeof(cpu_list_str)); @@ -1021,6 +1552,8 @@ int main(int argc, char *argv[]) { if (strcmp(argv[2], "start") == 0) { err = zone_start(argc, argv); + } else if (strcmp(argv[2], "start_dyna") == 0) { + err = zone_start_dyna(argc, argv); } else if (strcmp(argv[2], "shutdown") == 0) { err = zone_shutdown(argc - 3, &argv[3]); } else if (strcmp(argv[2], "list") == 0) { diff --git a/tools/log.c b/tools/log.c index af19f58..455bd83 100644 --- a/tools/log.c +++ b/tools/log.c @@ -68,13 +68,17 @@ void log_log(int level, const char *file, int line, const char *fmt, ...) { return; } - /* Primary output to syslog */ va_list ap; va_start(ap, fmt); char buf[2048]; vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); + + /* Output to syslog */ syslog(syslog_levels[level], "%s:%d: %s", file, line, buf); + + /* Also output to stderr so errors are visible in terminal */ + fprintf(stderr, "[%s] %s:%d: %s\n", level_strings[level], file, line, buf); } void multithread_log_init() { diff --git a/tools/virtio/include/virtio.h b/tools/virtio/include/virtio.h index 4a83496..d3cface 100644 --- a/tools/virtio/include/virtio.h +++ b/tools/virtio/include/virtio.h @@ -131,8 +131,17 @@ struct VirtIODevice { // used event idx for driver telling device when to notify driver. #define VQ_USED_EVENT(vq) ((vq)->avail_ring->ring[(vq)->num]) -// avail event idx for device telling driver when to notify device. -#define VQ_AVAIL_EVENT(vq) (*(uint16_t *)&(vq)->used_ring->ring[(vq)->num]) + +// avail event idx: read/write via memcpy to avoid strict-aliasing violation. +static inline uint16_t vq_avail_event_get(VirtQueue *vq) { + uint16_t val; + memcpy(&val, (const char *)&(vq)->used_ring->ring[(vq)->num], sizeof(val)); + return val; +} +static inline void vq_avail_event_set(VirtQueue *vq, uint16_t val) { + memcpy((char *)&(vq)->used_ring->ring[(vq)->num], &val, sizeof(val)); +} +#define VQ_AVAIL_EVENT(vq) vq_avail_event_get(vq) #define VIRT_MAGIC 0x74726976 /* 'virt' */ @@ -220,4 +229,8 @@ int virtio_start(int argc, char *argv[]); void *read_file(const char *filename, uint64_t *filesize); +// boneinscri 2026.04 +char *open_json_file(const char *json_config_path); +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + #endif /* __HVISOR_VIRTIO_H */ diff --git a/tools/virtio/virtio.c b/tools/virtio/virtio.c index 336732e..6bc88f3 100644 --- a/tools/virtio/virtio.c +++ b/tools/virtio/virtio.c @@ -388,7 +388,7 @@ void *get_virt_addr(void *zonex_ipa, int zone_id) { // virtqueue is no need to notify device. void virtqueue_disable_notify(VirtQueue *vq) { if (vq->event_idx_enabled) { - VQ_AVAIL_EVENT(vq) = vq->last_avail_idx - 1; + vq_avail_event_set(vq, vq->last_avail_idx - 1); } else { vq->used_ring->flags |= (uint16_t)VRING_USED_F_NO_NOTIFY; } @@ -397,7 +397,7 @@ void virtqueue_disable_notify(VirtQueue *vq) { void virtqueue_enable_notify(VirtQueue *vq) { if (vq->event_idx_enabled) { - VQ_AVAIL_EVENT(vq) = vq->avail_ring->idx; + vq_avail_event_set(vq, vq->avail_ring->idx); } else { vq->used_ring->flags &= !(uint16_t)VRING_USED_F_NO_NOTIFY; } @@ -1001,6 +1001,25 @@ void virtio_close() { destroy_event_monitor(); for (int i = 0; i < vdevs_num; i++) vdevs[i]->virtio_close(vdevs[i]); + + // Free all pages allocated via HVISOR_ZONE_M_ALLOC before closing ko_fd +#ifdef LOONGARCH64 + for (int i = 0; i < MAX_ZONES; i++) { + for (int j = 0; j < MAX_RAMS; j++) { + if (zone_mem[i][j][MEM_SIZE] != 0) { + kmalloc_info_t info = { + .pa = zone_mem[i][j][ZONE0_IPA], + .size = zone_mem[i][j][MEM_SIZE], + }; + if (ioctl(ko_fd, HVISOR_ZONE_M_FREE, &info) != 0) + log_warn( + "HVISOR_ZONE_M_FREE failed for pa=0x%llx size=0x%llx", + info.pa, info.size); + } + } + } +#endif + close(ko_fd); if (efd >= 0) { @@ -1201,6 +1220,9 @@ void handle_virtio_requests(void) { log_info("virtio request handler loop started."); int signal_count = 0, proc_count = 0; struct epoll_event events[16]; + (void)signal_count; + (void)proc_count; + (void)events; while (true) { #ifndef LOONGARCH64 log_info("signal_count is %d, proc_count is %d", signal_count, @@ -1261,6 +1283,7 @@ int virtio_init() { log_error("open hvisor failed"); exit(1); } + // ioctl for init virtio // Communicate with hvisor kernel module err = ioctl(ko_fd, HVISOR_INIT_VIRTIO); @@ -1270,6 +1293,8 @@ int virtio_init() { exit(1); } + printf("[trace], virtio init point1 !!!!!!!!!\n"); + // create eventfd efd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); if (efd < 0) { @@ -1277,6 +1302,9 @@ int virtio_init() { close(ko_fd); exit(1); } + + printf("[trace], virtio init point2 !!!!!!!!!\n"); + if (ioctl(ko_fd, HVISOR_SET_EVENTFD, efd) < 0) { log_error("ioctl HVISOR_SET_EVENTFD failed, errno is %d", errno); close(ko_fd); @@ -1284,6 +1312,8 @@ int virtio_init() { exit(1); } + printf("[trace], virtio bridge before !!!!!!!!!\n"); + // mmap: create shared memory // Map the virtio_bridge set by the kernel module to this space virtio_bridge = (struct virtio_bridge *)mmap( @@ -1292,10 +1322,13 @@ int virtio_init() { log_error("mmap failed"); goto unmap; } + printf("[trace], virtio bridge ok!\n"); // Initialize event_monitor used by console and net devices initialize_event_monitor(); log_info("hvisor init okay!"); + printf("[trace], hvisor init okay!\n"); + return 0; unmap: munmap((void *)virtio_bridge, MMAP_SIZE); @@ -1510,14 +1543,285 @@ int virtio_start_from_json(char *json_path) { return err; } +int virtio_start_from_json_dynamic(char *json_path) { + char *buffer = NULL; + u_int64_t file_size; + int zone_id, num_devices = 0, err = 0, num_zones = 0; + void *zone0_ipa; + __u64 zonex_ipa; + (void)zone0_ipa; + + unsigned long long mem_size; + buffer = read_file(json_path, &file_size); + buffer[file_size] = '\0'; + + // Read zones + cJSON *root = cJSON_Parse(buffer); + cJSON *zones_json = cJSON_GetObjectItem(root, "zones"); + num_zones = cJSON_GetArraySize(zones_json); + if (num_zones > MAX_ZONES) { + log_error("Exceed maximum zone number"); + err = -1; + goto err_out; + } + + // Match zone information + for (int i = 0; i < num_zones; i++) { + printf("[trace], zone : %d\n", i); + cJSON *zone_json = cJSON_GetArrayItem(zones_json, i); + cJSON *zone_id_json = cJSON_GetObjectItem(zone_json, "id"); + cJSON *memory_region_json = + cJSON_GetObjectItem(zone_json, "memory_region"); + cJSON *devices_json = cJSON_GetObjectItem(zone_json, "devices"); + zone_id = zone_id_json->valueint; + if (zone_id >= MAX_ZONES) { + log_error("Exceed maximum zone number"); + err = -1; + goto err_out; + } + int num_mems = cJSON_GetArraySize(memory_region_json); + + cJSON *zone_ram_root = cJSON_CreateObject(); + if (!zone_ram_root) { + fprintf(stderr, "Failed to create root JSON object\n"); + return EXIT_FAILURE; + } + cJSON *memory_regions = + cJSON_AddArrayToObject(zone_ram_root, "memory_regions"); + if (!memory_regions) { + fprintf(stderr, "Failed to create memory_region array\n"); + cJSON_Delete(zone_ram_root); + return EXIT_FAILURE; + } + + // shm : create shared memory for each zone + cJSON *zone_shm_root = cJSON_CreateObject(); + if (!zone_shm_root) { + fprintf(stderr, "Failed to create root JSON object\n"); + return EXIT_FAILURE; + } + cJSON *shm_regions = + cJSON_AddArrayToObject(zone_shm_root, "shm_regions"); + if (!shm_regions) { + fprintf(stderr, "Failed to create shm_regions array\n"); + cJSON_Delete(zone_shm_root); + return EXIT_FAILURE; + } + + int chunk = 0; + // Memory regions + + int exist_shm_flag = 0; + + printf("[trace] ready to mmap regions"); + for (int j = 0; j < num_mems; j++) { + cJSON *mem_region = cJSON_GetArrayItem(memory_region_json, j); + + char *region_type = + cJSON_GetObjectItem(mem_region, "type")->valuestring; + + zonex_ipa = strtoull( + cJSON_GetObjectItem(mem_region, "zonex_ipa")->valuestring, NULL, + 16); + + mem_size = strtoull( + cJSON_GetObjectItem(mem_region, "size")->valuestring, NULL, 16); + + if (mem_size == 0) { + log_error("Invalid memory size"); + continue; + } + + // split the large memory into small chunks + __u64 offset = 0; + + kmalloc_info_t kmalloc_info; + kmalloc_info.pa = 0; + kmalloc_info.size = mem_size; + + while (kmalloc_info.size > 0) { + __u64 old_memsize = kmalloc_info.size; + + long ret = ioctl(ko_fd, HVISOR_ZONE_M_ALLOC, &kmalloc_info); + __u64 zone0_ipa_chunk = kmalloc_info.pa; + if (ret) { + log_error("HVISOR_ZONE_M_ALLOC ioctl failed"); + close(ko_fd); + exit(1); + } + __u64 chunk_size = old_memsize - kmalloc_info.size; + __u64 zonex_ipa_chunk = zonex_ipa + offset; + + __u64 virt_addr_chunk = (__u64)(uintptr_t)mmap( + NULL, chunk_size, PROT_READ | PROT_WRITE, MAP_SHARED, ko_fd, + (off_t)zone0_ipa_chunk); + + if (virt_addr_chunk == (__u64)(uintptr_t)(void *)-1) { + log_error("virt_addr_chunk mmap failed"); + err = -1; + goto err_out; + } + zone_mem[zone_id][chunk][VIRT_ADDR] = + virt_addr_chunk; // the virtual address of the chunk for + // zone0_ipa + zone_mem[zone_id][chunk][ZONE0_IPA] = + zone0_ipa_chunk; // one-to-one mapping (zone0_ipa == + // zone0_hpa) + zone_mem[zone_id][chunk][ZONEX_IPA] = zonex_ipa_chunk; + zone_mem[zone_id][chunk][MEM_SIZE] = chunk_size; + + // save the memory region information to zone_ram_root(all types + // of memory regions) + cJSON *region = cJSON_CreateObject(); + char buf[128]; + + char type_str[128] = "ram"; + cJSON_AddStringToObject(region, "type", type_str); + + snprintf(buf, sizeof(buf), "0x%llx", zonex_ipa_chunk); + cJSON_AddStringToObject(region, "ipa", buf); + + snprintf(buf, sizeof(buf), "0x%llx", zone0_ipa_chunk); + cJSON_AddStringToObject(region, "hpa", buf); + + snprintf(buf, sizeof(buf), "0x%llx", chunk_size); + cJSON_AddStringToObject(region, "size", buf); + + cJSON_AddItemToArray(memory_regions, region); + + // save the shm region information to zone_shm_root(only for + // shm) + if (strncmp(region_type, "shm", 3) == 0) { + cJSON *region = cJSON_CreateObject(); + char buf[128]; + + cJSON_AddStringToObject( + region, "flag", + cJSON_GetObjectItem(mem_region, "flag")->valuestring); + + snprintf(buf, sizeof(buf), "0x%llx", + zone0_ipa_chunk); // for linux, zone0_ram_ipa == + // zone0_ram_hpa + cJSON_AddStringToObject(region, "zone0_ram_ipa", + buf); // used for linux -> zonex (in + // linux address space) + + snprintf(buf, sizeof(buf), "0x%llx", + zonex_ipa_chunk); // for linux, zone0_ram_ipa == + // zone0_ram_hpa + cJSON_AddStringToObject(region, "zonex_ram_ipa", + buf); // used for zonex -> zonex (in + // zonex address space) + + snprintf(buf, sizeof(buf), "0x%llx", chunk_size); + cJSON_AddStringToObject(region, "size", buf); + + cJSON_AddItemToArray(shm_regions, region); + + exist_shm_flag = 1; + } + + offset += chunk_size; // decrease + chunk++; + } + } + + num_devices = cJSON_GetArraySize(devices_json); + for (int j = 0; j < num_devices; j++) { + cJSON *device = cJSON_GetArrayItem(devices_json, j); + err = create_virtio_device_from_json(device, zone_id); + if (err) { + log_error("create virtio device failed"); + goto err_out; + } + + // create virtio mapping region for non root zone + char *enable = cJSON_GetObjectItem(device, "status")->valuestring; + if (strcmp(enable, "disable") == 0) { + continue; + } + char *base_addr_str = + cJSON_GetObjectItem(device, "addr")->valuestring; + char *len_str = cJSON_GetObjectItem(device, "len")->valuestring; + cJSON *virtio_region = cJSON_CreateObject(); + + char type_str[128] = "virtio"; + cJSON_AddStringToObject(virtio_region, "type", type_str); + cJSON_AddStringToObject(virtio_region, "hpa", base_addr_str); + cJSON_AddStringToObject(virtio_region, "ipa", base_addr_str); + cJSON_AddStringToObject(virtio_region, "size", len_str); + + cJSON_AddItemToArray(memory_regions, virtio_region); + } + + // save zone_ram_root to zone_ram_json + char *json_string = cJSON_Print(zone_ram_root); + if (!json_string) { + fprintf(stderr, "Failed to print JSON\n"); + cJSON_Delete(zone_ram_root); + goto err_out; + } + char filename[64]; + snprintf(filename, sizeof(filename), "zone%d_ram.json", i + 1); + FILE *fp = fopen(filename, "w"); + if (!fp) { + perror("Failed to open file for writing"); + free(json_string); + cJSON_Delete(zone_ram_root); + goto err_out; + } + fprintf(fp, "%s", json_string); + fclose(fp); + free(json_string); + cJSON_Delete(zone_ram_root); + + // save zone_shm_root to zone_shm_json + if (exist_shm_flag) { + json_string = cJSON_Print(zone_shm_root); + if (!json_string) { + fprintf(stderr, "Failed to print JSON\n"); + cJSON_Delete(zone_shm_root); + goto err_out; + } + snprintf(filename, sizeof(filename), "zone%d_shm.json", i + 1); + fp = fopen(filename, "w"); + if (!fp) { + perror("Failed to open file for writing"); + free(json_string); + cJSON_Delete(zone_shm_root); + goto err_out; + } + fprintf(fp, "%s", json_string); + fclose(fp); + free(json_string); + cJSON_Delete(zone_shm_root); + printf("SHM JSON file created successfully: %s\n", filename); + } + } + +err_out: + cJSON_Delete(root); + free(buffer); + return err; +} + int virtio_start(int argc, char *argv[]) { - int opt, err = 0; + int err = 0; + (void)argc; + + printf("virtio start, test!!!"); err = virtio_init(); // Initialize virtio dependencies if (err) return -1; +#ifdef LOONGARCH64 + err = virtio_start_from_json_dynamic(argv[3]); +#else err = virtio_start_from_json( argv[3]); // Start virtio devices based on virtio_cfg_*.json +#endif + + printf("[trace] virtio start from json over"); if (err) goto err_out;