diff options
author | Keir Fraser <keir.fraser@citrix.com> | 2008-02-11 10:51:41 +0000 |
---|---|---|
committer | Keir Fraser <keir.fraser@citrix.com> | 2008-02-11 10:51:41 +0000 |
commit | be0999cb87d4e5fbaa9b96f604918db02229f71e (patch) | |
tree | 4547427239ceeb400356f98446bf50353f665030 | |
parent | b65a97ff7378c6aae7f3c9326823b58c29d28a07 (diff) | |
download | xen-be0999cb87d4e5fbaa9b96f604918db02229f71e.tar.gz xen-be0999cb87d4e5fbaa9b96f604918db02229f71e.tar.bz2 xen-be0999cb87d4e5fbaa9b96f604918db02229f71e.zip |
vt-d: Remap interrupt for passthru device if such HW is detected on VT-d platforms.
Signed-off-by: Allen Kay <allen.m.kay@intel.com>
-rw-r--r-- | xen/arch/x86/hvm/vmx/vtd/Makefile | 1 | ||||
-rw-r--r-- | xen/arch/x86/hvm/vmx/vtd/extern.h | 7 | ||||
-rw-r--r-- | xen/arch/x86/hvm/vmx/vtd/intel-iommu.c | 6 | ||||
-rw-r--r-- | xen/arch/x86/hvm/vmx/vtd/intremap.c | 340 | ||||
-rw-r--r-- | xen/include/asm-x86/io_apic.h | 5 | ||||
-rw-r--r-- | xen/include/asm-x86/iommu.h | 4 |
6 files changed, 356 insertions, 7 deletions
diff --git a/xen/arch/x86/hvm/vmx/vtd/Makefile b/xen/arch/x86/hvm/vmx/vtd/Makefile index 1cae6385ab..dcff4e3ab1 100644 --- a/xen/arch/x86/hvm/vmx/vtd/Makefile +++ b/xen/arch/x86/hvm/vmx/vtd/Makefile @@ -3,3 +3,4 @@ obj-y += dmar.o obj-y += utils.o obj-y += io.o obj-y += qinval.o +obj-y += intremap.o diff --git a/xen/arch/x86/hvm/vmx/vtd/extern.h b/xen/arch/x86/hvm/vmx/vtd/extern.h index 6143b935b9..9e2ac576ea 100644 --- a/xen/arch/x86/hvm/vmx/vtd/extern.h +++ b/xen/arch/x86/hvm/vmx/vtd/extern.h @@ -23,11 +23,6 @@ #include "dmar.h" -extern int iommu_setup_done; -extern int vtd2_thurley_enabled; -extern int vtd2_qinval_enabled; - -extern spinlock_t ioapic_lock; extern struct qi_ctrl *qi_ctrl; extern struct ir_ctrl *ir_ctrl; @@ -37,6 +32,7 @@ void print_vtd_entries(struct domain *d, struct iommu *iommu, void pdev_flr(u8 bus, u8 devfn); int qinval_setup(struct iommu *iommu); +int intremap_setup(struct iommu *iommu); int queue_invalidate_context(struct iommu *iommu, u16 did, u16 source_id, u8 function_mask, u8 granu); int queue_invalidate_iotlb(struct iommu *iommu, @@ -46,7 +42,6 @@ int queue_invalidate_iec(struct iommu *iommu, int invalidate_sync(struct iommu *iommu); int iommu_flush_iec_global(struct iommu *iommu); int iommu_flush_iec_index(struct iommu *iommu, u8 im, u16 iidx); -void gsi_remapping(unsigned int gsi); void print_iommu_regs(struct acpi_drhd_unit *drhd); int vtd_hw_check(void); struct iommu * ioapic_to_iommu(unsigned int apic_id); diff --git a/xen/arch/x86/hvm/vmx/vtd/intel-iommu.c b/xen/arch/x86/hvm/vmx/vtd/intel-iommu.c index 4b05f1d87c..eb2c5eef8e 100644 --- a/xen/arch/x86/hvm/vmx/vtd/intel-iommu.c +++ b/xen/arch/x86/hvm/vmx/vtd/intel-iommu.c @@ -1816,9 +1816,13 @@ static int init_vtd_hw(void) flush->context = flush_context_reg; flush->iotlb = flush_iotlb_reg; - if ( qinval_setup(iommu) != 0); + if ( qinval_setup(iommu) != 0 ) dprintk(XENLOG_ERR VTDPREFIX, "Queued Invalidation hardware not found\n"); + + if ( intremap_setup(iommu) != 0 ) + dprintk(XENLOG_ERR VTDPREFIX, + "Interrupt Remapping hardware not found\n"); } return 0; } diff --git a/xen/arch/x86/hvm/vmx/vtd/intremap.c b/xen/arch/x86/hvm/vmx/vtd/intremap.c new file mode 100644 index 0000000000..21645599b6 --- /dev/null +++ b/xen/arch/x86/hvm/vmx/vtd/intremap.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2006, Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) Allen Kay <allen.m.kay@intel.com> + * Copyright (C) Xiaohui Xin <xiaohui.xin@intel.com> + */ + +#include <xen/config.h> +#include <xen/lib.h> +#include <xen/init.h> +#include <xen/irq.h> +#include <xen/delay.h> +#include <xen/sched.h> +#include <xen/acpi.h> +#include <xen/keyhandler.h> +#include <xen/spinlock.h> +#include <asm/io.h> +#include <asm/mc146818rtc.h> +#include <asm/smp.h> +#include <asm/desc.h> +#include <mach_apic.h> +#include <io_ports.h> + +#include <xen/spinlock.h> +#include <xen/xmalloc.h> +#include <xen/domain_page.h> +#include <asm/delay.h> +#include <asm/string.h> +#include <asm/iommu.h> +#include <asm/hvm/vmx/intel-iommu.h> +#include "dmar.h" +#include "vtd.h" +#include "pci-direct.h" +#include "pci_regs.h" +#include "msi.h" +#include "extern.h" + +u16 apicid_to_bdf(int apic_id) +{ + struct acpi_drhd_unit *drhd = ioapic_to_drhd(apic_id); + struct acpi_ioapic_unit *acpi_ioapic_unit; + + list_for_each_entry ( acpi_ioapic_unit, &drhd->ioapic_list, list ) + if ( acpi_ioapic_unit->apic_id == apic_id ) + return acpi_ioapic_unit->ioapic.info; + + dprintk(XENLOG_ERR VTDPREFIX, "Didn't find the bdf for the apic_id!\n"); + return 0; +} + +static void remap_entry_to_ioapic_rte( + struct iommu *iommu, struct IO_APIC_route_entry *old_rte) +{ + struct iremap_entry *iremap_entry = NULL; + struct IO_APIC_route_remap_entry *remap_rte; + unsigned int index; + unsigned long flags; + struct ir_ctrl *ir_ctrl = iommu_ir_ctrl(iommu); + + if ( ir_ctrl == NULL ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "remap_entry_to_ioapic_rte: ir_ctl == NULL"); + return; + } + + remap_rte = (struct IO_APIC_route_remap_entry *) old_rte; + index = (remap_rte->index_15 << 15) + remap_rte->index_0_14; + + if ( index > ir_ctrl->iremap_index ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "Index is larger than remap table entry size. Error!\n"); + return; + } + + spin_lock_irqsave(&ir_ctrl->iremap_lock, flags); + + iremap_entry = &ir_ctrl->iremap[index]; + + old_rte->vector = iremap_entry->lo.vector; + old_rte->delivery_mode = iremap_entry->lo.dlm; + old_rte->dest_mode = iremap_entry->lo.dm; + old_rte->trigger = iremap_entry->lo.tm; + old_rte->__reserved_2 = 0; + old_rte->dest.logical.__reserved_1 = 0; + old_rte->dest.logical.logical_dest = iremap_entry->lo.dst; + + spin_unlock_irqrestore(&ir_ctrl->iremap_lock, flags); +} + +static void ioapic_rte_to_remap_entry(struct iommu *iommu, + int apic_id, struct IO_APIC_route_entry *old_rte) +{ + struct iremap_entry *iremap_entry = NULL; + struct IO_APIC_route_remap_entry *remap_rte; + unsigned int index; + unsigned long flags; + int ret = 0; + struct ir_ctrl *ir_ctrl = iommu_ir_ctrl(iommu); + + remap_rte = (struct IO_APIC_route_remap_entry *) old_rte; + spin_lock_irqsave(&ir_ctrl->iremap_lock, flags); + index = ir_ctrl->iremap_index; + if ( index > IREMAP_ENTRY_NR - 1 ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "The interrupt number is more than 256!\n"); + goto out; + } + + iremap_entry = &(ir_ctrl->iremap[index]); + if ( *(u64 *)iremap_entry != 0 ) + dprintk(XENLOG_WARNING VTDPREFIX, + "Interrupt remapping entry is in use already!\n"); + iremap_entry->lo.fpd = 0; + iremap_entry->lo.dm = old_rte->dest_mode; + iremap_entry->lo.rh = 0; + iremap_entry->lo.tm = old_rte->trigger; + iremap_entry->lo.dlm = old_rte->delivery_mode; + iremap_entry->lo.avail = 0; + iremap_entry->lo.res_1 = 0; + iremap_entry->lo.vector = old_rte->vector; + iremap_entry->lo.res_2 = 0; + iremap_entry->lo.dst = (old_rte->dest.logical.logical_dest << 8); + iremap_entry->hi.sid = apicid_to_bdf(apic_id); + iremap_entry->hi.sq = 0; /* comparing all 16-bit of SID */ + iremap_entry->hi.svt = 1; /* turn on requestor ID verification SID/SQ */ + iremap_entry->hi.res_1 = 0; + iremap_entry->lo.p = 1; /* finally, set present bit */ + ir_ctrl->iremap_index++; + + iommu_flush_iec_index(iommu, 0, index); + ret = invalidate_sync(iommu); + + /* now construct new ioapic rte entry */ + remap_rte->vector = old_rte->vector; + remap_rte->delivery_mode = 0; /* has to be 0 for remap format */ + remap_rte->index_15 = index & 0x8000; + remap_rte->index_0_14 = index & 0x7fff; + remap_rte->delivery_status = old_rte->delivery_status; + remap_rte->polarity = old_rte->polarity; + remap_rte->irr = old_rte->irr; + remap_rte->trigger = old_rte->trigger; + remap_rte->mask = 1; + remap_rte->reserved = 0; + remap_rte->format = 1; /* indicate remap format */ +out: + spin_unlock_irqrestore(&ir_ctrl->iremap_lock, flags); + return; +} + +unsigned int +io_apic_read_remap_rte( + unsigned int apic, unsigned int reg) +{ + struct IO_APIC_route_entry old_rte = { 0 }; + struct IO_APIC_route_remap_entry *remap_rte; + int rte_upper = (reg & 1) ? 1 : 0; + struct iommu *iommu = ioapic_to_iommu(mp_ioapics[apic].mpc_apicid); + struct ir_ctrl *ir_ctrl = iommu_ir_ctrl(iommu); + + if ( !iommu || !(ir_ctrl->iremap) ) + { + *IO_APIC_BASE(apic) = reg; + return *(IO_APIC_BASE(apic)+4); + } + + if ( rte_upper ) + reg--; + + /* read lower and upper 32-bits of rte entry */ + *IO_APIC_BASE(apic) = reg; + *(((u32 *)&old_rte) + 0) = *(IO_APIC_BASE(apic)+4); + *IO_APIC_BASE(apic) = reg + 1; + *(((u32 *)&old_rte) + 1) = *(IO_APIC_BASE(apic)+4); + + remap_rte = (struct IO_APIC_route_remap_entry *) &old_rte; + + if ( remap_rte->mask || (remap_rte->format == 0) ) + { + *IO_APIC_BASE(apic) = reg; + return *(IO_APIC_BASE(apic)+4); + } + + remap_entry_to_ioapic_rte(iommu, &old_rte); + if ( rte_upper ) + { + *IO_APIC_BASE(apic) = reg + 1; + return (*(((u32 *)&old_rte) + 1)); + } + else + { + *IO_APIC_BASE(apic) = reg; + return (*(((u32 *)&old_rte) + 0)); + } +} + +void +io_apic_write_remap_rte( + unsigned int apic, unsigned int reg, unsigned int value) +{ + struct IO_APIC_route_entry old_rte = { 0 }; + struct IO_APIC_route_remap_entry *remap_rte; + int rte_upper = (reg & 1) ? 1 : 0; + struct iommu *iommu = ioapic_to_iommu(mp_ioapics[apic].mpc_apicid); + struct ir_ctrl *ir_ctrl = iommu_ir_ctrl(iommu); + + if ( !iommu || !(ir_ctrl->iremap) ) + { + *IO_APIC_BASE(apic) = reg; + *(IO_APIC_BASE(apic)+4) = value; + return; + } + + if ( rte_upper ) + reg--; + + /* read both lower and upper 32-bits of rte entry */ + *IO_APIC_BASE(apic) = reg; + *(((u32 *)&old_rte) + 0) = *(IO_APIC_BASE(apic)+4); + *IO_APIC_BASE(apic) = reg + 1; + *(((u32 *)&old_rte) + 1) = *(IO_APIC_BASE(apic)+4); + + remap_rte = (struct IO_APIC_route_remap_entry *) &old_rte; + if ( remap_rte->mask || (remap_rte->format == 0) ) + { + *IO_APIC_BASE(apic) = rte_upper ? ++reg : reg; + *(IO_APIC_BASE(apic)+4) = value; + return; + } + + *(((u32 *)&old_rte) + rte_upper) = value; + ioapic_rte_to_remap_entry(iommu, mp_ioapics[apic].mpc_apicid, &old_rte); + + /* write new entry to ioapic */ + *IO_APIC_BASE(apic) = reg; + *(IO_APIC_BASE(apic)+4) = *(((int *)&old_rte)+0); + *IO_APIC_BASE(apic) = reg + 1; + *(IO_APIC_BASE(apic)+4) = *(((int *)&old_rte)+1); +} + +int intremap_setup(struct iommu *iommu) +{ + struct ir_ctrl *ir_ctrl; + unsigned long start_time; + u64 paddr; + + if ( !ecap_intr_remap(iommu->ecap) ) + return -ENODEV; + + ir_ctrl = iommu_ir_ctrl(iommu); + if ( ir_ctrl->iremap == NULL ) + { + ir_ctrl->iremap = alloc_xenheap_page(); + if ( ir_ctrl->iremap == NULL ) + { + dprintk(XENLOG_WARNING VTDPREFIX, + "Cannot allocate memory for ir_ctrl->iremap\n"); + return -ENODEV; + } + memset(ir_ctrl->iremap, 0, PAGE_SIZE); + } + + paddr = virt_to_maddr(ir_ctrl->iremap); +#if defined(ENABLED_EXTENDED_INTERRUPT_SUPPORT) + /* set extended interrupt mode bit */ + paddr |= ecap_ext_intr(iommu->ecap) ? (1 << IRTA_REG_EIMI_SHIFT) : 0; +#endif + /* size field = 256 entries per 4K page = 8 - 1 */ + paddr |= 7; + dmar_writeq(iommu->reg, DMAR_IRTA_REG, paddr); + + /* set SIRTP */ + iommu->gcmd |= DMA_GCMD_SIRTP; + dmar_writel(iommu->reg, DMAR_GCMD_REG, iommu->gcmd); + + /* Make sure hardware complete it */ + start_time = jiffies; + while ( !(dmar_readl(iommu->reg, DMAR_GSTS_REG) & DMA_GSTS_SIRTPS) ) + { + if ( time_after(jiffies, start_time + DMAR_OPERATION_TIMEOUT) ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "Cannot set SIRTP field for interrupt remapping\n"); + return -ENODEV; + } + cpu_relax(); + } + + /* enable comaptiblity format interrupt pass through */ + iommu->gcmd |= DMA_GCMD_CFI; + dmar_writel(iommu->reg, DMAR_GCMD_REG, iommu->gcmd); + + start_time = jiffies; + while ( !(dmar_readl(iommu->reg, DMAR_GSTS_REG) & DMA_GSTS_CFIS) ) + { + if ( time_after(jiffies, start_time + DMAR_OPERATION_TIMEOUT) ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "Cannot set CFI field for interrupt remapping\n"); + return -ENODEV; + } + cpu_relax(); + } + + /* enable interrupt remapping hardware */ + iommu->gcmd |= DMA_GCMD_IRE; + dmar_writel(iommu->reg, DMAR_GCMD_REG, iommu->gcmd); + + start_time = jiffies; + while ( !(dmar_readl(iommu->reg, DMAR_GSTS_REG) & DMA_GSTS_IRES) ) + { + if ( time_after(jiffies, start_time + DMAR_OPERATION_TIMEOUT) ) + { + dprintk(XENLOG_ERR VTDPREFIX, + "Cannot set IRE field for interrupt remapping\n"); + return -ENODEV; + } + cpu_relax(); + } + + /* After set SIRTP, we should do globally invalidate the IEC */ + iommu_flush_iec_global(iommu); + + return 0; +} diff --git a/xen/include/asm-x86/io_apic.h b/xen/include/asm-x86/io_apic.h index e8e102a6b8..86c91b6762 100644 --- a/xen/include/asm-x86/io_apic.h +++ b/xen/include/asm-x86/io_apic.h @@ -6,6 +6,7 @@ #include <asm/mpspec.h> #include <asm/apicdef.h> #include <asm/fixmap.h> +#include <asm/iommu.h> /* * Intel IO-APIC support for SMP and UP systems. @@ -124,12 +125,16 @@ extern int mpc_default_type; static inline unsigned int io_apic_read(unsigned int apic, unsigned int reg) { + if (vtd_enabled) + return io_apic_read_remap_rte(apic, reg); *IO_APIC_BASE(apic) = reg; return *(IO_APIC_BASE(apic)+4); } static inline void io_apic_write(unsigned int apic, unsigned int reg, unsigned int value) { + if (vtd_enabled) + return io_apic_write_remap_rte(apic, reg, value); *IO_APIC_BASE(apic) = reg; *(IO_APIC_BASE(apic)+4) = value; } diff --git a/xen/include/asm-x86/iommu.h b/xen/include/asm-x86/iommu.h index daec64bf76..1f5e61ee63 100644 --- a/xen/include/asm-x86/iommu.h +++ b/xen/include/asm-x86/iommu.h @@ -81,6 +81,10 @@ int hvm_do_IRQ_dpci(struct domain *d, unsigned int irq); int dpci_ioport_intercept(ioreq_t *p); int pt_irq_create_bind_vtd(struct domain *d, xen_domctl_bind_pt_irq_t *pt_irq_bind); +unsigned int io_apic_read_remap_rte( + unsigned int apic, unsigned int reg); +void io_apic_write_remap_rte(unsigned int apic, + unsigned int reg, unsigned int value); #define PT_IRQ_TIME_OUT MILLISECS(8) #define VTDPREFIX "[VT-D]" |