From nobody Wed Aug 26 18:48:37 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) client-ip=192.237.175.120; envelope-from=xen-devel-bounces@lists.xenproject.org; helo=lists.xenproject.org; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) smtp.mailfrom=xen-devel-bounces@lists.xenproject.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1783353565; cv=none; d=zohomail.com; s=zohoarc; b=byeeHqJsX3CuU1AUB1wq28AhEZnmBq21Ee0D3fo1uBiCuWlQs8stqgKp2FJx6EhXnXlh0rrzfkf/+Gnrp7wfV1vm973Fob5prCa2o8RbYuBTmfAX7rluXeuLyGi988VAkcs13mAYYaldsYo5k4z7PrsusHoezuCFi2ez3p85kOY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783353565; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=qhDSRqZCa8FiBzhpxge5NcsdKHKmZpIkmXlsTE8qD7o=; b=kBNc57LFIXjLrcxyycfr66vVNhyse9Tm21WMjf1Q+DdhSMdsEiC4C0f5wXOY3fHkTQherFuIUElkrfgLtC1gEOHKfjC4HBujgb1wcWs1LH+NHkSp5DVh/Wu0KnwCW54nYHV3Z+n4BdyglOIqgtfyaBisxBRkCZGByugLBGtgtec= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of lists.xenproject.org designates 192.237.175.120 as permitted sender) smtp.mailfrom=xen-devel-bounces@lists.xenproject.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) by mx.zohomail.com with SMTPS id 1783353565540969.8116671990264; Mon, 6 Jul 2026 08:59:25 -0700 (PDT) Received: from list by lists.xenproject.org with outflank-mailman.1355542.1610438 (Exim 4.92) (envelope-from ) id 1wglih-0004Yn-31; Mon, 06 Jul 2026 15:58:55 +0000 Received: by outflank-mailman (output) from mailman id 1355542.1610438; Mon, 06 Jul 2026 15:58:54 +0000 Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.92) (envelope-from ) id 1wglig-0004Tv-2C; Mon, 06 Jul 2026 15:58:54 +0000 Received: by outflank-mailman (input) for mailman id 1355542; Mon, 06 Jul 2026 15:58:49 +0000 Received: from mx.expurgate.net ([195.190.135.10]) by lists.xenproject.org with esmtp (Exim 4.92) id 1wgliZ-00038y-QU for xen-devel@lists.xenproject.org; Mon, 06 Jul 2026 15:58:48 +0000 Received: from mx.expurgate.net (helo=localhost) by mx.expurgate.net with esmtp id 1wgliZ-00AcZM-2B for xen-devel@lists.xenproject.org; Mon, 06 Jul 2026 17:58:47 +0200 Received: from [10.42.69.9] (helo=localhost) by localhost with ESMTP (eXpurgate MTA 0.9.1) (envelope-from ) id 6a4bd09e-5cb7-0a2a0a5109dd-0a2a4509a9ae-26 for ; Mon, 06 Jul 2026 17:58:47 +0200 Received: from [209.85.221.47] (helo=mail-wr1-f47.google.com) by tlsNG-bad1c0.mxtls.expurgate.net with ESMTPS (eXpurgate 4.57.1) (envelope-from ) id 6a4bd0b6-97e6-0a2a45090019-d155dd2fc94c-3 for ; Mon, 06 Jul 2026 17:58:47 +0200 Received: by mail-wr1-f47.google.com with SMTP id ffacd0b85a97d-4703bc0a99aso1762405f8f.3 for ; Mon, 06 Jul 2026 08:58:46 -0700 (PDT) Received: from fedora (user-109-243-144-234.play-internet.pl. [109.243.144.234]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-493cce03fa1sm284910325e9.11.2026.07.06.08.58.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Jul 2026 08:58:45 -0700 (PDT) X-Outflank-Mailman: Message body and most headers restored to incoming version X-BeenThere: xen-devel@lists.xenproject.org List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Errors-To: xen-devel-bounces@lists.xenproject.org Precedence: list Sender: "Xen-devel" Authentication-Results: eu.smtp.expurgate.cloud; dkim=pass header.s=20251104 header.d=gmail.com header.i="@gmail.com" header.h="Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID:Date:Subject:Cc:To:From" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783353526; x=1783958326; darn=lists.xenproject.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qhDSRqZCa8FiBzhpxge5NcsdKHKmZpIkmXlsTE8qD7o=; b=nMXQ+opT4rriqH71Vu7lhQenbFWxJffsFH8qVLBn7+KFi7A1oPmzEzLzOcYURbCHbm fgLhPG5YgRNMtpgr1cURPug3GifN/MSCOwxvoZW30KNBJ4+DXUhv/Y4RX39vK/GbcENN nMrF1a8KJFUAqeZdzmhYvQfBHEA+rtfll+8t2MRmsJVy3z1MdmZFr8gLDvIyWQZfBtpv 8CXRgXns1xyLOLmTgxe54zNzoyr9ur0UMFfUVC13KXAP2YgwV4hK1ABGQx0fGQwZTIMt PvmN6sR5b5/7hDkf1FlA5aA6stVAP246+r2tPIx1x2PY3h73qFj6kI7+LzSGQRhrDIlK br6A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783353526; x=1783958326; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qhDSRqZCa8FiBzhpxge5NcsdKHKmZpIkmXlsTE8qD7o=; b=hAJ5UuJV3OIn1KcRx0afXseYOyV56886NLDpKtw2azkbXQVaWG8EpuLq5+BVvaHwOG CABbsamzRzNuLJdqYYxiU8kXevhAKztGcL9lLEaiOnDSD1gyEiQqrww281C1olGsbkLD h2cLN3XqfOWLorSXUVdjIJVDBAD+14R4m/gKiopXrkVfUmk2yL9+Cm4WmW5HB5VOArdF hixFD2uBm0rV32etGnCWAUZBU0a89aZtAG5Qk6C2Qrkg/Z4244+udcQ5xr5wMussePOX CEXJkCGowrbqcwwStYESfYh+sqnQVfnOHFpKeu2XLpF0L0w1WETwaAGJkpQQPXi/Xaeq A2ug== X-Gm-Message-State: AOJu0YwCM7mg5vZlnn0NxZmZT1B9JS5Aw9PyUA57pbBpTCDgIhOAR4UE Z5TFM9Lvef2Z0uVyuxD156p+v7hZuerpBU9hP84tDJdnARGZu1Y6LtjbwfhIZw== X-Gm-Gg: AfdE7cmmlNd6Ge7zbOJbwuMsZwkx/z+d/sqaASK8FvDzv9NKExJJjsJcoYKYqHyqC9q DxqX0mL5gikXbwNQxI0F/zClbUhdhK88OKmkXm+4GAgKJhOqMzvHh+GjPpSXIIrqyjuHv9hFngM fHc2iAJ31F1l4lqrz7XRo6D0Dj8ZERY2i7QgYRZk8WlNnu9WnAS/HA53Pz8Y15iJj9Ib8Jv4Mzk dIJP+D1w+hKHb2JLfIWGaAnvWv6hrOn02I3AsIBynZwHKxqFyPqJYJffcJgHemuO+CCU63XFU2S 3UWZ8Qu1gsYRmfmjaa0+uqQHpj0yzsD3QT5b7JgiEGsYUwLyJ4J2wKYwY0djBkz3m+IMfaJB0N1 4gTfpcuc6ag94JMGIHnP2FEWx0ksKKmpENmRTR2pTzrc9RUWdHINVSPWePu5YjJPKB7/PIDsmP3 aiaW+GI3gJFlbcncyno13RDPwNL4MRPdKwugOphxRbn04kXvtKoRO0qNg2kg== X-Received: by 2002:a05:600c:5897:b0:492:4cce:53e2 with SMTP id 5b1f17b1804b1-493df0412f5mr11029555e9.3.1783353526179; Mon, 06 Jul 2026 08:58:46 -0700 (PDT) From: Oleksii Kurochko To: xen-devel@lists.xenproject.org Cc: Romain Caritey , Baptiste Le Duc , Oleksii Kurochko , Alistair Francis , Connor Davis , "Daniel P. Smith" Subject: [PATCH v5 21/26] xen/riscv: implement IRQ routing for device passthrough Date: Mon, 6 Jul 2026 17:58:02 +0200 Message-ID: X-Mailer: git-send-email 2.54.0 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-purgate-ID: tlsNG-bad1c0/1783353527-46D3C986-51781026/10/73395122804 X-purgate-type: spam X-purgate-size: 22158 X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1783353566989158500 Content-Type: text/plain; charset="utf-8" dom0less device passthrough requires granting guest domains access to device interrupts. Introduce map_device_irqs_to_domain() to enumerate a DT node's interrupt properties, skipping those not owned by the primary interrupt controller (as at the moment I haven't seen usages of it), and map_irq_to_domain() to grant domain access and configure Xen's interrupt descriptor accordingly. Sharing IRQ between domains is rejected. Both map_irq_to_domain() and map_device_irqs_to_domain() are marked __overlay_init, mirroring Arm: without CONFIG_OVERLAY_DTB this expands to __init, so the functions are init-only and need no XSM check; with CONFIG_OVERLAY_DTB they become runtime-callable, but the only runtime entry point is dt_overlay_domctl(), which performs the XSM checks at the domctl layer. RISC-V does not wire up DT overlay yet, so today these are strictly __init; if/when overlay support is added, the domctl-level XSM gating must be added together with it, as on Arm. route_irq_to_guest() and release_irq() manage irq_desc ownership for guest-assigned interrupts. Each assignment carries a small irq_guest structure as irqaction::dev_id, recording the owning domain and virtual IRQ number which is 1:1 mapped to physical IRQ number. A per-domain vIRQ allocation bitmap (used_irqs in struct vintc), managed by vintc_reserve_virq(), prevents the same vIRQ being claimed twice. Host and guest interrupts may differ in some operations (EOI timing in particular, possibly others): a host IRQ is completed once Xen's handler runs, whereas a passthrough IRQ must defer the physical completion until the guest issues its own EOI, otherwise a still-asserted level line would immediately retrigger and storm. This affects only the .end callback; the rest of hw_interrupt_type is shared, hence the separate host and guest hw_interrupt_type instances. With APLIC+IMSIC, guest interrupts are delivered directly by hardware through the IMSIC, bypassing do_IRQ(). The _IRQ_GUEST branch in do_IRQ() is therefore left as BUG() until a platform without direct IMSIC delivery is encountered. Signed-off-by: Oleksii Kurochko Acked-by: Jan Beulich --- Changes in v5: - add early -EINVAL return in route_irq_to_guest() if domain is dying - use __clear_bit() instead of clear_bit() in release_guest_irq() since desc->lock is already held - remove irq_get_domain() wrapper; inline irq_get_guest_info(desc)->d at its single call site - reword IRQ_GUEST comment in do_IRQ() for clarity - move XVFREE(used_irqs) before the switch so it is freed prior to variant-specific vintc teardown - fix missing space in dt_dprintk() format string split across lines - Drop 'inline' for irq_get_guest_info() and leave it only static. - Drop xfree(info) from release_guest_irq() to avoid a potential dangling-pointer issue with the ->dev_id field. Now that 'struct irqaction action;' is embedded into 'struct irq_guest', 'info' will be freed as part of release_irq() at the end. --- Changes in v4: - Update the commit message. - Mark map_irq_to_domain() and map_device_irqs_to_domain() as __overlay_init (mirroring Arm) and include . - Fix grammar in the controller-skip comment ("IRQ" -> "IRQs"). - Drop the redundant 'base' local in guest_imsic_make_reg_property(); use GUEST_IMSIC_S_BASE directly. - Rename vintc::irq_nums -> nr_virqs and update all users. - Guard domain_vintc_deinit() against a NULL d->arch.vintc. - Use smp_rmb() instead of smp_mb() in release_irq()'s wait loop and document how it pairs with the spin_unlock() in do_IRQ(). - In release_guest_irq(), reject live unrouting from a non-dying domain (-EBUSY) and clear _IRQ_GUEST under desc->lock so a concurrent release for the same IRQ bails out instead of double-freeing 'info'. - Tidy spurious whitespace in release_irq()'s spin_lock/unlock calls. --- Changes in v3: - Drop extraneous "to" from "Unable to permit to %pd" message. - Move res/irq/rirq to loop scope; use nirq as declaration initializer. - Hoist irq_ranges check before the loop (it is loop-invariant). - Remove spurious forward declarations (struct dt_device_node, struct rangeset) from intc.h; remove all three from setup.h. - Use __set_bit() instead of set_bit() in intc_route_irq_to_guest() since desc->lock is always held on every write path for desc->status. - Use XVFREE() instead of xvfree() in domain_vintc_deinit(). - Rename allocated_irqs -> used_irqs in struct vintc. - Fix dangling desc->action in release_irq()'s !IRQ_HAS_MULTIPLE_ACTION path by nulling *action_ptr after saving the action pointer. - Use true (not 1) for free_on_release in route_irq_to_guest(). - Use %pd for domain printing in route_irq_to_guest() error paths. - Introduce release_guest_irq() to pair with route_irq_to_guest() and plug the irq_guest info leak; call it from domain_vintc_deinit() for each vIRQ recorded in used_irqs. --- Changes in v2: - Rework IRQ mapping in more common (similar approach to Arm). --- --- xen/arch/riscv/Makefile | 1 + xen/arch/riscv/aplic.c | 4 + xen/arch/riscv/device.c | 94 ++++++++++++ xen/arch/riscv/include/asm/intc.h | 9 ++ xen/arch/riscv/include/asm/irq.h | 5 + xen/arch/riscv/intc.c | 45 ++++++ xen/arch/riscv/irq.c | 238 ++++++++++++++++++++++++++++++ xen/arch/riscv/vaplic.c | 2 + 8 files changed, 398 insertions(+) create mode 100644 xen/arch/riscv/device.c diff --git a/xen/arch/riscv/Makefile b/xen/arch/riscv/Makefile index 9d8d21b65188..fc6b34661111 100644 --- a/xen/arch/riscv/Makefile +++ b/xen/arch/riscv/Makefile @@ -1,6 +1,7 @@ obj-y +=3D aia.o obj-y +=3D aplic.o obj-y +=3D cpufeature.o +obj-y +=3D device.o obj-y +=3D domain.o obj-y +=3D domain-build.init.o obj-$(CONFIG_DOM0LESS_BOOT) +=3D dom0less-build.init.o diff --git a/xen/arch/riscv/aplic.c b/xen/arch/riscv/aplic.c index 5f8ae4d97b87..9b9a0929a89b 100644 --- a/xen/arch/riscv/aplic.c +++ b/xen/arch/riscv/aplic.c @@ -306,9 +306,13 @@ static const hw_irq_controller aplic_xen_irq_type =3D { .set_affinity =3D aplic_set_irq_affinity, }; =20 +/* At the moment there is no difference between guest and Xen ops */ +#define aplic_guest_irq_type aplic_xen_irq_type + static const struct intc_hw_operations aplic_ops =3D { .info =3D &aplic_info, .host_irq_type =3D &aplic_xen_irq_type, + .guest_irq_type =3D &aplic_guest_irq_type, .handle_interrupt =3D aplic_handle_interrupt, .set_irq_type =3D aplic_set_irq_type, }; diff --git a/xen/arch/riscv/device.c b/xen/arch/riscv/device.c new file mode 100644 index 000000000000..f54d0fdaa7ab --- /dev/null +++ b/xen/arch/riscv/device.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include +#include +#include +#include +#include + +#include + +int __overlay_init map_irq_to_domain(struct domain *d, unsigned int irq, + bool need_mapping, const char *devnam= e) +{ + int res; + + res =3D irq_permit_access(d, irq); + if ( res ) + { + printk(XENLOG_ERR "Unable to permit %pd access to IRQ %u\n", d, ir= q); + return res; + } + + if ( need_mapping ) + { + /* + * Checking the return of vintc_reserve_virq is not + * necessary. It should not fail except when we try to map + * the IRQ twice. This can legitimately happen if the IRQ is share= d. + */ + vintc_reserve_virq(d, irq); + + res =3D route_irq_to_guest(d, irq, irq, devname); + if ( res < 0 ) + { + printk(XENLOG_ERR "Unable to map IRQ%u to %pd\n", irq, d); + return res; + } + } + + dt_dprintk(" - IRQ: %u\n", irq); + + return 0; +} + +int __overlay_init map_device_irqs_to_domain(struct domain *d, + struct dt_device_node *dev, + bool need_mapping, + struct rangeset *irq_ranges) +{ + unsigned int i, nirq =3D dt_number_of_irq(dev); + + if ( irq_ranges ) + return -EOPNOTSUPP; + + /* Give permission and map IRQs */ + for ( i =3D 0; i < nirq; i++ ) + { + int res, irq; + struct dt_raw_irq rirq; + + res =3D dt_device_get_raw_irq(dev, i, &rirq); + if ( res ) + { + printk(XENLOG_ERR "Unable to retrieve irq %u for %s\n", + i, dt_node_full_name(dev)); + return res; + } + + /* + * Don't map IRQs that have no physical meaning + * ie: IRQs whose controller is not APLIC/IMSIC/PLIC. + */ + if ( rirq.controller !=3D dt_interrupt_controller ) + { + dt_dprintk("irq %u not connected to primary controller. Connec= ted to %s\n", + i, dt_node_full_name(rirq.controller)); + continue; + } + + irq =3D platform_get_irq(dev, i); + if ( irq < 0 ) + { + printk("Unable to get irq %u for %s\n", i, dt_node_full_name(d= ev)); + return irq; + } + + res =3D map_irq_to_domain(d, irq, need_mapping, dt_node_name(dev)); + if ( res ) + return res; + } + + return 0; +} diff --git a/xen/arch/riscv/include/asm/intc.h b/xen/arch/riscv/include/asm= /intc.h index dad4c5fab08b..2ee5d1533c8e 100644 --- a/xen/arch/riscv/include/asm/intc.h +++ b/xen/arch/riscv/include/asm/intc.h @@ -15,6 +15,7 @@ enum intc_variant { }; =20 struct cpu_user_regs; +struct domain; struct irq_desc; struct kernel_info; struct vcpu; @@ -34,6 +35,9 @@ struct intc_hw_operations { /* hw_irq_controller to enable/disable/eoi host irq */ const struct hw_interrupt_type *host_irq_type; =20 + /* hw_irq_controller to enable/disable/eoi guest irq */ + const struct hw_interrupt_type *guest_irq_type; + /* Set IRQ type */ void (*set_irq_type)(struct irq_desc *desc, unsigned int type); /* Set IRQ priority */ @@ -63,6 +67,8 @@ struct vintc_ops { }; =20 struct vintc { + unsigned int nr_virqs; + unsigned long *used_irqs; /* Used during domain build only; dropped afterwards. */ const struct vintc_init_ops *init_ops; /* Runtime callbacks used for the lifetime of the guest. */ @@ -76,10 +82,13 @@ void register_intc_ops(const struct intc_hw_init_ops *i= nit_ops); void intc_init(void); =20 void intc_route_irq_to_xen(struct irq_desc *desc, unsigned int priority); +int intc_route_irq_to_guest(struct irq_desc *desc, unsigned int priority); =20 void intc_handle_external_irqs(struct cpu_user_regs *regs); =20 int domain_vintc_init(struct domain *d); void domain_vintc_deinit(struct domain *d); =20 +bool vintc_reserve_virq(const struct domain *d, unsigned int virq); + #endif /* ASM__RISCV__INTERRUPT_CONTOLLER_H */ diff --git a/xen/arch/riscv/include/asm/irq.h b/xen/arch/riscv/include/asm/= irq.h index f633636dc308..2b95f8226be2 100644 --- a/xen/arch/riscv/include/asm/irq.h +++ b/xen/arch/riscv/include/asm/irq.h @@ -49,6 +49,11 @@ void init_IRQ(void); =20 void do_IRQ(struct cpu_user_regs *regs, unsigned int irq); =20 +int route_irq_to_guest(struct domain *d, unsigned int virq, + unsigned int irq, const char *devname); + +int release_guest_irq(struct domain *d, unsigned int virq); + #endif /* ASM__RISCV__IRQ_H */ =20 /* diff --git a/xen/arch/riscv/intc.c b/xen/arch/riscv/intc.c index f5c8af6ddea4..372c8d3a20f9 100644 --- a/xen/arch/riscv/intc.c +++ b/xen/arch/riscv/intc.c @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include =20 #include #include @@ -78,6 +80,22 @@ void intc_route_irq_to_xen(struct irq_desc *desc, unsign= ed int priority) intc_set_irq_priority(desc, priority); } =20 +int intc_route_irq_to_guest(struct irq_desc *desc, + unsigned int priority) +{ + ASSERT(spin_is_locked(&desc->lock)); + + ASSERT(intc_hw_ops->guest_irq_type); + + desc->handler =3D intc_hw_ops->guest_irq_type; + __set_bit(_IRQ_GUEST, &desc->status); + + intc_set_irq_type(desc, desc->arch.type); + intc_set_irq_priority(desc, priority); + + return 0; +} + int __init make_intc_domU_node(struct kernel_info *kinfo) { const struct vintc *vintc =3D kinfo->bd.d->arch.vintc; @@ -101,12 +119,31 @@ int domain_vintc_init(struct domain *d) break; } =20 + if ( !ret ) + { + d->arch.vintc->used_irqs =3D + xvzalloc_array(unsigned long, + BITS_TO_LONGS(d->arch.vintc->nr_virqs)); + if ( !d->arch.vintc->used_irqs ) + ret =3D -ENOMEM; + } + return ret; } =20 void domain_vintc_deinit(struct domain *d) { const enum intc_variant variant =3D intc_hw_ops->info->hw_variant; + unsigned int virq; + + if ( !d->arch.vintc ) + return; + + for ( virq =3D 0; virq < d->arch.vintc->nr_virqs; virq++ ) + if ( test_bit(virq, d->arch.vintc->used_irqs) ) + release_guest_irq(d, virq); + + XVFREE(d->arch.vintc->used_irqs); =20 switch ( variant ) { @@ -118,3 +155,11 @@ void domain_vintc_deinit(struct domain *d) break; } } + +bool vintc_reserve_virq(const struct domain *d, unsigned int virq) +{ + if ( virq >=3D d->arch.vintc->nr_virqs ) + return false; + + return !test_and_set_bit(virq, d->arch.vintc->used_irqs); +} diff --git a/xen/arch/riscv/irq.c b/xen/arch/riscv/irq.c index 25d329500212..ee75e0e88d85 100644 --- a/xen/arch/riscv/irq.c +++ b/xen/arch/riscv/irq.c @@ -12,11 +12,29 @@ #include #include #include +#include #include +#include =20 #include #include =20 +/* + * Describe an IRQ assigned to a guest. + * + * The irqaction is embedded here (rather than allocated separately with + * its dev_id pointing at a standalone struct irq_guest) so that freeing + * the action in release_irq() also frees this whole structure in one go. + * That avoids the alternative of release_irq()'s caller having to free + * dev_id itself (something like in Arm release_guest_irq()). + */ +struct irq_guest +{ + struct irqaction action; + struct domain *d; + unsigned int virq; +}; + static irq_desc_t irq_desc[NR_IRQS]; =20 static bool irq_validate_new_type(unsigned int curr, unsigned int new) @@ -192,6 +210,14 @@ void do_IRQ(struct cpu_user_regs *regs, unsigned int i= rq) if ( desc->handler->ack ) desc->handler->ack(desc); =20 + if ( desc->status & IRQ_GUEST ) + /* + * With APLIC + IMSIC, guest interrupts bypass Xen and are deliver= ed + * directly to the guest. Without IMSIC, interrupts would be trapp= ed + * by Xen and would need injecting into the guest here. + */ + panic("unimplemented"); + if ( desc->status & IRQ_DISABLED ) goto out; =20 @@ -221,3 +247,215 @@ void do_IRQ(struct cpu_user_regs *regs, unsigned int = irq) spin_unlock(&desc->lock); irq_exit(); } + +static struct irq_guest *irq_get_guest_info(struct irq_desc *desc) +{ + ASSERT(spin_is_locked(&desc->lock)); + ASSERT(test_bit(_IRQ_GUEST, &desc->status)); + ASSERT(desc->action !=3D NULL); + + return desc->action->dev_id; +} + + +void release_irq(unsigned int irq, const void *dev_id) +{ + struct irq_desc *desc; + unsigned long flags; + struct irqaction *action, **action_ptr; + + desc =3D irq_to_desc(irq); + + spin_lock_irqsave(&desc->lock, flags); + + action_ptr =3D &desc->action; +#ifdef CONFIG_IRQ_HAS_MULTIPLE_ACTION + for ( ;; ) + { + action =3D *action_ptr; + if ( !action ) + { + printk(XENLOG_WARNING "Trying to free already-free IRQ %u\n", = irq); + spin_unlock_irqrestore(&desc->lock, flags); + return; + } + + if ( action->dev_id =3D=3D dev_id ) + break; + + action_ptr =3D &action->next; + } + + /* Found it - remove it from the action list */ + *action_ptr =3D action->next; +#else + action =3D *action_ptr; + *action_ptr =3D NULL; +#endif + + /* If this was the last action, shut down the IRQ */ + if ( !desc->action ) + { + desc->handler->shutdown(desc); + __clear_bit(_IRQ_GUEST, &desc->status); + } + + spin_unlock_irqrestore(&desc->lock, flags); + + /* + * Wait to make sure it's not being used on another CPU. + * + * The read barrier pairs with the spin_unlock() in do_IRQ(): once we + * observe _IRQ_INPROGRESS cleared, we are guaranteed to also see the + * writes do_IRQ() made to desc (e.g. desc->action) before releasing t= he + * lock, so it is safe to free the action below. + */ + do { smp_rmb(); } while ( test_bit(_IRQ_INPROGRESS, &desc->status) ); + + if ( action->free_on_release ) + xvfree(action); +} + +int release_guest_irq(struct domain *d, unsigned int virq) +{ + struct irq_desc *desc =3D irq_to_desc(virq); + struct irq_guest *info; + unsigned long flags; + int ret =3D -EINVAL; + + spin_lock_irqsave(&desc->lock, flags); + + if ( !test_bit(_IRQ_GUEST, &desc->status) ) + goto unlock_err; + + info =3D irq_get_guest_info(desc); + if ( d !=3D info->d ) + goto unlock_err; + + /* + * Live IRQ unrouting from a running domain is not supported: the tear= -down + * drops desc->lock across release_irq()/xvfree() and relies on no + * concurrent route_irq_to_guest() being issued for this domain. Only = permit + * it for a dying domain, where assignment is frozen and no new routes= can + * appear. + */ + if ( !d->is_dying ) + { + ret =3D -EBUSY; + goto unlock_err; + } + + /* + * Clear _IRQ_GUEST while still holding the lock so that a concurrent + * release_guest_irq() for the same IRQ observes it and bails out, rat= her + * than capturing the same 'info' and double-freeing it below. + */ + __clear_bit(_IRQ_GUEST, &desc->status); + + spin_unlock_irqrestore(&desc->lock, flags); + + release_irq(desc->irq, info); + + return 0; + + unlock_err: + spin_unlock_irqrestore(&desc->lock, flags); + return ret; +} + +/* Route an IRQ to a specific guest */ +int route_irq_to_guest(struct domain *d, unsigned int virq, + unsigned int irq, const char *devname) +{ + struct irqaction *action; + struct irq_guest *info; + struct irq_desc *desc; + unsigned long flags; + int retval =3D 0; + + if ( d->is_dying ) + return -EINVAL; + + desc =3D irq_to_desc(irq); + + /* + * release_irq() frees this action via xvfree(), relying on action + * being the first member of struct irq_guest so that &info->action + * coincides with info itself. Guard the layout so a future field + * reorder can't silently turn that into a free() of a mid-allocation + * pointer. + */ + BUILD_BUG_ON(offsetof(struct irq_guest, action) !=3D 0); + + info =3D xvmalloc(struct irq_guest); + if ( !info ) + return -ENOMEM; + + info->d =3D d; + info->virq =3D virq; + + action =3D &info->action; + action->dev_id =3D info; + action->name =3D devname; + action->free_on_release =3D true; + + spin_lock_irqsave(&desc->lock, flags); + + /* + * If the IRQ is already used by someone + * - If it's the same domain -> Xen doesn't need to update the IRQ de= sc. + * For safety check if we are not trying to assign the IRQ to a + * different vIRQ. + * - Otherwise -> For now, don't allow the IRQ to be shared between + * Xen and domains. + */ + if ( desc->action !=3D NULL ) + { + if ( test_bit(_IRQ_GUEST, &desc->status) ) + { + struct domain *ad =3D irq_get_guest_info(desc)->d; + + if ( d !=3D ad ) + { + printk(XENLOG_G_ERR "IRQ %u is already used by %pd\n", + irq, ad); + retval =3D -EBUSY; + } + else if ( irq_get_guest_info(desc)->virq !=3D virq ) + { + printk(XENLOG_G_ERR + "%pd: IRQ %u is already assigned to vIRQ %u\n", + d, irq, irq_get_guest_info(desc)->virq); + retval =3D -EBUSY; + } + } + else + { + printk(XENLOG_G_ERR "IRQ %u is already used by Xen\n", irq); + retval =3D -EBUSY; + } + goto out; + } + + retval =3D _setup_irq(desc, 0, action); + if ( retval ) + goto out; + + retval =3D intc_route_irq_to_guest(desc, IRQ_NO_PRIORITY); + + spin_unlock_irqrestore(&desc->lock, flags); + + if ( retval ) + { + release_irq(desc->irq, info); + return retval; + } + + return 0; + + out: + spin_unlock_irqrestore(&desc->lock, flags); + xvfree(info); + + return retval; +} diff --git a/xen/arch/riscv/vaplic.c b/xen/arch/riscv/vaplic.c index 719c439e48d9..2d0b31e08252 100644 --- a/xen/arch/riscv/vaplic.c +++ b/xen/arch/riscv/vaplic.c @@ -124,6 +124,8 @@ int domain_vaplic_init(struct domain *d) vaplic->regs.domaincfg =3D APLIC_DOMAINCFG_IE | APLIC_DOMAINCFG_DM | APLIC_DOMAINCFG_RO; =20 + d->arch.vintc->nr_virqs =3D guest_num_sources; + return 0; } =20 --=20 2.54.0