[PATCH v2 39/39] xen/riscv: introduce IMSIC h/w interrupt file attaching to vcpu

Oleksii Kurochko <[email protected]>
Newsgroups gmane.comp.emulators.xen.devel
Message-ID <4df9cf63943d0f371c8a25c6c9e84adbd3083a61.1787838835.git.oleksii.kurochko@gmail.com>
Introduce imsic_vsfile_attach() to initialize the AIA-related state needed
for a vCPU to have a working guest interrupt file.

A guest (VS) interrupt file must be mapped to one of a pCPU's
hardware interrupt files (if they exist), so the pCPU a vCPU will actually
run on needs to be known first. arch_vcpu_create() is therefore not a
suitable place to call vcpu_aia_init(), since the pCPU assigned to a
vCPU can still change before it is first scheduled. To avoid
reassigning the VS interrupt file id and remapping it to a different
pCPU's hardware interrupt file, imsic_vsfile_attach() is called from a
later point in the scheduling path (e.g. continue_new_vcpu()). Since
it will end up being called from a non-__init context, it is not
itself marked __init.

Introduce imsic_update_state() to update a vCPU's guest IMSIC state
(the guest interrupt file id and the pCPU whose hardware interrupt
file it is mapped to) as a single consistent unit. This state can be
read concurrently, e.g. by a future helper that checks whether a
vCPU has a pending IMSIC interrupt, though no such consumer exists
yet at this stage, so it is protected by a lock.

Signed-off-by: Oleksii Kurochko <[email protected]>
---
Changes in v2:
 - Update vcpu_aia_init() to catch sw interrupt file and update some debug
   messages in it.
 - Add vgein_release() if IMSIC h/w mapping failed.
 - imsic_update_state(): store v->processor rather than cpuid_to_hartid(),
   as ->vsfile_cpu is consumed as a Xen CPU id (aplic_hart_field(),
   cpumask_of()) and its NR_CPUS sentinel lives in that numbering space.
 - Drop parantethis aroud guest_file_id ? ... in imsic_update_state().
 - Rename vcpu_aia_init to imsic_vsfile_attach() and move the code to
   imsic.c.
---
---
 xen/arch/riscv/domain.c            |   2 +
 xen/arch/riscv/imsic.c             | 146 +++++++++++++++++++++++------
 xen/arch/riscv/include/asm/imsic.h |   2 +
 3 files changed, 121 insertions(+), 29 deletions(-)

diff --git a/xen/arch/riscv/domain.c b/xen/arch/riscv/domain.c
index 0782148b7207..15b6bfffa97d 100644
--- a/xen/arch/riscv/domain.c
+++ b/xen/arch/riscv/domain.c
@@ -155,6 +155,8 @@ static void continue_new_vcpu(struct vcpu *prev)
         reset_stack_and_jump(idle_loop);
     else
     {
+        imsic_vsfile_attach(current);
+
         /*
          * During a context switch to a new vCPU, interrupts must be disabled
          * to guarantee that the vCPU's CSR state can be safely restored into
diff --git a/xen/arch/riscv/imsic.c b/xen/arch/riscv/imsic.c
index 374a21ace15f..ad638d748517 100644
--- a/xen/arch/riscv/imsic.c
+++ b/xen/arch/riscv/imsic.c
@@ -212,7 +212,14 @@ unsigned int vcpu_guest_file_id(const struct vcpu *v)
 
 void imsic_update_state(struct vcpu *v, unsigned int guest_file_id)
 {
-    BUG_ON("unimplemented\n");
+    unsigned long flags;
+    struct vimsic_state *vimsic_state = v->arch.vimsic_state;
+    unsigned int cpu = guest_file_id ? v->processor : NR_CPUS;
+
+    write_lock_irqsave(&vimsic_state->vsfile_lock, flags);
+    vimsic_state->guest_file_id = guest_file_id;
+    vimsic_state->vsfile_cpu = cpu;
+    write_unlock_irqrestore(&vimsic_state->vsfile_lock, flags);
 }
 
 void __init imsic_ids_local_delivery(bool enable)
@@ -640,6 +647,16 @@ struct imsic_vsfile_data {
     struct imsic_mrif *mrif;
 };
 
+/*
+ * Number of 64-bit EIx groups needed to cover all the interrupt identities an
+ * IMSIC interrupt file provides, which are 0 (never valid, but it still
+ * occupies a bit) up to and including imsic_cfg.nr_ids.
+ */
+static unsigned int imsic_nr_eix(void)
+{
+    return DIV_ROUND_UP(imsic_cfg.nr_ids + 1, BITS_PER_TYPE(uint64_t));
+}
+
 /*
  * Execute func() on the pCPU which owns the IMSIC interrupt file func() is
  * going to work with.
@@ -1138,20 +1155,90 @@ static void cf_check imsic_vsfile_local_update(void *data)
     csr_write(CSR_VSISELECT, old_vsiselect);
 }
 
+/*
+ * Point the vCPU's HSTATUS.VGEIN at the guest interrupt file it has been
+ * given. It is applied to the hart when the vCPU's context is restored.
+ */
+static void vcpu_set_vgein(struct vcpu *v, unsigned int vsfile_id)
+{
+    unsigned long hstatus = vcpu_guest_cpu_user_regs(v)->hstatus;
+
+    hstatus &= ~HSTATUS_VGEIN;
+    hstatus |= MASK_INSR(vsfile_id, HSTATUS_VGEIN);
+
+    vcpu_guest_cpu_user_regs(v)->hstatus = hstatus;
+}
+
+/*
+ * Take a h/w guest interrupt file of 'cpu' for the vCPU: zero the file out,
+ * map it into the domain's G-stage at the vCPU's virtual IMSIC page and
+ * record the new location in the per-vCPU IMSIC state.
+ *
+ * HSTATUS.VGEIN is deliberately left alone: the vCPU may be pointed at the
+ * file only when the file already holds the vCPU's interrupt state, which in
+ * the case of imsic_migrate_vcpu() happens only after the old file has been
+ * moved to the new one. Thereby it is up to the caller to call
+ * vcpu_set_vgein() at the right moment.
+ *
+ * Returns the id of the taken interrupt file, or 0 if none could be taken, in
+ * which case the domain is crashed.
+ */
+static unsigned int imsic_vsfile_acquire(struct vcpu *v, unsigned int cpu)
+{
+    struct imsic_vsfile_data vsfile_data = { .nr_eix = imsic_nr_eix() };
+    unsigned int vsfile_id;
+    int rc;
+
+    vsfile_id = vgein_assign(v);
+    if ( !vsfile_id )
+    {
+        /*
+         * vgein_assign() returns 0 when no free h/w guest interrupt file is
+         * available. s/w guest interrupt files aren't supported yet, so such
+         * a vCPU can't be run.
+         */
+        domain_crash(v->domain,
+                     "%pv: no free h/w guest interrupt file on CPU%u\n",
+                     v, cpu);
+        return 0;
+    }
+
+    vsfile_data.hgei = vsfile_id;
+
+    /* The file could still hold the state of its previous owner */
+    imsic_call_on_cpu(cpu, imsic_vsfile_local_clear, &vsfile_data);
+
+    rc = imsic_map_guest_file(v, vsfile_id);
+    if ( rc )
+    {
+        vgein_release(v, vsfile_id, cpu);
+
+        /* Can't continue w/o correctly mapped IMSIC interrupt file */
+        domain_crash(v->domain,
+                     "%pv: failed to map h/w guest interrupt file %u: %d\n",
+                     v, vsfile_id, rc);
+        return 0;
+    }
+
+    imsic_update_state(v, vsfile_id);
+
+    return vsfile_id;
+}
+
 void imsic_migrate_vcpu(struct vcpu *v)
 {
-    unsigned int new_vsfile_hgei;
+    unsigned int new_vsfile_id;
     unsigned int new_vsfile_cpu;
-    unsigned int nr_hw_eix = DIV_ROUND_UP(imsic_cfg.nr_ids + 1,
-                                          BITS_PER_TYPE(uint64_t));
-    struct imsic_vsfile_data vsfile_data = {
-        .nr_eix = nr_hw_eix,
-    };
+    unsigned int nr_hw_eix = imsic_nr_eix();
     struct vimsic_state *imsic_state = v->arch.vimsic_state;
     unsigned long flags;
     unsigned int old_vsfile_id;
     unsigned int old_vsfile_cpu;
     struct imsic_mrif tmrif = { };
+    struct imsic_vsfile_data vsfile_data = {
+        .nr_eix = nr_hw_eix,
+        .mrif = &tmrif,
+    };
 
     /*
      * The scheduler can mark a freshly created vCPU's unit as migrated and
@@ -1189,25 +1276,10 @@ void imsic_migrate_vcpu(struct vcpu *v)
      */
     new_vsfile_cpu = v->processor;
 
-    new_vsfile_hgei = vgein_assign(v);
-
-    /* We don't support SW interrupt files at the moment. */
-    BUG_ON(!new_vsfile_hgei);
-
-    vsfile_data.hgei = new_vsfile_hgei;
-
-    /* Zero-out new IMSIC VS-file */
-    imsic_call_on_cpu(new_vsfile_cpu, imsic_vsfile_local_clear, &vsfile_data);
-
-    /* Update G-stage mapping for the new IMSIC VS-file */
-    if ( imsic_map_guest_file(v, new_vsfile_hgei) )
-    {
-        domain_crash(v->domain, "Migration to hw interrupt file failed\n");
-
+    /* Zero-out, map and start to use the new IMSIC VS-file */
+    new_vsfile_id = imsic_vsfile_acquire(v, new_vsfile_cpu);
+    if ( !new_vsfile_id )
         return;
-    }
-
-    imsic_update_state(v, new_vsfile_hgei);
 
     /*
      * TODO: Modify the relevant translation tables at all IOMMUs so that MSIs
@@ -1245,7 +1317,7 @@ void imsic_migrate_vcpu(struct vcpu *v)
     vgein_release(v, old_vsfile_id, old_vsfile_cpu);
 
     /* Restore register state in the new IMSIC VS-file */
-    vsfile_data.mrif = &tmrif;
+    vsfile_data.hgei = new_vsfile_id;
     imsic_call_on_cpu(new_vsfile_cpu, imsic_vsfile_local_update, &vsfile_data);
 
     /*
@@ -1262,7 +1334,23 @@ void imsic_migrate_vcpu(struct vcpu *v)
         imsic_call_on_cpu(new_vsfile_cpu, imsic_local_hgeie_set, &vsfile_data);
 
     /* Set VCPU HSTATUS.VGEIN to new IMSIC VS-file */
-    vcpu_guest_cpu_user_regs(v)->hstatus &= ~HSTATUS_VGEIN;
-    vcpu_guest_cpu_user_regs(v)->hstatus |=
-            MASK_INSR(new_vsfile_hgei, HSTATUS_VGEIN);
+    vcpu_set_vgein(v, new_vsfile_id);
+}
+
+void imsic_vsfile_attach(struct vcpu *v)
+{
+    unsigned int new_vsfile_id;
+
+    if ( !aia_usable() )
+        return;
+
+    new_vsfile_id = imsic_vsfile_acquire(v, v->processor);
+    if ( !new_vsfile_id )
+        return;
+
+    /*
+     * The vCPU has never run yet, so the just zeroed out file is all the
+     * interrupt state it has and HSTATUS.VGEIN can be pointed at it at once.
+     */
+    vcpu_set_vgein(v, new_vsfile_id);
 }
diff --git a/xen/arch/riscv/include/asm/imsic.h b/xen/arch/riscv/include/asm/imsic.h
index 6395b539c52d..f0edf0bff5d9 100644
--- a/xen/arch/riscv/include/asm/imsic.h
+++ b/xen/arch/riscv/include/asm/imsic.h
@@ -125,4 +125,6 @@ int imsic_map_guest_file(struct vcpu *v, unsigned int vsfile_id);
 
 void imsic_migrate_vcpu(struct vcpu *v);
 
+void imsic_vsfile_attach(struct vcpu *v);
+
 #endif /* ASM_RISCV_IMSIC_H */
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.