KVM: arm/arm64: vgic: Add debugfs vgic-state file
authorChristoffer Dall <christoffer.dall@linaro.org>
Tue, 17 Jan 2017 22:09:13 +0000 (23:09 +0100)
committerChristoffer Dall <christoffer.dall@linaro.org>
Wed, 25 Jan 2017 12:50:03 +0000 (13:50 +0100)
Add a file to debugfs to read the in-kernel state of the vgic.  We don't
do any locking of the entire VGIC state while traversing all the IRQs,
so if the VM is running the user/developer may not see a quiesced state,
but should take care to pause the VM using facilities in user space for
that purpose.

We also don't support LPIs yet, but they can be added easily if needed.

Reviewed-by: Eric Auger <eric.auger@redhat.com>
Tested-by: Eric Auger <eric.auger@redhat.com>
Tested-by: Andre Przywara <andre.przywara@arm.com>
Acked-by: Marc Zyngier <marc.zyngier@arm.com>
Signed-off-by: Christoffer Dall <christoffer.dall@linaro.org>
arch/arm/kvm/Makefile
arch/arm64/kvm/Makefile
include/kvm/arm_vgic.h
virt/kvm/arm/vgic/vgic-debug.c [new file with mode: 0644]
virt/kvm/arm/vgic/vgic-init.c
virt/kvm/arm/vgic/vgic.h

index d571243ab4d186aba20a12a0d0b076bad58e0cb5..12b628187e900280ab39071e805ff5d54d649e8c 100644 (file)
@@ -33,5 +33,6 @@ obj-y += $(KVM)/arm/vgic/vgic-mmio-v2.o
 obj-y += $(KVM)/arm/vgic/vgic-mmio-v3.o
 obj-y += $(KVM)/arm/vgic/vgic-kvm-device.o
 obj-y += $(KVM)/arm/vgic/vgic-its.o
+obj-y += $(KVM)/arm/vgic/vgic-debug.o
 obj-y += $(KVM)/irqchip.o
 obj-y += $(KVM)/arm/arch_timer.o
index d50a82a16ff68cfb5f57808a0f20b08166a75788..e025beceda393365dd1a5822f55b8ad87a014eb2 100644 (file)
@@ -31,6 +31,7 @@ kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic-mmio-v2.o
 kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic-mmio-v3.o
 kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic-kvm-device.o
 kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic-its.o
+kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/vgic/vgic-debug.o
 kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/irqchip.o
 kvm-$(CONFIG_KVM_ARM_HOST) += $(KVM)/arm/arch_timer.o
 kvm-$(CONFIG_KVM_ARM_PMU) += $(KVM)/arm/pmu.o
index da2ce086ce311c3aac0f00bb0d95a74a02e3ad2c..0af1477cfe8ddfa44cd7cfa5fe8a96ffc7651ee6 100644 (file)
@@ -166,6 +166,8 @@ struct vgic_its {
        struct list_head        collection_list;
 };
 
+struct vgic_state_iter;
+
 struct vgic_dist {
        bool                    in_kernel;
        bool                    ready;
@@ -213,6 +215,9 @@ struct vgic_dist {
        spinlock_t              lpi_list_lock;
        struct list_head        lpi_list_head;
        int                     lpi_list_count;
+
+       /* used by vgic-debug */
+       struct vgic_state_iter *iter;
 };
 
 struct vgic_v2_cpu_if {
diff --git a/virt/kvm/arm/vgic/vgic-debug.c b/virt/kvm/arm/vgic/vgic-debug.c
new file mode 100644 (file)
index 0000000..7072ab7
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2016 Linaro
+ * Author: Christoffer Dall <christoffer.dall@linaro.org>
+ *
+ * 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/cpu.h>
+#include <linux/debugfs.h>
+#include <linux/interrupt.h>
+#include <linux/kvm_host.h>
+#include <linux/seq_file.h>
+#include <kvm/arm_vgic.h>
+#include <asm/kvm_mmu.h>
+#include "vgic.h"
+
+/*
+ * Structure to control looping through the entire vgic state.  We start at
+ * zero for each field and move upwards.  So, if dist_id is 0 we print the
+ * distributor info.  When dist_id is 1, we have already printed it and move
+ * on.
+ *
+ * When vcpu_id < nr_cpus we print the vcpu info until vcpu_id == nr_cpus and
+ * so on.
+ */
+struct vgic_state_iter {
+       int nr_cpus;
+       int nr_spis;
+       int dist_id;
+       int vcpu_id;
+       int intid;
+};
+
+static void iter_next(struct vgic_state_iter *iter)
+{
+       if (iter->dist_id == 0) {
+               iter->dist_id++;
+               return;
+       }
+
+       iter->intid++;
+       if (iter->intid == VGIC_NR_PRIVATE_IRQS &&
+           ++iter->vcpu_id < iter->nr_cpus)
+               iter->intid = 0;
+}
+
+static void iter_init(struct kvm *kvm, struct vgic_state_iter *iter,
+                     loff_t pos)
+{
+       int nr_cpus = atomic_read(&kvm->online_vcpus);
+
+       memset(iter, 0, sizeof(*iter));
+
+       iter->nr_cpus = nr_cpus;
+       iter->nr_spis = kvm->arch.vgic.nr_spis;
+
+       /* Fast forward to the right position if needed */
+       while (pos--)
+               iter_next(iter);
+}
+
+static bool end_of_vgic(struct vgic_state_iter *iter)
+{
+       return iter->dist_id > 0 &&
+               iter->vcpu_id == iter->nr_cpus &&
+               (iter->intid - VGIC_NR_PRIVATE_IRQS) == iter->nr_spis;
+}
+
+static void *vgic_debug_start(struct seq_file *s, loff_t *pos)
+{
+       struct kvm *kvm = (struct kvm *)s->private;
+       struct vgic_state_iter *iter;
+
+       mutex_lock(&kvm->lock);
+       iter = kvm->arch.vgic.iter;
+       if (iter) {
+               iter = ERR_PTR(-EBUSY);
+               goto out;
+       }
+
+       iter = kmalloc(sizeof(*iter), GFP_KERNEL);
+       if (!iter) {
+               iter = ERR_PTR(-ENOMEM);
+               goto out;
+       }
+
+       iter_init(kvm, iter, *pos);
+       kvm->arch.vgic.iter = iter;
+
+       if (end_of_vgic(iter))
+               iter = NULL;
+out:
+       mutex_unlock(&kvm->lock);
+       return iter;
+}
+
+static void *vgic_debug_next(struct seq_file *s, void *v, loff_t *pos)
+{
+       struct kvm *kvm = (struct kvm *)s->private;
+       struct vgic_state_iter *iter = kvm->arch.vgic.iter;
+
+       ++*pos;
+       iter_next(iter);
+       if (end_of_vgic(iter))
+               iter = NULL;
+       return iter;
+}
+
+static void vgic_debug_stop(struct seq_file *s, void *v)
+{
+       struct kvm *kvm = (struct kvm *)s->private;
+       struct vgic_state_iter *iter;
+
+       /*
+        * If the seq file wasn't properly opened, there's nothing to clearn
+        * up.
+        */
+       if (IS_ERR(v))
+               return;
+
+       mutex_lock(&kvm->lock);
+       iter = kvm->arch.vgic.iter;
+       kfree(iter);
+       kvm->arch.vgic.iter = NULL;
+       mutex_unlock(&kvm->lock);
+}
+
+static void print_dist_state(struct seq_file *s, struct vgic_dist *dist)
+{
+       seq_printf(s, "Distributor\n");
+       seq_printf(s, "===========\n");
+       seq_printf(s, "vgic_model:\t%s\n",
+                  (dist->vgic_model == KVM_DEV_TYPE_ARM_VGIC_V3) ?
+                  "GICv3" : "GICv2");
+       seq_printf(s, "nr_spis:\t%d\n", dist->nr_spis);
+       seq_printf(s, "enabled:\t%d\n", dist->enabled);
+       seq_printf(s, "\n");
+
+       seq_printf(s, "P=pending_latch, L=line_level, A=active\n");
+       seq_printf(s, "E=enabled, H=hw, C=config (level=1, edge=0)\n");
+}
+
+static void print_header(struct seq_file *s, struct vgic_irq *irq,
+                        struct kvm_vcpu *vcpu)
+{
+       int id = 0;
+       char *hdr = "SPI ";
+
+       if (vcpu) {
+               hdr = "VCPU";
+               id = vcpu->vcpu_id;
+       }
+
+       seq_printf(s, "\n");
+       seq_printf(s, "%s%2d TYP   ID TGT_ID PLAEHC     HWID   TARGET SRC PRI VCPU_ID\n", hdr, id);
+       seq_printf(s, "---------------------------------------------------------------\n");
+}
+
+static void print_irq_state(struct seq_file *s, struct vgic_irq *irq,
+                           struct kvm_vcpu *vcpu)
+{
+       char *type;
+       if (irq->intid < VGIC_NR_SGIS)
+               type = "SGI";
+       else if (irq->intid < VGIC_NR_PRIVATE_IRQS)
+               type = "PPI";
+       else
+               type = "SPI";
+
+       if (irq->intid ==0 || irq->intid == VGIC_NR_PRIVATE_IRQS)
+               print_header(s, irq, vcpu);
+
+       seq_printf(s, "       %s %4d "
+                     "    %2d "
+                     "%d%d%d%d%d%d "
+                     "%8d "
+                     "%8x "
+                     " %2x "
+                     "%3d "
+                     "     %2d "
+                     "\n",
+                       type, irq->intid,
+                       (irq->target_vcpu) ? irq->target_vcpu->vcpu_id : -1,
+                       irq->pending_latch,
+                       irq->line_level,
+                       irq->active,
+                       irq->enabled,
+                       irq->hw,
+                       irq->config == VGIC_CONFIG_LEVEL,
+                       irq->hwintid,
+                       irq->mpidr,
+                       irq->source,
+                       irq->priority,
+                       (irq->vcpu) ? irq->vcpu->vcpu_id : -1);
+
+}
+
+static int vgic_debug_show(struct seq_file *s, void *v)
+{
+       struct kvm *kvm = (struct kvm *)s->private;
+       struct vgic_state_iter *iter = (struct vgic_state_iter *)v;
+       struct vgic_irq *irq;
+       struct kvm_vcpu *vcpu = NULL;
+
+       if (iter->dist_id == 0) {
+               print_dist_state(s, &kvm->arch.vgic);
+               return 0;
+       }
+
+       if (!kvm->arch.vgic.initialized)
+               return 0;
+
+       if (iter->vcpu_id < iter->nr_cpus) {
+               vcpu = kvm_get_vcpu(kvm, iter->vcpu_id);
+               irq = &vcpu->arch.vgic_cpu.private_irqs[iter->intid];
+       } else {
+               irq = &kvm->arch.vgic.spis[iter->intid - VGIC_NR_PRIVATE_IRQS];
+       }
+
+       spin_lock(&irq->irq_lock);
+       print_irq_state(s, irq, vcpu);
+       spin_unlock(&irq->irq_lock);
+
+       return 0;
+}
+
+static struct seq_operations vgic_debug_seq_ops = {
+       .start = vgic_debug_start,
+       .next  = vgic_debug_next,
+       .stop  = vgic_debug_stop,
+       .show  = vgic_debug_show
+};
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+       int ret;
+       ret = seq_open(file, &vgic_debug_seq_ops);
+       if (!ret) {
+               struct seq_file *seq;
+               /* seq_open will have modified file->private_data */
+               seq = file->private_data;
+               seq->private = inode->i_private;
+       }
+
+       return ret;
+};
+
+static struct file_operations vgic_debug_fops = {
+       .owner   = THIS_MODULE,
+       .open    = debug_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = seq_release
+};
+
+int vgic_debug_init(struct kvm *kvm)
+{
+       if (!kvm->debugfs_dentry)
+               return -ENOENT;
+
+       if (!debugfs_create_file("vgic-state", 0444,
+                                kvm->debugfs_dentry,
+                                kvm,
+                                &vgic_debug_fops))
+               return -ENOMEM;
+
+       return 0;
+}
+
+int vgic_debug_destroy(struct kvm *kvm)
+{
+       return 0;
+}
index c737ea0a310a732cc6f878c57877aa3086e67280..276139a24e6fd097f791537c07b7c182717e0693 100644 (file)
@@ -259,6 +259,8 @@ int vgic_init(struct kvm *kvm)
        if (ret)
                goto out;
 
+       vgic_debug_init(kvm);
+
        dist->initialized = true;
 out:
        return ret;
@@ -288,6 +290,8 @@ static void __kvm_vgic_destroy(struct kvm *kvm)
        struct kvm_vcpu *vcpu;
        int i;
 
+       vgic_debug_destroy(kvm);
+
        kvm_vgic_dist_destroy(kvm);
 
        kvm_for_each_vcpu(i, vcpu, kvm)
index b2cf34bde243c0e8487c827ee6ecf899facaf9a4..48da1f65a6c36e11f79ec65d3c3cbb3d3194b2b8 100644 (file)
@@ -102,4 +102,7 @@ int kvm_register_vgic_device(unsigned long type);
 int vgic_lazy_init(struct kvm *kvm);
 int vgic_init(struct kvm *kvm);
 
+int vgic_debug_init(struct kvm *kvm);
+int vgic_debug_destroy(struct kvm *kvm);
+
 #endif