[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH v4 14/25] arm: driver for CoreLink GIC-400 Generic Interrupt Controller
From: Stefano Stabellini <stefano.stabellini@xxxxxxxxxxxxx> - GICC, GICD and GICH initialization; - interrupts routing, acking and EOI; - interrupt injection into guests; - maintenance interrupt handler, that takes care of EOI physical interrupts on behalf of the guest; - a function to remap the virtual cpu interface into the guest address space, where the guest expect the GICC to be. Signed-off-by: Stefano Stabellini <stefano.stabellini@xxxxxxxxxxxxx> Signed-off-by: Ian Campbell <ian.campbell@xxxxxxxxxx> Signed-off-by: Tim Deegan <Tim.Deegan@xxxxxxxxxx> --- xen/arch/arm/domain.c | 2 + xen/arch/arm/gic.c | 473 +++++++++++++++++++++++++++++++++++++++++++++++++ xen/arch/arm/gic.h | 151 ++++++++++++++++ 3 files changed, 626 insertions(+), 0 deletions(-) create mode 100644 xen/arch/arm/gic.c create mode 100644 xen/arch/arm/gic.h diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c index d706b5f..ecbc5b7 100644 --- a/xen/arch/arm/domain.c +++ b/xen/arch/arm/domain.c @@ -11,6 +11,8 @@ #include <asm/p2m.h> #include <asm/irq.h> +#include "gic.h" + DEFINE_PER_CPU(struct vcpu *, curr_vcpu); static void continue_idle_domain(struct vcpu *v) diff --git a/xen/arch/arm/gic.c b/xen/arch/arm/gic.c new file mode 100644 index 0000000..9643a7d --- /dev/null +++ b/xen/arch/arm/gic.c @@ -0,0 +1,473 @@ +/* + * xen/arch/arm/gic.c + * + * ARM Generic Interrupt Controller support + * + * Tim Deegan <tim@xxxxxxx> + * Copyright (c) 2011 Citrix Systems. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#include <xen/config.h> +#include <xen/lib.h> +#include <xen/init.h> +#include <xen/mm.h> +#include <xen/irq.h> +#include <xen/sched.h> +#include <xen/errno.h> +#include <xen/softirq.h> +#include <asm/p2m.h> +#include <asm/domain.h> + +#include "gic.h" + +/* Access to the GIC Distributor registers through the fixmap */ +#define GICD ((volatile uint32_t *) FIXMAP_ADDR(FIXMAP_GICD)) +#define GICC ((volatile uint32_t *) (FIXMAP_ADDR(FIXMAP_GICC1) \ + + (GIC_CR_OFFSET & 0xfff))) +#define GICH ((volatile uint32_t *) (FIXMAP_ADDR(FIXMAP_GICH) \ + + (GIC_HR_OFFSET & 0xfff))) + +/* Global state */ +static struct { + paddr_t dbase; /* Address of distributor registers */ + paddr_t cbase; /* Address of CPU interface registers */ + paddr_t hbase; /* Address of virtual interface registers */ + unsigned int lines; + unsigned int cpus; + spinlock_t lock; +} gic; + +irq_desc_t irq_desc[NR_IRQS]; +unsigned nr_lrs; + +static unsigned int gic_irq_startup(struct irq_desc *desc) +{ + uint32_t enabler; + int irq = desc->irq; + + /* Enable routing */ + enabler = GICD[GICD_ISENABLER + irq / 32]; + GICD[GICD_ISENABLER + irq / 32] = enabler | (1u << (irq % 32)); + + return 0; +} + +static void gic_irq_shutdown(struct irq_desc *desc) +{ + uint32_t enabler; + int irq = desc->irq; + + /* Disable routing */ + enabler = GICD[GICD_ICENABLER + irq / 32]; + GICD[GICD_ICENABLER + irq / 32] = enabler | (1u << (irq % 32)); +} + +static void gic_irq_enable(struct irq_desc *desc) +{ + +} + +static void gic_irq_disable(struct irq_desc *desc) +{ + +} + +static void gic_irq_ack(struct irq_desc *desc) +{ + /* No ACK -- reading IAR has done this for us */ +} + +static void gic_host_irq_end(struct irq_desc *desc) +{ + int irq = desc->irq; + /* Lower the priority */ + GICC[GICC_EOIR] = irq; + /* Deactivate */ + GICC[GICC_DIR] = irq; +} + +static void gic_guest_irq_end(struct irq_desc *desc) +{ + int irq = desc->irq; + /* Lower the priority of the IRQ */ + GICC[GICC_EOIR] = irq; + /* Deactivation happens in maintenance interrupt / via GICV */ +} + +static void gic_irq_set_affinity(struct irq_desc *desc, const cpumask_t *mask) +{ + BUG(); +} + +/* XXX different for level vs edge */ +static hw_irq_controller gic_host_irq_type = { + .typename = "gic", + .startup = gic_irq_startup, + .shutdown = gic_irq_shutdown, + .enable = gic_irq_enable, + .disable = gic_irq_disable, + .ack = gic_irq_ack, + .end = gic_host_irq_end, + .set_affinity = gic_irq_set_affinity, +}; +static hw_irq_controller gic_guest_irq_type = { + .typename = "gic", + .startup = gic_irq_startup, + .shutdown = gic_irq_shutdown, + .enable = gic_irq_enable, + .disable = gic_irq_disable, + .ack = gic_irq_ack, + .end = gic_guest_irq_end, + .set_affinity = gic_irq_set_affinity, +}; + +/* Program the GIC to route an interrupt */ +static int gic_route_irq(unsigned int irq, bool_t level, + unsigned int cpu_mask, unsigned int priority) +{ + volatile unsigned char *bytereg; + uint32_t cfg, edgebit; + struct irq_desc *desc = irq_to_desc(irq); + unsigned long flags; + + ASSERT(!(cpu_mask & ~0xff)); /* Targets bitmap only supports 8 CPUs */ + ASSERT(priority <= 0xff); /* Only 8 bits of priority */ + ASSERT(irq < gic.lines + 32); /* Can't route interrupts that don't exist */ + + spin_lock_irqsave(&desc->lock, flags); + spin_lock(&gic.lock); + + if ( desc->action != NULL ) + { + spin_unlock(&desc->lock); + return -EBUSY; + } + + desc->handler = &gic_host_irq_type; + + /* Disable interrupt */ + desc->handler->shutdown(desc); + + /* Set edge / level */ + cfg = GICD[GICD_ICFGR + irq / 16]; + edgebit = 2u << (2 * (irq % 16)); + if ( level ) + cfg &= ~edgebit; + else + cfg |= edgebit; + GICD[GICD_ICFGR + irq / 16] = cfg; + + /* Set target CPU mask (RAZ/WI on uniprocessor) */ + bytereg = (unsigned char *) (GICD + GICD_ITARGETSR); + bytereg[irq] = cpu_mask; + + /* Set priority */ + bytereg = (unsigned char *) (GICD + GICD_IPRIORITYR); + bytereg[irq] = priority; + + spin_unlock(&gic.lock); + spin_unlock_irqrestore(&desc->lock, flags); + return 0; +} + +static void __init gic_dist_init(void) +{ + uint32_t type; + uint32_t cpumask = 1 << smp_processor_id(); + int i; + + cpumask |= cpumask << 8; + cpumask |= cpumask << 16; + + /* Disable the distributor */ + GICD[GICD_CTLR] = 0; + + type = GICD[GICD_TYPER]; + gic.lines = 32 * (type & GICD_TYPE_LINES); + gic.cpus = 1 + ((type & GICD_TYPE_CPUS) >> 5); + printk("GIC: %d lines, %d cpu%s%s (IID %8.8x).\n", + gic.lines, gic.cpus, (gic.cpus == 1) ? "" : "s", + (type & GICD_TYPE_SEC) ? ", secure" : "", + GICD[GICD_IIDR]); + + /* Default all global IRQs to level, active low */ + for ( i = 32; i < gic.lines; i += 16 ) + GICD[GICD_ICFGR + i / 16] = 0x0; + + /* Route all global IRQs to this CPU */ + for ( i = 32; i < gic.lines; i += 4 ) + GICD[GICD_ICFGR + i / 4] = cpumask; + + /* Default priority for global interrupts */ + for ( i = 32; i < gic.lines; i += 4 ) + GICD[GICD_IPRIORITYR + i / 4] = 0xa0a0a0a0; + + /* Disable all global interrupts */ + for ( i = 32; i < gic.lines; i += 32 ) + GICD[GICD_ICENABLER + i / 32] = ~0ul; + + /* Turn on the distributor */ + GICD[GICD_CTLR] = GICD_CTL_ENABLE; +} + +static void __cpuinit gic_cpu_init(void) +{ + int i; + + /* Disable all PPI and enable all SGI */ + GICD[GICD_ICENABLER] = 0xffff0000; /* Disable all PPI */ + GICD[GICD_ISENABLER] = 0x0000ffff; /* Enable all SGI */ + /* Set PPI and SGI priorities */ + for (i = 0; i < 32; i += 4) + GICD[GICD_IPRIORITYR + i / 4] = 0xa0a0a0a0; + + /* Local settings: interface controller */ + GICC[GICC_PMR] = 0xff; /* Don't mask by priority */ + GICC[GICC_BPR] = 0; /* Finest granularity of priority */ + GICC[GICC_CTLR] = GICC_CTL_ENABLE|GICC_CTL_EOI; /* Turn on delivery */ +} + +static void __cpuinit gic_hyp_init(void) +{ + uint32_t vtr; + + vtr = GICH[GICH_VTR]; + nr_lrs = (vtr & GICH_VTR_NRLRGS) + 1; + printk("GICH: %d list registers available\n", nr_lrs); + + GICH[GICH_HCR] = GICH_HCR_EN; + GICH[GICH_MISR] = GICH_MISR_EOI; +} + +/* Set up the GIC */ +void gic_init(void) +{ + /* XXX FIXME get this from devicetree */ + gic.dbase = GIC_BASE_ADDRESS + GIC_DR_OFFSET; + gic.cbase = GIC_BASE_ADDRESS + GIC_CR_OFFSET; + gic.hbase = GIC_BASE_ADDRESS + GIC_HR_OFFSET; + set_fixmap(FIXMAP_GICD, gic.dbase >> PAGE_SHIFT, DEV_SHARED); + BUILD_BUG_ON(FIXMAP_ADDR(FIXMAP_GICC1) != + FIXMAP_ADDR(FIXMAP_GICC2)-PAGE_SIZE); + set_fixmap(FIXMAP_GICC1, gic.cbase >> PAGE_SHIFT, DEV_SHARED); + set_fixmap(FIXMAP_GICC2, (gic.cbase >> PAGE_SHIFT) + 1, DEV_SHARED); + set_fixmap(FIXMAP_GICH, gic.hbase >> PAGE_SHIFT, DEV_SHARED); + + /* Global settings: interrupt distributor */ + spin_lock_init(&gic.lock); + spin_lock(&gic.lock); + + gic_dist_init(); + gic_cpu_init(); + gic_hyp_init(); + + spin_unlock(&gic.lock); +} + +void gic_route_irqs(void) +{ + /* XXX should get these from DT */ + /* GIC maintenance */ + gic_route_irq(25, 1, 1u << smp_processor_id(), 0xa0); + /* Hypervisor Timer */ + gic_route_irq(26, 1, 1u << smp_processor_id(), 0xa0); + /* Timer */ + gic_route_irq(30, 1, 1u << smp_processor_id(), 0xa0); + /* UART */ + gic_route_irq(37, 0, 1u << smp_processor_id(), 0xa0); +} + +void __init release_irq(unsigned int irq) +{ + struct irq_desc *desc; + unsigned long flags; + struct irqaction *action; + + desc = irq_to_desc(irq); + + spin_lock_irqsave(&desc->lock,flags); + action = desc->action; + desc->action = NULL; + desc->status |= IRQ_DISABLED; + + spin_lock(&gic.lock); + desc->handler->shutdown(desc); + spin_unlock(&gic.lock); + + spin_unlock_irqrestore(&desc->lock,flags); + + /* Wait to make sure it's not being used on another CPU */ + do { smp_mb(); } while ( desc->status & IRQ_INPROGRESS ); + + if (action && action->free_on_release) + xfree(action); +} + +static int __setup_irq(struct irq_desc *desc, unsigned int irq, + struct irqaction *new) +{ + if ( desc->action != NULL ) + return -EBUSY; + + desc->action = new; + desc->status &= ~IRQ_DISABLED; + dsb(); + + desc->handler->startup(desc); + + return 0; +} + +int __init setup_irq(unsigned int irq, struct irqaction *new) +{ + int rc; + unsigned long flags; + struct irq_desc *desc; + + desc = irq_to_desc(irq); + + spin_lock_irqsave(&desc->lock, flags); + + rc = __setup_irq(desc, irq, new); + + spin_unlock_irqrestore(&desc->lock,flags); + + return rc; +} + +void gic_set_guest_irq(unsigned int virtual_irq, + unsigned int state, unsigned int priority) +{ + BUG_ON(virtual_irq > nr_lrs); + GICH[GICH_LR + virtual_irq] = state | + GICH_LR_MAINTENANCE_IRQ | + ((priority >> 3) << GICH_LR_PRIORITY_SHIFT) | + ((virtual_irq & GICH_LR_VIRTUAL_MASK) << GICH_LR_VIRTUAL_SHIFT); +} + +void gic_inject_irq_start(void) +{ + uint32_t hcr; + hcr = READ_CP32(HCR); + WRITE_CP32(hcr | HCR_VI, HCR); + isb(); +} + +void gic_inject_irq_stop(void) +{ + uint32_t hcr; + hcr = READ_CP32(HCR); + if (hcr & HCR_VI) { + WRITE_CP32(hcr & ~HCR_VI, HCR); + isb(); + } +} + +int gic_route_irq_to_guest(struct domain *d, unsigned int irq, + const char * devname) +{ + struct irqaction *action; + struct irq_desc *desc = irq_to_desc(irq); + unsigned long flags; + int retval; + + action = xmalloc(struct irqaction); + if (!action) + return -ENOMEM; + + action->dev_id = d; + action->name = devname; + + spin_lock_irqsave(&desc->lock, flags); + + desc->handler = &gic_guest_irq_type; + desc->status |= IRQ_GUEST; + + retval = __setup_irq(desc, irq, action); + if (retval) { + xfree(action); + goto out; + } + +out: + spin_unlock_irqrestore(&desc->lock, flags); + return retval; +} + +/* Accept an interrupt from the GIC and dispatch its handler */ +void gic_interrupt(struct cpu_user_regs *regs, int is_fiq) +{ + uint32_t intack = GICC[GICC_IAR]; + unsigned int irq = intack & GICC_IA_IRQ; + + if ( irq == 1023 ) + /* Spurious interrupt */ + return; + + do_IRQ(regs, irq, is_fiq); +} + +void gicv_setup(struct domain *d) +{ + /* map the gic virtual cpu interface in the gic cpu interface region of + * the guest */ + printk("mapping GICC at %#"PRIx32" to %#"PRIx32"\n", + GIC_BASE_ADDRESS + GIC_CR_OFFSET, + GIC_BASE_ADDRESS + GIC_VR_OFFSET); + map_mmio_regions(d, GIC_BASE_ADDRESS + GIC_CR_OFFSET, + GIC_BASE_ADDRESS + GIC_CR_OFFSET + (2 * PAGE_SIZE) - 1, + GIC_BASE_ADDRESS + GIC_VR_OFFSET); +} + +static void maintenance_interrupt(int irq, void *dev_id, struct cpu_user_regs *regs) +{ + int i, virq; + uint32_t lr; + uint64_t eisr = GICH[GICH_EISR0] | (((uint64_t) GICH[GICH_EISR1]) << 32); + + for ( i = 0; i < 64; i++ ) { + if ( eisr & ((uint64_t)1 << i) ) { + struct pending_irq *p; + + lr = GICH[GICH_LR + i]; + virq = lr & GICH_LR_VIRTUAL_MASK; + GICH[GICH_LR + i] = 0; + + spin_lock(¤t->arch.vgic.lock); + p = irq_to_pending(current, virq); + if ( p->desc != NULL ) { + p->desc->status &= ~IRQ_INPROGRESS; + GICC[GICC_DIR] = virq; + } + gic_inject_irq_stop(); + list_del(&p->link); + INIT_LIST_HEAD(&p->link); + cpu_raise_softirq(current->processor, VGIC_SOFTIRQ); + spin_unlock(¤t->arch.vgic.lock); + } + } +} + +void __cpuinit init_maintenance_interrupt(void) +{ + request_irq(25, maintenance_interrupt, 0, "irq-maintenance", NULL); +} + +/* + * Local variables: + * mode: C + * c-set-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/gic.h b/xen/arch/arm/gic.h new file mode 100644 index 0000000..63b6648 --- /dev/null +++ b/xen/arch/arm/gic.h @@ -0,0 +1,151 @@ +/* + * xen/arch/arm/gic.h + * + * ARM Generic Interrupt Controller support + * + * Tim Deegan <tim@xxxxxxx> + * Copyright (c) 2011 Citrix Systems. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +#ifndef __ARCH_ARM_GIC_H__ +#define __ARCH_ARM_GIC_H__ + +#define GICD_CTLR (0x000/4) +#define GICD_TYPER (0x004/4) +#define GICD_IIDR (0x008/4) +#define GICD_IGROUPR (0x080/4) +#define GICD_IGROUPRN (0x0FC/4) +#define GICD_ISENABLER (0x100/4) +#define GICD_ISENABLERN (0x17C/4) +#define GICD_ICENABLER (0x180/4) +#define GICD_ICENABLERN (0x1fC/4) +#define GICD_ISPENDR (0x200/4) +#define GICD_ISPENDRN (0x27C/4) +#define GICD_ICPENDR (0x280/4) +#define GICD_ICPENDRN (0x2FC/4) +#define GICD_ISACTIVER (0x300/4) +#define GICD_ISACTIVERN (0x37C/4) +#define GICD_ICACTIVER (0x380/4) +#define GICD_ICACTIVERN (0x3FC/4) +#define GICD_IPRIORITYR (0x400/4) +#define GICD_IPRIORITYRN (0x7F8/4) +#define GICD_ITARGETSR (0x800/4) +#define GICD_ITARGETSRN (0xBF8/4) +#define GICD_ICFGR (0xC00/4) +#define GICD_ICFGRN (0xCFC/4) +#define GICD_NSACR (0xE00/4) +#define GICD_NSACRN (0xEFC/4) +#define GICD_ICPIDR2 (0xFE8/4) +#define GICD_SGIR (0xF00/4) +#define GICD_CPENDSGIR (0xF10/4) +#define GICD_CPENDSGIRN (0xF1C/4) +#define GICD_SPENDSGIR (0xF20/4) +#define GICD_SPENDSGIRN (0xF2C/4) +#define GICD_ICPIDR2 (0xFE8/4) + +#define GICC_CTLR (0x0000/4) +#define GICC_PMR (0x0004/4) +#define GICC_BPR (0x0008/4) +#define GICC_IAR (0x000C/4) +#define GICC_EOIR (0x0010/4) +#define GICC_RPR (0x0014/4) +#define GICC_HPPIR (0x0018/4) +#define GICC_APR (0x00D0/4) +#define GICC_NSAPR (0x00E0/4) +#define GICC_DIR (0x1000/4) + +#define GICH_HCR (0x00/4) +#define GICH_VTR (0x04/4) +#define GICH_VMCR (0x08/4) +#define GICH_MISR (0x10/4) +#define GICH_EISR0 (0x20/4) +#define GICH_EISR1 (0x24/4) +#define GICH_ELRSR0 (0x30/4) +#define GICH_ELRSR1 (0x34/4) +#define GICH_APR (0xF0/4) +#define GICH_LR (0x100/4) + +/* Register bits */ +#define GICD_CTL_ENABLE 0x1 + +#define GICD_TYPE_LINES 0x01f +#define GICD_TYPE_CPUS 0x0e0 +#define GICD_TYPE_SEC 0x400 + +#define GICC_CTL_ENABLE 0x1 +#define GICC_CTL_EOI (0x1 << 9) + +#define GICC_IA_IRQ 0x03ff +#define GICC_IA_CPU 0x1c00 + +#define GICH_HCR_EN (1 << 0) +#define GICH_HCR_UIE (1 << 1) +#define GICH_HCR_LRENPIE (1 << 2) +#define GICH_HCR_NPIE (1 << 3) +#define GICH_HCR_VGRP0EIE (1 << 4) +#define GICH_HCR_VGRP0DIE (1 << 5) +#define GICH_HCR_VGRP1EIE (1 << 6) +#define GICH_HCR_VGRP1DIE (1 << 7) + +#define GICH_MISR_EOI (1 << 0) +#define GICH_MISR_U (1 << 1) +#define GICH_MISR_LRENP (1 << 2) +#define GICH_MISR_NP (1 << 3) +#define GICH_MISR_VGRP0E (1 << 4) +#define GICH_MISR_VGRP0D (1 << 5) +#define GICH_MISR_VGRP1E (1 << 6) +#define GICH_MISR_VGRP1D (1 << 7) + +#define GICH_LR_VIRTUAL_MASK 0x3ff +#define GICH_LR_VIRTUAL_SHIFT 0 +#define GICH_LR_PHYSICAL_MASK 0x3ff +#define GICH_LR_PHYSICAL_SHIFT 10 +#define GICH_LR_STATE_MASK 0x3 +#define GICH_LR_STATE_SHIFT 28 +#define GICH_LR_PRIORITY_SHIFT 23 +#define GICH_LR_MAINTENANCE_IRQ (1<<19) +#define GICH_LR_PENDING (1<<28) +#define GICH_LR_ACTIVE (1<<29) +#define GICH_LR_GRP1 (1<<30) +#define GICH_LR_HW (1<<31) +#define GICH_LR_CPUID_SHIFT 9 +#define GICH_VTR_NRLRGS 0x3f + +extern struct pending_irq *irq_to_pending(struct vcpu *v, unsigned int irq); + +extern void gic_route_irqs(void); + +extern void __cpuinit init_maintenance_interrupt(void); +extern void gic_set_guest_irq(unsigned int irq, + unsigned int state, unsigned int priority); +extern void gic_inject_irq_start(void); +extern void gic_inject_irq_stop(void); +extern int gic_route_irq_to_guest(struct domain *d, unsigned int irq, + const char * devname); + +/* Accept an interrupt from the GIC and dispatch its handler */ +extern void gic_interrupt(struct cpu_user_regs *regs, int is_fiq); +/* Bring up the interrupt controller */ +extern void gic_init(void); +/* setup the gic virtual interface for a guest */ +extern void gicv_setup(struct domain *d); +#endif + +/* + * Local variables: + * mode: C + * c-set-style: "BSD" + * c-basic-offset: 4 + * indent-tabs-mode: nil + * End: + */ -- 1.7.2.5 _______________________________________________ Xen-devel mailing list Xen-devel@xxxxxxxxxxxxxxxxxxx http://lists.xensource.com/xen-devel
|
Lists.xenproject.org is hosted with RackSpace, monitoring our |