Updated Priority Drop/Deactivation patch - - moved bitmap to per gic data - moved initialization of gic irqchip eoi handler to gic_init_bases - cleaned up the code per suggestions. - added documentation Signed-off-by: Mario Smarduch <mario.smarduch@xxxxxxxxxx> --- arch/arm/kvm/Kconfig | 8 ++ drivers/irqchip/irq-gic.c | 204 ++++++++++++++++++++++++++++++++++++++- include/linux/irqchip/arm-gic.h | 6 ++ 3 files changed, 213 insertions(+), 5 deletions(-) diff --git a/arch/arm/kvm/Kconfig b/arch/arm/kvm/Kconfig index 370e1a8..ae69e9a 100644 --- a/arch/arm/kvm/Kconfig +++ b/arch/arm/kvm/Kconfig @@ -59,6 +59,14 @@ config KVM_ARM_VGIC ---help--- Adds support for a hardware assisted, in-kernel GIC emulation. +config KVM_ARM_INT_PRIO_DROP + bool "KVM support for Interrupt pass-through" + depends on KVM_ARM_VGIC && OF + default n + ---help--- + Seperates interrupt priority drop and deactivation to enable device + pass-through to Guests. + config KVM_ARM_TIMER bool "KVM support for Architected Timers" depends on KVM_ARM_VGIC && ARM_ARCH_TIMER diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index 1760ceb..ac345ec 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -41,10 +41,13 @@ #include <linux/slab.h> #include <linux/irqchip/chained_irq.h> #include <linux/irqchip/arm-gic.h> +#include <linux/irqflags.h> +#include <linux/bitops.h> #include <asm/irq.h> #include <asm/exception.h> #include <asm/smp_plat.h> +#include <asm/virt.h> #include "irqchip.h" @@ -68,6 +71,9 @@ struct gic_chip_data { #ifdef CONFIG_GIC_NON_BANKED void __iomem *(*get_base)(union gic_base *); #endif +#ifdef CONFIG_KVM_ARM_INT_PRIO_DROP + DECLARE_BITMAP(gic_irq_prio_drop, 1020); +#endif }; static DEFINE_RAW_SPINLOCK(irq_controller_lock); @@ -148,6 +154,189 @@ static inline unsigned int gic_irq(struct irq_data *d) return d->hwirq; } +#ifdef CONFIG_KVM_ARM_INT_PRIO_DROP +static inline unsigned long *get_priodrop_map(int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + return ((struct gic_chip_data *) (d->chip_data))->gic_irq_prio_drop; +} + +/** + * void gic_eoi_irq_priodrop() - process end of interrupt handling for + * passthrough IRQs + * @struct irq_data *d: irq_data associed irq_desc + * + * When Priority Drop/Deactivation is set this handler replaces the standard + * EOI (irq_eoi) handler. The use of this handler will applyt to IRQa on all + * CPU for end of interrupt operation. This mode of operations is for + * passing IRQs through to guest, and eliminate guest exit for EOI execution. + * IRQs not marked for passthrough will write to CPU EOIR and DIR registers, + * IRQs makred for passhtrough will only write to EOIR register and guests + * write to EOIR register will reactivate the IRQ being passed through. + * + * IMPORTANT - when enabling this feature the host GICC interface must map + * additional page, the device tree should be updated to map 0x2000 instead + * of 0x1000. + * + * Return: void + */ +static void gic_eoi_irq_priodrop(struct irq_data *d) +{ + if (gic_arch_extn.irq_eoi) { + raw_spin_lock(&irq_controller_lock); + gic_arch_extn.irq_eoi(d); + raw_spin_unlock(&irq_controller_lock); + } + + /* Set from KVM device passthrough to determine which interrupts + * must be deactivated by the Guest. + */ + if (unlikely(gic_spi_get_priodrop(gic_irq(d)))) { + /* + * IRQ marked for priority drop, deactivation is deferred + * in this case the Guest deactivates the interrupt, or regular + * path if GCTLR.EOImodeNS=0 or not suppored. + */ + writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); + } else { + /* + * De-prioritize/de-activate interrupt, disable interrupts + * so lower priority interrupt does not stall this one. + */ + unsigned long flags; + raw_local_irq_save(flags); + writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); + writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_DIR); + raw_local_irq_restore(flags); + } +} +/** + * gic_enable_gicc() - enable CPU GIC interface + * @struct gic_chip_data *gic - gic chip data for GIC interface + * + * If HYP mode is enabled puts CPU GIC interface int priodrop mode. SGI bits + * are set in per-gic prio drop map to proces EOI for SGI correctly. + * It may happen that boot CPU will set HYP mode to true but subsequent CPUs + * may turn HYP mode off and system may come up in mixed mode. Writes to CPU + * DIR register with priodrop disabled results in upredictable access. + * In this case the host will panic. The Guest never runs in priodrop + * mode. + * + * Return: void + */ +static void gic_enable_gicc(struct gic_chip_data *gic) +{ + static bool priodrop_set; + if (is_hyp_mode_available()) { + /* + * Allow Priority Drop in host, but not in Guest + */ + gic->gic_irq_prio_drop[0] = (u32) (1 << 16) - 1; + writel_relaxed(0x201, gic_data_cpu_base(gic) + GIC_CPU_CTRL); + priodrop_set = true; + } else { + if (priodrop_set == true) + panic("OS misconfigured running in mixed interrupt modes"); + writel_relaxed(1, gic_data_cpu_base(gic) + GIC_CPU_CTRL); + } +} +/** + * gic_eoi_sgi() - end of interrupt for SGIs in priodrop mode + * + * @u32 irqstat - EOI word + * @struct gic_chip_data *gic - per GIC interface chip data + * + * Handles SGI EOI handling, for host writes to EOIR and DIR registers. For + * Guest to EOIR only. + * + * Return: void + */ +static void gic_eoi_sgi(u32 irqstat, struct gic_chip_data *gic) +{ + unsigned long flags; + if (unlikely(gic->gic_irq_prio_drop[0] & (1<<((irqstat & ~0x1c00) % 16)))) { + raw_local_irq_save(flags); + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI); + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_DIR); + raw_local_irq_restore(flags); + return; + } + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI); +} + +/** + * gic_spi_set_priodrop() - set IRQ to priodrop mode + * + * @int irq - the IRQ for passthrough + * + * Hypervisor code sets this bit for interrupt passthrough. + * + * Return: void + */ +void gic_spi_set_priodrop(int irq) +{ + if (likely(irq >= 32 && irq <= 1020)) + set_bit(irq, get_priodrop_map(irq)); +} + +/** + * gic_spi_clr_priodrop() - clears priodrop for IRQ + * + * @int irq - the IRQ to clear passthrough + * + * Hypervisor code sets this to interrupt passthrough for the IRQ + * + * Return: void + */ +void gic_spi_clr_priodrop(int irq) +{ + struct irq_data *d = irq_get_irq_data(irq); + if (likely(irq >= 32 && irq < 1020)) { + clear_bit(irq, get_priodrop_map(irq)); + writel_relaxed(irq, gic_cpu_base(d) + GIC_CPU_DIR); + } +} + +/** + * gic_spi_get_priodrop() - determine if interrupt is set for passthrough + * + * @int irq - irq to check passhtrough status + * + * Used by priodrop eoi code and Hypervisor to check IRQ priority drop status. + * + * Return: bool - true if irq set for passthrough. + */ +bool gic_spi_get_priodrop(int irq) +{ + if (likely(irq >= 32 && irq <= 1020)) + return test_bit(irq, get_priodrop_map(irq)); + return false; +} +#else +static void gic_eoi_irq_priodrop(struct irq_data *d) +{ +} +static void gic_enable_gicc(struct gic_chip_data *gic) +{ + writel_relaxed(1, gic_data_cpu_base(gic) + GIC_CPU_CTRL); +} +static void gic_eoi_sgi(u32 irqstat, struct gic_chip_data *gic) +{ + writel_relaxed(irqstat, gic_data_cpu_base(gic) + GIC_CPU_EOI); +} +void gic_spi_set_priodrop(int irq) +{ +} +void gic_spi_clr_priodrop(int irq) +{ +} +bool gic_spi_get_priodrop(int irq) +{ + return false; +} +#endif + + /* * Routines to acknowledge, disable and enable interrupts */ @@ -180,7 +369,6 @@ static void gic_eoi_irq(struct irq_data *d) gic_arch_extn.irq_eoi(d); raw_spin_unlock(&irq_controller_lock); } - writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI); } @@ -296,7 +484,7 @@ static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs continue; } if (irqnr < 16) { - writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); + gic_eoi_sgi(irqstat, gic); #ifdef CONFIG_SMP handle_IPI(irqnr, regs); #endif @@ -450,7 +638,7 @@ static void __cpuinit gic_cpu_init(struct gic_chip_data *gic) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4 / 4); writel_relaxed(0xf0, base + GIC_CPU_PRIMASK); - writel_relaxed(1, base + GIC_CPU_CTRL); + gic_enable_gicc(gic); } #ifdef CONFIG_CPU_PM @@ -585,7 +773,7 @@ static void gic_cpu_restore(unsigned int gic_nr) writel_relaxed(0xa0a0a0a0, dist_base + GIC_DIST_PRI + i * 4); writel_relaxed(0xf0, cpu_base + GIC_CPU_PRIMASK); - writel_relaxed(1, cpu_base + GIC_CPU_CTRL); + gic_enable_gicc(&gic_data[gic_nr]); } static int gic_notifier(struct notifier_block *self, unsigned long cmd, void *v) @@ -735,6 +923,13 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start, BUG_ON(gic_nr >= MAX_GIC_NR); + /* + * If HYP mode enabled and PRIO DROP set EOIR function to handle PRIO + * DROP + */ + if (IS_ENABLED(CONFIG_KVM_ARM_INT_PRIO_DROP) && is_hyp_mode_available()) + gic_chip.irq_eoi = gic_eoi_irq_priodrop; + gic = &gic_data[gic_nr]; #ifdef CONFIG_GIC_NON_BANKED if (percpu_offset) { /* Frankein-GIC without banked registers... */ @@ -856,5 +1051,4 @@ IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init); IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init); IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init); IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init); - #endif diff --git a/include/linux/irqchip/arm-gic.h b/include/linux/irqchip/arm-gic.h index 3e203eb..5a906c9 100644 --- a/include/linux/irqchip/arm-gic.h +++ b/include/linux/irqchip/arm-gic.h @@ -17,6 +17,7 @@ #define GIC_CPU_EOI 0x10 #define GIC_CPU_RUNNINGPRI 0x14 #define GIC_CPU_HIGHPRI 0x18 +#define GIC_CPU_DIR 0x1000 #define GIC_DIST_CTRL 0x000 #define GIC_DIST_CTR 0x004 @@ -73,6 +74,11 @@ static inline void gic_init(unsigned int nr, int start, gic_init_bases(nr, start, dist, cpu, 0, NULL); } +/* Functions to manage interrupt pass-through via priority drop/deactivation */ +void gic_spi_set_priodrop(int); +void gic_spi_clr_priodrop(int); +bool gic_spi_get_priodrop(int); + #endif /* __ASSEMBLY */ #endif -- 1.7.9.5 _______________________________________________ kvmarm mailing list kvmarm@xxxxxxxxxxxxxxxxxxxxx https://lists.cs.columbia.edu/cucslists/listinfo/kvmarm