[PATCH v4 11/13] ARM: KVM: VGIC initialisation code
Will Deacon
will.deacon at arm.com
Wed Dec 5 05:43:13 EST 2012
On Sat, Nov 10, 2012 at 03:45:32PM +0000, Christoffer Dall wrote:
> From: Marc Zyngier <marc.zyngier at arm.com>
>
> Add the init code for the hypervisor, the virtual machine, and
> the virtual CPUs.
>
> An interrupt handler is also wired to allow the VGIC maintenance
> interrupts, used to deal with level triggered interrupts and LR
> underflows.
>
> Signed-off-by: Marc Zyngier <marc.zyngier at arm.com>
> Signed-off-by: Christoffer Dall <c.dall at virtualopensystems.com>
> ---
> arch/arm/include/asm/kvm_vgic.h | 11 ++
> arch/arm/kvm/arm.c | 14 ++
> arch/arm/kvm/vgic.c | 237 +++++++++++++++++++++++++++++++++++++++
> 3 files changed, 258 insertions(+), 4 deletions(-)
>
> diff --git a/arch/arm/include/asm/kvm_vgic.h b/arch/arm/include/asm/kvm_vgic.h
> index 6e3d303..a8e7a93 100644
> --- a/arch/arm/include/asm/kvm_vgic.h
> +++ b/arch/arm/include/asm/kvm_vgic.h
> @@ -154,6 +154,7 @@ static inline void vgic_bytemap_set_irq_val(struct vgic_bytemap *x,
> struct vgic_dist {
> #ifdef CONFIG_KVM_ARM_VGIC
> spinlock_t lock;
> + bool ready;
>
> /* Virtual control interface mapping */
> void __iomem *vctrl_base;
> @@ -239,6 +240,10 @@ struct kvm_exit_mmio;
>
> #ifdef CONFIG_KVM_ARM_VGIC
> int kvm_vgic_set_addr(struct kvm *kvm, unsigned long type, u64 addr);
> +int kvm_vgic_hyp_init(void);
> +int kvm_vgic_init(struct kvm *kvm);
> +int kvm_vgic_create(struct kvm *kvm);
> +void kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu);
> void kvm_vgic_sync_to_cpu(struct kvm_vcpu *vcpu);
> void kvm_vgic_sync_from_cpu(struct kvm_vcpu *vcpu);
> int kvm_vgic_inject_irq(struct kvm *kvm, int cpuid, unsigned int irq_num,
> @@ -248,6 +253,7 @@ bool vgic_handle_mmio(struct kvm_vcpu *vcpu, struct kvm_run *run,
> struct kvm_exit_mmio *mmio);
>
> #define irqchip_in_kernel(k) (!!((k)->arch.vgic.vctrl_base))
> +#define vgic_initialized(k) ((k)->arch.vgic.ready)
> #else
> static inline int kvm_vgic_hyp_init(void)
> {
> @@ -294,6 +300,11 @@ static inline int irqchip_in_kernel(struct kvm *kvm)
> {
> return 0;
> }
> +
> +static inline bool vgic_initialized(struct kvm *kvm)
> +{
> + return true;
> +}
> #endif
Shouldn't this return false?
>
> #endif
> diff --git a/arch/arm/kvm/arm.c b/arch/arm/kvm/arm.c
> index f43da01..a633d9d 100644
> --- a/arch/arm/kvm/arm.c
> +++ b/arch/arm/kvm/arm.c
> @@ -187,6 +187,8 @@ int kvm_dev_ioctl_check_extension(long ext)
> switch (ext) {
> #ifdef CONFIG_KVM_ARM_VGIC
> case KVM_CAP_IRQCHIP:
> + r = vgic_present;
> + break;
> #endif
> case KVM_CAP_USER_MEMORY:
> case KVM_CAP_DESTROY_MEMORY_REGION_WORKS:
> @@ -623,6 +625,14 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu, struct kvm_run *run)
> if (unlikely(vcpu->arch.target < 0))
> return -ENOEXEC;
>
> + /* Initalize the VGIC before running a vcpu the first time on this VM */
Initialize
> + if (unlikely(irqchip_in_kernel(vcpu->kvm) &&
Why unlikely? I'd just drop the hint.
> + !vgic_initialized(vcpu->kvm))) {
> + ret = kvm_vgic_init(vcpu->kvm);
> + if (ret)
> + return ret;
> + }
> +
> if (run->exit_reason == KVM_EXIT_MMIO) {
> ret = kvm_handle_mmio_return(vcpu, vcpu->run);
> if (ret)
> @@ -1024,8 +1034,8 @@ static int init_hyp_mode(void)
> * Init HYP view of VGIC
> */
> err = kvm_vgic_hyp_init();
> - if (err)
> - goto out_free_mappings;
> + if (!err)
> + vgic_present = true;
>
> return 0;
Is that an intended change in behaviour? If kvm_vgic_hyp_init fails, you'll
return 0...
> out_free_vfp:
> diff --git a/arch/arm/kvm/vgic.c b/arch/arm/kvm/vgic.c
> index 70040bb..415ddb8 100644
> --- a/arch/arm/kvm/vgic.c
> +++ b/arch/arm/kvm/vgic.c
> @@ -20,7 +20,14 @@
> #include <linux/kvm_host.h>
> #include <linux/interrupt.h>
> #include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +
> #include <asm/kvm_emulate.h>
> +#include <asm/hardware/gic.h>
> +#include <asm/kvm_arm.h>
> +#include <asm/kvm_mmu.h>
>
> /*
> * How the whole thing works (courtesy of Christoffer Dall):
> @@ -59,11 +66,18 @@
> */
>
> #define VGIC_ADDR_UNDEF (-1)
> -#define IS_VGIC_ADDR_UNDEF(_x) ((_x) == (typeof(_x))VGIC_ADDR_UNDEF)
> +#define IS_VGIC_ADDR_UNDEF(_x) ((_x) == VGIC_ADDR_UNDEF)
Huh?
>
> #define VGIC_DIST_SIZE 0x1000
> #define VGIC_CPU_SIZE 0x2000
>
> +/* Physical address of vgic virtual cpu interface */
> +static phys_addr_t vgic_vcpu_base;
> +
> +/* Virtual control interface base address */
> +static void __iomem *vgic_vctrl_base;
> +
> +static struct device_node *vgic_node;
>
> #define ACCESS_READ_VALUE (1 << 0)
> #define ACCESS_READ_RAZ (0 << 0)
> @@ -527,7 +541,7 @@ bool vgic_handle_mmio(struct kvm_vcpu *vcpu, struct kvm_run *run, struct kvm_exi
>
> if (!irqchip_in_kernel(vcpu->kvm) ||
> mmio->phys_addr < base ||
> - (mmio->phys_addr + mmio->len) > (base + dist->vgic_dist_size))
> + (mmio->phys_addr + mmio->len) > (base + VGIC_DIST_SIZE))
> return false;
Again, this is an odd hunk. It looks like you've accidentally got some older
context lying around in this patch.
>
> range = find_matching_range(vgic_ranges, mmio, base);
> @@ -957,6 +971,225 @@ int kvm_vgic_inject_irq(struct kvm *kvm, int cpuid, unsigned int irq_num,
> return 0;
> }
>
> +static irqreturn_t vgic_maintenance_handler(int irq, void *data)
> +{
> + struct kvm_vcpu *vcpu = *(struct kvm_vcpu **)data;
> + struct vgic_dist *dist;
> + struct vgic_cpu *vgic_cpu;
> +
> + if (WARN(!vcpu,
> + "VGIC interrupt on CPU %d with no vcpu\n", smp_processor_id()))
> + return IRQ_HANDLED;
We need to get an answer about the potential delaying of this interrupt,
otherwise this might kick more often that we'd like.
> +
> + vgic_cpu = &vcpu->arch.vgic_cpu;
> + dist = &vcpu->kvm->arch.vgic;
> + kvm_debug("MISR = %08x\n", vgic_cpu->vgic_misr);
> +
> + /*
> + * We do not need to take the distributor lock here, since the only
> + * action we perform is clearing the irq_active_bit for an EOIed
> + * level interrupt. There is a potential race with
> + * the queuing of an interrupt in __kvm_sync_to_cpu(), where we check
> + * if the interrupt is already active. Two possibilities:
> + *
> + * - The queuing is occuring on the same vcpu: cannot happen, as we're
> + * already in the context of this vcpu, and executing the handler
> + * - The interrupt has been migrated to another vcpu, and we ignore
> + * this interrupt for this run. Big deal. It is still pending though,
> + * and will get considered when this vcpu exits.
> + */
We've already discussed this in an earlier thread.
> + if (vgic_cpu->vgic_misr & VGIC_MISR_EOI) {
> + /*
> + * Some level interrupts have been EOIed. Clear their
> + * active bit.
> + */
> + int lr, irq;
> +
> + for_each_set_bit(lr, (unsigned long *)vgic_cpu->vgic_eisr,
> + vgic_cpu->nr_lr) {
> + irq = vgic_cpu->vgic_lr[lr] & VGIC_LR_VIRTUALID;
> +
> + vgic_bitmap_set_irq_val(&dist->irq_active,
> + vcpu->vcpu_id, irq, 0);
> + vgic_cpu->vgic_lr[lr] &= ~VGIC_LR_EOI;
> + writel_relaxed(vgic_cpu->vgic_lr[lr],
> + dist->vctrl_base + GICH_LR0 + (lr << 2));
> +
> + /* Any additionnal pending interrupt? */
additional
> + if (vgic_bitmap_get_irq_val(&dist->irq_state,
> + vcpu->vcpu_id, irq)) {
> + set_bit(irq, vcpu->arch.vgic_cpu.pending);
> + set_bit(vcpu->vcpu_id,
> + &dist->irq_pending_on_cpu);
> + } else {
> + clear_bit(irq, vgic_cpu->pending);
> + }
> + }
> + }
> +
> + if (vgic_cpu->vgic_misr & VGIC_MISR_U) {
> + vgic_cpu->vgic_hcr &= ~VGIC_HCR_UIE;
> + writel_relaxed(vgic_cpu->vgic_hcr, dist->vctrl_base + GICH_HCR);
> + }
> +
> + return IRQ_HANDLED;
> +}
I wonder whether we could instead simplify the irq handler so it just sets
a flag indicating that the list registers need updating, then we pick that
up on the resume path and keep all the work in one place. Hard to tell.
> +
> +void kvm_vgic_vcpu_init(struct kvm_vcpu *vcpu)
> +{
> + struct vgic_cpu *vgic_cpu = &vcpu->arch.vgic_cpu;
> + struct vgic_dist *dist = &vcpu->kvm->arch.vgic;
> + u32 reg;
> + int i;
> +
> + if (!irqchip_in_kernel(vcpu->kvm))
> + return;
> +
> + for (i = 0; i < VGIC_NR_IRQS; i++) {
> + if (i < 16)
> + vgic_bitmap_set_irq_val(&dist->irq_enabled,
> + vcpu->vcpu_id, i, 1);
> + if (i < 32)
> + vgic_bitmap_set_irq_val(&dist->irq_cfg,
> + vcpu->vcpu_id, i, 1);
> +
Usual comments here :)
> + vgic_cpu->vgic_irq_lr_map[i] = LR_EMPTY;
> + }
> +
> + BUG_ON(!vcpu->kvm->arch.vgic.vctrl_base);
> + reg = readl_relaxed(vcpu->kvm->arch.vgic.vctrl_base + GICH_VTR);
> + vgic_cpu->nr_lr = (reg & 0x1f) + 1;
> +
> + reg = readl_relaxed(vcpu->kvm->arch.vgic.vctrl_base + GICH_VMCR);
> + vgic_cpu->vgic_vmcr = reg | (0x1f << 27); /* Priority */
Why isn't this defined with the other registers?
> +
> + vgic_cpu->vgic_hcr |= VGIC_HCR_EN; /* Get the show on the road... */
> +}
> +
> +static void vgic_init_maintenance_interrupt(void *info)
> +{
> + unsigned int *irqp = info;
> +
> + enable_percpu_irq(*irqp, 0);
> +}
> +
> +int kvm_vgic_hyp_init(void)
> +{
> + int ret;
> + unsigned int irq;
> + struct resource vctrl_res;
> + struct resource vcpu_res;
> +
> + vgic_node = of_find_compatible_node(NULL, NULL, "arm,cortex-a15-gic");
> + if (!vgic_node)
> + return -ENODEV;
> +
> + irq = irq_of_parse_and_map(vgic_node, 0);
> + if (!irq)
> + return -ENXIO;
Don't you need to put of_node_put to decrement the refcount before returning?
> +
> + ret = request_percpu_irq(irq, vgic_maintenance_handler,
> + "vgic", kvm_get_running_vcpus());
> + if (ret) {
> + kvm_err("Cannot register interrupt %d\n", irq);
> + return ret;
Likewise (and similarly throughout this function).
> + }
> +
> + ret = of_address_to_resource(vgic_node, 2, &vctrl_res);
> + if (ret) {
> + kvm_err("Cannot obtain VCTRL resource\n");
> + goto out_free_irq;
> + }
> +
> + vgic_vctrl_base = of_iomap(vgic_node, 2);
> + if (!vgic_vctrl_base) {
> + kvm_err("Cannot ioremap VCTRL\n");
> + ret = -ENOMEM;
> + goto out_free_irq;
> + }
> +
> + ret = create_hyp_io_mappings(vgic_vctrl_base,
> + vgic_vctrl_base + resource_size(&vctrl_res),
> + vctrl_res.start);
> + if (ret) {
> + kvm_err("Cannot map VCTRL into hyp\n");
> + goto out_unmap;
> + }
> +
> + kvm_info("%s@%llx IRQ%d\n", vgic_node->name, vctrl_res.start, irq);
> + on_each_cpu(vgic_init_maintenance_interrupt, &irq, 1);
What if all your CPUs aren't online at this point? Do you need a hotplug
notifier to re-enable the PPI? (you should try hotplug on the host actually,
it could be hilarious fun :)
> +
> + if (of_address_to_resource(vgic_node, 3, &vcpu_res)) {
> + kvm_err("Cannot obtain VCPU resource\n");
> + ret = -ENXIO;
> + goto out_unmap;
> + }
> + vgic_vcpu_base = vcpu_res.start;
> +
> + return 0;
> +
> +out_unmap:
> + iounmap(vgic_vctrl_base);
> +out_free_irq:
> + free_percpu_irq(irq, kvm_get_running_vcpus());
> +
> + return ret;
> +}
> +
> +int kvm_vgic_init(struct kvm *kvm)
> +{
> + int ret = 0, i;
> +
> + mutex_lock(&kvm->lock);
> +
> + if (vgic_initialized(kvm))
> + goto out;
> +
> + if (IS_VGIC_ADDR_UNDEF(kvm->arch.vgic.vgic_dist_base) ||
> + IS_VGIC_ADDR_UNDEF(kvm->arch.vgic.vgic_cpu_base)) {
> + kvm_err("Need to set vgic cpu and dist addresses first\n");
> + ret = -ENXIO;
> + goto out;
> + }
> +
> + ret = kvm_phys_addr_ioremap(kvm, kvm->arch.vgic.vgic_cpu_base,
> + vgic_vcpu_base, VGIC_CPU_SIZE);
> + if (ret) {
> + kvm_err("Unable to remap VGIC CPU to VCPU\n");
> + goto out;
> + }
> +
> + for (i = 32; i < VGIC_NR_IRQS; i += 4)
> + vgic_set_target_reg(kvm, 0, i);
> +
> + kvm->arch.vgic.ready = true;
> +out:
> + mutex_unlock(&kvm->lock);
> + return ret;
> +}
> +
> +int kvm_vgic_create(struct kvm *kvm)
> +{
> + int ret;
> +
> + mutex_lock(&kvm->lock);
> +
> + if (atomic_read(&kvm->online_vcpus) || kvm->arch.vgic.vctrl_base) {
> + ret = -EEXIST;
> + goto out;
> + }
> +
> + spin_lock_init(&kvm->arch.vgic.lock);
> + kvm->arch.vgic.vctrl_base = vgic_vctrl_base;
> + kvm->arch.vgic.vgic_dist_base = VGIC_ADDR_UNDEF;
> + kvm->arch.vgic.vgic_cpu_base = VGIC_ADDR_UNDEF;
> +
> + ret = 0;
Might as well assign this when it's declared.
Will
More information about the linux-arm-kernel
mailing list