From a61b06df5ae179d2367a2385a9bd37737a88250d Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 17:07:56 +0800 Subject: [PATCH 1/3] fix(loongarch64): model virtio IRQ line state --- driver/hvisor_main.c | 17 +++- include/hvisor.h | 9 ++- tools/virtio/include/virtio.h | 5 +- tools/virtio/virtio.c | 146 +++++++++++++++++++++++++++------- 4 files changed, 141 insertions(+), 36 deletions(-) diff --git a/driver/hvisor_main.c b/driver/hvisor_main.c index 12700905..17a2fbd0 100644 --- a/driver/hvisor_main.c +++ b/driver/hvisor_main.c @@ -93,6 +93,19 @@ static int hvisor_finish_req(void) { return 0; } +#ifdef LOONGARCH64 +static int hvisor_deassert_irq(struct hvisor_irq_line_args __user *arg) { + struct hvisor_irq_line_args kargs; + + if (copy_from_user(&kargs, arg, sizeof(kargs))) + return -EFAULT; + + pr_debug("hvisor.ko: deassert zone=%u irq=%u\n", kargs.zone_id, + kargs.irq_id); + return hvisor_call(HVISOR_HC_CLEAR_INJECT_IRQ, kargs.zone_id, kargs.irq_id); +} +#endif + // Flush mapped cache range to memory. static int flush_cache_mapped(void *vaddr, __u64 size) { unsigned long start, end, addr, line_size; @@ -375,8 +388,8 @@ static long hvisor_ioctl(struct file *file, unsigned int ioctl, break; #endif #ifdef LOONGARCH64 - case HVISOR_CLEAR_INJECT_IRQ: - err = hvisor_call(HVISOR_HC_CLEAR_INJECT_IRQ, 0, 0); + case HVISOR_DEASSERT_IRQ: + err = hvisor_deassert_irq((struct hvisor_irq_line_args __user *)arg); break; #endif default: diff --git a/include/hvisor.h b/include/hvisor.h index 675715e5..caa74107 100644 --- a/include/hvisor.h +++ b/include/hvisor.h @@ -42,6 +42,11 @@ struct device_res { __u32 irq_id; }; +struct hvisor_irq_line_args { + __u32 zone_id; + __u32 irq_id; +}; + struct virtio_bridge { __u32 req_front; __u32 req_rear; @@ -215,8 +220,8 @@ struct hvisor_scmi_power_args { #ifdef LOONGARCH64 -#define HVISOR_CLEAR_INJECT_IRQ _IO(1, 6) // used for ioctl -#define HVISOR_HC_CLEAR_INJECT_IRQ 20 // hvcall code in hvisor +#define HVISOR_DEASSERT_IRQ _IOW(1, 11, struct hvisor_irq_line_args) +#define HVISOR_HC_CLEAR_INJECT_IRQ 11 // hvcall code in hvisor #endif /* LOONGARCH64 */ #ifdef LOONGARCH64 diff --git a/tools/virtio/include/virtio.h b/tools/virtio/include/virtio.h index 2eadb0c0..996eeb6e 100644 --- a/tools/virtio/include/virtio.h +++ b/tools/virtio/include/virtio.h @@ -34,9 +34,6 @@ typedef struct VirtMmioRegs { uint32_t queue_sel; // The driver frontend writes this register to select // which virtqueue to use uint32_t interrupt_status; - // interrupt_count doesn't exist in virtio specification, - // only used for ensuring the correctness of interrupt_status. - uint32_t interrupt_count; uint32_t status; uint32_t generation; uint64_t dev_feature; // device_features @@ -130,6 +127,8 @@ struct VirtIODevice { void (*status_changed)(VirtIODevice *vdev, uint32_t status); // Called on STATUS register write bool activated; // Whether the current virtio device is activated + pthread_mutex_t interrupt_lock; + bool interrupt_line_asserted; }; // used event idx for driver telling device when to notify driver. diff --git a/tools/virtio/virtio.c b/tools/virtio/virtio.c index b31b4f37..15e9fe1c 100644 --- a/tools/virtio/virtio.c +++ b/tools/virtio/virtio.c @@ -67,6 +67,32 @@ volatile struct virtio_bridge *virtio_bridge; pthread_mutex_t RES_MUTEX = PTHREAD_MUTEX_INITIALIZER; VirtIODevice *vdevs[MAX_DEVS]; int vdevs_num; +static _Atomic uint64_t virtio_irq_trace_seq; + +static bool virtio_trace_sample(uint64_t seq) { + return seq < 128 || (seq != 0 && (seq & (seq - 1)) == 0); +} + +static int virtio_deassert_line_locked(VirtIODevice *vdev) { +#ifdef LOONGARCH64 + struct hvisor_irq_line_args args = { + .zone_id = vdev->zone_id, + .irq_id = vdev->irq_id, + }; + int ret; + do { + ret = ioctl(ko_fd, HVISOR_DEASSERT_IRQ, &args); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + log_error("deassert failed: zone=%u irq=%u errno=%d (%s)", + vdev->zone_id, vdev->irq_id, errno, strerror(errno)); + } + return ret; +#else + (void)vdev; + return 0; +#endif +} struct zone_mem_region { uintptr_t virt_addr; @@ -192,6 +218,8 @@ VirtIODevice *create_virtio_device(VirtioDeviceType dev_type, uint32_t zone_id, vdev->zone_id = zone_id; vdev->irq_id = irq_id; vdev->type = dev_type; + pthread_mutex_init(&vdev->interrupt_lock, NULL); + vdev->interrupt_line_asserted = false; switch (dev_type) { case VirtioTBlock: @@ -364,9 +392,17 @@ void init_mmio_regs(VirtMmioRegs *regs, VirtioDeviceType type) { void virtio_dev_reset(VirtIODevice *vdev) { // When driver read first 4 encoded messages, it will reset dev. log_debug("virtio dev reset"); + pthread_mutex_lock(&vdev->interrupt_lock); + if (vdev->interrupt_line_asserted) { + if (virtio_deassert_line_locked(vdev) == 0) { + vdev->interrupt_line_asserted = false; + } + } + if (!vdev->interrupt_line_asserted) { + vdev->regs.interrupt_status = 0; + } + pthread_mutex_unlock(&vdev->interrupt_lock); vdev->regs.status = 0; - vdev->regs.interrupt_status = 0; - vdev->regs.interrupt_count = 0; int idx = vdev->regs.queue_sel; vdev->vqs[idx].ready = 0; for (uint32_t i = 0; i < vdev->vqs_len; i++) { @@ -856,16 +892,21 @@ uint64_t virtio_mmio_read(VirtIODevice *vdev, uint64_t offset, unsigned size) { case VIRTIO_MMIO_QUEUE_READY: log_debug("read VIRTIO_MMIO_QUEUE_READY"); return vdev->vqs[vdev->regs.queue_sel].ready; - case VIRTIO_MMIO_INTERRUPT_STATUS: - log_debug("(%s) current interrupt status is %d", __func__, - vdev->regs.interrupt_status); -#ifdef LOONGARCH64 - // clear lvz gintc irq injection bit to avoid endless interrupt... - log_warn( - "clear lvz gintc irq injection bit to avoid endless interrupt..."); - ioctl(ko_fd, HVISOR_CLEAR_INJECT_IRQ); -#endif - return vdev->regs.interrupt_status; + case VIRTIO_MMIO_INTERRUPT_STATUS: { + pthread_mutex_lock(&vdev->interrupt_lock); + uint32_t interrupt_status = vdev->regs.interrupt_status; + pthread_mutex_unlock(&vdev->interrupt_lock); + uint64_t trace_seq = atomic_fetch_add_explicit(&virtio_irq_trace_seq, 1, + memory_order_relaxed); + if (virtio_trace_sample(trace_seq)) { + log_info("[VDBG:status-read] seq=%llu zone=%u dev=%s irq=%u " + "status=%#x", + (unsigned long long)trace_seq, vdev->zone_id, + virtio_device_type_to_string(vdev->type), vdev->irq_id, + interrupt_status); + } + return interrupt_status; + } case VIRTIO_MMIO_STATUS: log_debug("read VIRTIO_MMIO_STATUS"); return vdev->regs.status; @@ -987,20 +1028,36 @@ void virtio_mmio_write(VirtIODevice *vdev, uint64_t offset, uint64_t value, virtio_device_type_to_string(vdev->type)); break; - case VIRTIO_MMIO_INTERRUPT_ACK: - log_debug("write VIRTIO_MMIO_INTERRUPT_ACK"); - - if (value == regs->interrupt_status && regs->interrupt_count > 0) { - regs->interrupt_count--; - break; - } else if (value != regs->interrupt_status) { - log_error("interrupt_status %d is not equal to ack %d, type is %d", - regs->interrupt_status, value, vdev->type); + case VIRTIO_MMIO_INTERRUPT_ACK: { + pthread_mutex_lock(&vdev->interrupt_lock); + uint32_t status_before = regs->interrupt_status; + uint32_t ack = + (uint32_t)value & (VIRTIO_MMIO_INT_VRING | VIRTIO_MMIO_INT_CONFIG); + uint32_t status_after = status_before & ~ack; + int deassert_ret = 0; + if (status_before != 0 && status_after == 0 && + vdev->interrupt_line_asserted) { + deassert_ret = virtio_deassert_line_locked(vdev); + } + if (deassert_ret == 0) { + regs->interrupt_status = status_after; + if (status_after == 0) + vdev->interrupt_line_asserted = false; + } else { + status_after = status_before; } - regs->interrupt_status &= !value; - log_debug("(%s) clearing! interrupt_status -> %d", __func__, - regs->interrupt_status); + uint64_t trace_seq = atomic_fetch_add_explicit(&virtio_irq_trace_seq, 1, + memory_order_relaxed); + if (virtio_trace_sample(trace_seq)) { + log_info("[VDBG:ack] seq=%llu zone=%u dev=%s irq=%u before=%#x " + "ack=%#x after=%#x deassert_ret=%d", + (unsigned long long)trace_seq, vdev->zone_id, + virtio_device_type_to_string(vdev->type), vdev->irq_id, + status_before, ack, status_after, deassert_ret); + } + pthread_mutex_unlock(&vdev->interrupt_lock); break; + } case VIRTIO_MMIO_STATUS: log_debug("write VIRTIO_MMIO_STATUS"); @@ -1088,6 +1145,21 @@ void virtio_inject_irq(VirtQueue *vq) { return; } } + pthread_mutex_lock(&vq->dev->interrupt_lock); + vq->dev->regs.interrupt_status |= VIRTIO_MMIO_INT_VRING; + if (vq->dev->interrupt_line_asserted) { + uint64_t trace_seq = atomic_fetch_add_explicit(&virtio_irq_trace_seq, 1, + memory_order_relaxed); + if (virtio_trace_sample(trace_seq)) { + log_info("[VDBG:assert-merge] seq=%llu zone=%u dev=%s irq=%u " + "status=%#x", + (unsigned long long)trace_seq, vq->dev->zone_id, + virtio_device_type_to_string(vq->dev->type), + vq->dev->irq_id, vq->dev->regs.interrupt_status); + } + pthread_mutex_unlock(&vq->dev->interrupt_lock); + return; + } volatile struct device_res *res; // virtio_bridge is a global resource located in shared memory. @@ -1109,12 +1181,28 @@ void virtio_inject_irq(VirtQueue *vq) { write_barrier(); virtio_bridge->res_rear = (res_rear + 1) & (MAX_REQ - 1); write_barrier(); - vq->dev->regs.interrupt_status = VIRTIO_MMIO_INT_VRING; - vq->dev->regs.interrupt_count++; pthread_mutex_unlock(&RES_MUTEX); - log_debug("inject irq to device %s, vq is %d", - virtio_device_type_to_string(vq->dev->type), vq->vq_idx); - ioctl(ko_fd, HVISOR_FINISH_REQ); + int ret; + do { + ret = ioctl(ko_fd, HVISOR_FINISH_REQ); + } while (ret < 0 && errno == EINTR); + if (ret < 0) { + log_error("assert failed: zone=%u irq=%u errno=%d (%s)", + vq->dev->zone_id, vq->dev->irq_id, errno, strerror(errno)); + } + if (ret == 0) + vq->dev->interrupt_line_asserted = true; + uint64_t trace_seq = atomic_fetch_add_explicit(&virtio_irq_trace_seq, 1, + memory_order_relaxed); + if (virtio_trace_sample(trace_seq)) { + log_info("[VDBG:assert] seq=%llu zone=%u dev=%s irq=%u vq=%u " + "status=%#x line=%u ret=%d", + (unsigned long long)trace_seq, vq->dev->zone_id, + virtio_device_type_to_string(vq->dev->type), vq->dev->irq_id, + (unsigned)vq->vq_idx, vq->dev->regs.interrupt_status, + vq->dev->interrupt_line_asserted, ret); + } + pthread_mutex_unlock(&vq->dev->interrupt_lock); } void virtio_finish_cfg_req(uint32_t target_cpu, uint64_t value) { From 479e68e22181fc0d94b39a80d0c3c103c91c48d4 Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 17:08:11 +0800 Subject: [PATCH 2/3] fix(ivc): harden MMIO and IRQ initialization --- driver/ivc_driver.c | 113 ++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/driver/ivc_driver.c b/driver/ivc_driver.c index 1effe299..88f3a40b 100644 --- a/driver/ivc_driver.c +++ b/driver/ivc_driver.c @@ -65,9 +65,16 @@ extern u8 __dtb_hivc_template_begin[], __dtb_hivc_template_end[]; static int hvisor_ivc_info(void) { int err = 0; - if (ivc_info == NULL) - ivc_info = kmalloc(sizeof(ivc_info_t), GFP_KERNEL); + if (ivc_info == NULL) { + ivc_info = kzalloc(sizeof(ivc_info_t), GFP_KERNEL); + if (!ivc_info) + return -ENOMEM; + } err = hvisor_call(HVISOR_HC_IVC_INFO, __pa(ivc_info), sizeof(ivc_info_t)); + if (err) + return err; + if (ivc_info->len == 0 || ivc_info->len > CONFIG_MAX_IVC_CONFIGS) + return -EINVAL; return err; } @@ -167,51 +174,20 @@ static irqreturn_t ivc_irq_handler(int irq, void *dev_id) { return IRQ_HANDLED; } -// static struct property *alloc_property(const char *name, int len) { -// struct property *prop; -// prop = kzalloc(sizeof(struct property), GFP_KERNEL); -// prop->name = kstrdup(name, GFP_KERNEL); -// prop->length = len; -// prop->value = kzalloc(len, GFP_KERNEL); -// return prop; -// } - -// static int add_ivc_device_node(void) -// { -// int err, i, j, overlay_id; -// struct device_node *node = NULL; -// struct property *prop; -// u32* values; -// err = of_overlay_fdt_apply(__dtb_hivc_template_begin, -// __dtb_hivc_template_end - __dtb_hivc_template_begin, -// &overlay_id); -// if (err) return err; - -// struct of_changeset overlay_changeset; -// of_changeset_init(&overlay_changeset); -// node = of_find_node_by_path("/hvisor_ivc_device"); - -// // TODO: Add detection for gic interrupt cell and error handling -// prop = alloc_property("interrupts", sizeof(u32)*3*dev_len); -// values = prop->value; -// for(i=0; iivc_irqs[i] - 32; -// values[j++] = 0x01; -// } -// of_changeset_add_property(&overlay_changeset, node, prop); -// of_changeset_apply(&overlay_changeset); -// return 0; -// } - static int __init ivc_init(void) { int err, i, soft_irq; struct device_node *node = NULL; - hvisor_ivc_info(); + err = hvisor_ivc_info(); + if (err) + goto err1; dev_len = ivc_info->len; - ivc_devs = kmalloc(sizeof(struct ivc_dev) * dev_len, GFP_KERNEL); + pr_info("ivc: hypervisor reports %d channel(s)\n", dev_len); + ivc_devs = kcalloc(dev_len, sizeof(struct ivc_dev), GFP_KERNEL); + if (!ivc_devs) { + err = -ENOMEM; + goto err1; + } err = alloc_chrdev_region(&mdev_id, 0, dev_len, "hivc"); if (err) goto err1; @@ -233,6 +209,7 @@ static int __init ivc_init(void) { ivc_devs[i].ivc_id = ivc_info->ivc_ids[i]; ivc_devs[i].dev_id = MKDEV(MAJOR(mdev_id), i); ivc_devs[i].idx = i; + ivc_devs[i].ivc_irq = -1; ivc_devs[i].cdev.owner = THIS_MODULE; ivc_devs[i].received_irq = 0; init_waitqueue_head(&ivc_devs[i].wq); @@ -254,13 +231,20 @@ static int __init ivc_init(void) { } else { for (i = 0; i < dev_len; i++) { soft_irq = of_irq_get(node, i); + if (soft_irq < 0) { + pr_err("ivc: of_irq_get channel %d failed (%d)\n", i, soft_irq); + continue; + } err = request_irq(soft_irq, ivc_irq_handler, IRQF_SHARED | IRQF_TRIGGER_RISING, "hvisor_ivc_device", &ivc_devs[i]); if (err) { - pr_err("request irq failed\n"); + pr_err("ivc: request irq channel %d failed (%d)\n", i, err); goto err2; } + ivc_devs[i].ivc_irq = soft_irq; + pr_info("ivc: ch%d id=%d linux_irq=%d hv_irq=%u\n", i, + ivc_devs[i].ivc_id, soft_irq, ivc_info->ivc_irqs[i]); } } of_node_put(node); @@ -269,8 +253,13 @@ static int __init ivc_init(void) { err2: for (i = 0; i < dev_len; i++) { + if (ivc_devs[i].ivc_irq >= 0) { + free_irq(ivc_devs[i].ivc_irq, &ivc_devs[i]); + ivc_devs[i].ivc_irq = -1; + } cdev_del(&ivc_devs[i].cdev); - device_destroy(ivc_class, ivc_devs[i].dev_id); + if (ivc_class && !IS_ERR(ivc_class)) + device_destroy(ivc_class, ivc_devs[i].dev_id); } class_destroy(ivc_class); unregister_chrdev_region(mdev_id, dev_len); @@ -281,7 +270,39 @@ static int __init ivc_init(void) { } static void __exit ivc_exit(void) { - // TODO + int i; + + if (!ivc_devs || dev_len <= 0) { + kfree(ivc_info); + ivc_info = NULL; + pr_info("ivc exit done!!!\n"); + return; + } + /* Free the IRQ first to prevent user-space and kernel paths from being + * woken. */ + for (i = 0; i < dev_len; i++) { + if (ivc_devs[i].ivc_irq >= 0) { + free_irq(ivc_devs[i].ivc_irq, &ivc_devs[i]); + ivc_devs[i].ivc_irq = -1; + } + } + + /* Destroy the character devices and sysfs device nodes. */ + if (ivc_class && !IS_ERR(ivc_class)) { + for (i = 0; i < dev_len; i++) { + cdev_del(&ivc_devs[i].cdev); + device_destroy(ivc_class, ivc_devs[i].dev_id); + } + class_destroy(ivc_class); + } + unregister_chrdev_region(mdev_id, dev_len); + + kfree(ivc_devs); + ivc_devs = NULL; + kfree(ivc_info); + ivc_info = NULL; + dev_len = 0; + pr_info("ivc exit done!!!\n"); } @@ -300,4 +321,4 @@ module_exit(ivc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("KouweiLee <15035660024@163.com>"); MODULE_DESCRIPTION("The hvisor device driver"); -MODULE_VERSION("1:0.0"); \ No newline at end of file +MODULE_VERSION("1:0.0"); From 7f917460fa0f74f3c50bee8ce76386769bbbac40 Mon Sep 17 00:00:00 2001 From: wheatfox Date: Tue, 21 Jul 2026 20:20:35 +0800 Subject: [PATCH 3/3] fix(cli): propagate zone start ioctl failures --- tools/hvisor.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/hvisor.c b/tools/hvisor.c index b9e07b31..ee9b6a04 100644 --- a/tools/hvisor.c +++ b/tools/hvisor.c @@ -990,7 +990,7 @@ static int zone_start_from_json(const char *json_config_path, close(fd); - return 0; + return err; err_out: if (root) cJSON_Delete(root);