KVM: arm/arm64: vgic-new: vgic_init: implement kvm_vgic_hyp_init
[cascardo/linux.git] / virt / kvm / arm / vgic / vgic-init.c
diff --git a/virt/kvm/arm/vgic/vgic-init.c b/virt/kvm/arm/vgic/vgic-init.c
new file mode 100644 (file)
index 0000000..4523beb
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2015, 2016 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/uaccess.h>
+#include <linux/interrupt.h>
+#include <linux/cpu.h>
+#include <linux/kvm_host.h>
+#include <kvm/arm_vgic.h>
+#include <asm/kvm_mmu.h>
+#include "vgic.h"
+
+/* GENERIC PROBE */
+
+static void vgic_init_maintenance_interrupt(void *info)
+{
+       enable_percpu_irq(kvm_vgic_global_state.maint_irq, 0);
+}
+
+static int vgic_cpu_notify(struct notifier_block *self,
+                          unsigned long action, void *cpu)
+{
+       switch (action) {
+       case CPU_STARTING:
+       case CPU_STARTING_FROZEN:
+               vgic_init_maintenance_interrupt(NULL);
+               break;
+       case CPU_DYING:
+       case CPU_DYING_FROZEN:
+               disable_percpu_irq(kvm_vgic_global_state.maint_irq);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block vgic_cpu_nb = {
+       .notifier_call = vgic_cpu_notify,
+};
+
+static irqreturn_t vgic_maintenance_handler(int irq, void *data)
+{
+       /*
+        * We cannot rely on the vgic maintenance interrupt to be
+        * delivered synchronously. This means we can only use it to
+        * exit the VM, and we perform the handling of EOIed
+        * interrupts on the exit path (see vgic_process_maintenance).
+        */
+       return IRQ_HANDLED;
+}
+
+/**
+ * kvm_vgic_hyp_init: populates the kvm_vgic_global_state variable
+ * according to the host GIC model. Accordingly calls either
+ * vgic_v2/v3_probe which registers the KVM_DEVICE that can be
+ * instantiated by a guest later on .
+ */
+int kvm_vgic_hyp_init(void)
+{
+       const struct gic_kvm_info *gic_kvm_info;
+       int ret;
+
+       gic_kvm_info = gic_get_kvm_info();
+       if (!gic_kvm_info)
+               return -ENODEV;
+
+       if (!gic_kvm_info->maint_irq) {
+               kvm_err("No vgic maintenance irq\n");
+               return -ENXIO;
+       }
+
+       switch (gic_kvm_info->type) {
+       case GIC_V2:
+               ret = vgic_v2_probe(gic_kvm_info);
+               break;
+       case GIC_V3:
+               ret = vgic_v3_probe(gic_kvm_info);
+               break;
+       default:
+               ret = -ENODEV;
+       };
+
+       if (ret)
+               return ret;
+
+       kvm_vgic_global_state.maint_irq = gic_kvm_info->maint_irq;
+       ret = request_percpu_irq(kvm_vgic_global_state.maint_irq,
+                                vgic_maintenance_handler,
+                                "vgic", kvm_get_running_vcpus());
+       if (ret) {
+               kvm_err("Cannot register interrupt %d\n",
+                       kvm_vgic_global_state.maint_irq);
+               return ret;
+       }
+
+       ret = __register_cpu_notifier(&vgic_cpu_nb);
+       if (ret) {
+               kvm_err("Cannot register vgic CPU notifier\n");
+               goto out_free_irq;
+       }
+
+       on_each_cpu(vgic_init_maintenance_interrupt, NULL, 1);
+
+       kvm_info("vgic interrupt IRQ%d\n", kvm_vgic_global_state.maint_irq);
+       return 0;
+
+out_free_irq:
+       free_percpu_irq(kvm_vgic_global_state.maint_irq,
+                       kvm_get_running_vcpus());
+       return ret;
+}