📄 iommu.c
字号:
/* * 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) Ashok Raj <ashok.raj@intel.com> * Copyright (C) Shaohua Li <shaohua.li@intel.com> * Copyright (C) Allen Kay <allen.m.kay@intel.com> - adapted to xen */#include <xen/irq.h>#include <xen/sched.h>#include <xen/xmalloc.h>#include <xen/domain_page.h>#include <xen/iommu.h>#include <xen/numa.h>#include <xen/time.h>#include <xen/pci.h>#include <xen/pci_regs.h>#include <xen/keyhandler.h>#include <asm/paging.h>#include <asm/msi.h>#include "iommu.h"#include "dmar.h"#include "extern.h"#include "vtd.h"#define domain_iommu_domid(d) ((d)->arch.hvm_domain.hvm_iommu.iommu_domid)static spinlock_t domid_bitmap_lock; /* protect domain id bitmap */static int domid_bitmap_size; /* domain id bitmap size in bits */static unsigned long *domid_bitmap; /* iommu domain id bitmap */static void setup_dom0_devices(struct domain *d);static void setup_dom0_rmrr(struct domain *d);#define DID_FIELD_WIDTH 16#define DID_HIGH_OFFSET 8static void context_set_domain_id(struct context_entry *context, struct domain *d){ unsigned long flags; domid_t iommu_domid = domain_iommu_domid(d); if ( iommu_domid == 0 ) { spin_lock_irqsave(&domid_bitmap_lock, flags); iommu_domid = find_first_zero_bit(domid_bitmap, domid_bitmap_size); set_bit(iommu_domid, domid_bitmap); spin_unlock_irqrestore(&domid_bitmap_lock, flags); d->arch.hvm_domain.hvm_iommu.iommu_domid = iommu_domid; } context->hi &= (1 << DID_HIGH_OFFSET) - 1; context->hi |= iommu_domid << DID_HIGH_OFFSET;}static void iommu_domid_release(struct domain *d){ domid_t iommu_domid = domain_iommu_domid(d); if ( iommu_domid != 0 ) { d->arch.hvm_domain.hvm_iommu.iommu_domid = 0; clear_bit(iommu_domid, domid_bitmap); }}static struct intel_iommu *alloc_intel_iommu(void){ struct intel_iommu *intel; intel = xmalloc(struct intel_iommu); if ( intel == NULL ) return NULL; memset(intel, 0, sizeof(struct intel_iommu)); spin_lock_init(&intel->qi_ctrl.qinval_lock); spin_lock_init(&intel->qi_ctrl.qinval_poll_lock); spin_lock_init(&intel->ir_ctrl.iremap_lock); return intel;}static void free_intel_iommu(struct intel_iommu *intel){ xfree(intel);}struct qi_ctrl *iommu_qi_ctrl(struct iommu *iommu){ return iommu ? &iommu->intel->qi_ctrl : NULL;}struct ir_ctrl *iommu_ir_ctrl(struct iommu *iommu){ return iommu ? &iommu->intel->ir_ctrl : NULL;}struct iommu_flush *iommu_get_flush(struct iommu *iommu){ return iommu ? &iommu->intel->flush : NULL;}static unsigned int clflush_size;static int iommus_incoherent;static void __iommu_flush_cache(void *addr, int size){ int i; if ( !iommus_incoherent ) return; for ( i = 0; i < size; i += clflush_size ) clflush((char *)addr + i);}void iommu_flush_cache_entry(void *addr){ __iommu_flush_cache(addr, 8);}void iommu_flush_cache_page(void *addr){ __iommu_flush_cache(addr, PAGE_SIZE_4K);}int nr_iommus;/* context entry handling */static u64 bus_to_context_maddr(struct iommu *iommu, u8 bus){ struct root_entry *root, *root_entries; unsigned long flags; u64 maddr; spin_lock_irqsave(&iommu->lock, flags); root_entries = (struct root_entry *)map_vtd_domain_page(iommu->root_maddr); root = &root_entries[bus]; if ( !root_present(*root) ) { maddr = alloc_pgtable_maddr(); if ( maddr == 0 ) { spin_unlock_irqrestore(&iommu->lock, flags); return 0; } set_root_value(*root, maddr); set_root_present(*root); iommu_flush_cache_entry(root); } maddr = (u64) get_context_addr(*root); unmap_vtd_domain_page(root_entries); spin_unlock_irqrestore(&iommu->lock, flags); return maddr;}static int device_context_mapped(struct iommu *iommu, u8 bus, u8 devfn){ struct root_entry *root, *root_entries; struct context_entry *context; u64 context_maddr; int ret; unsigned long flags; spin_lock_irqsave(&iommu->lock, flags); root_entries = (struct root_entry *)map_vtd_domain_page(iommu->root_maddr); root = &root_entries[bus]; if ( !root_present(*root) ) { ret = 0; goto out; } context_maddr = get_context_addr(*root); context = (struct context_entry *)map_vtd_domain_page(context_maddr); ret = context_present(context[devfn]); unmap_vtd_domain_page(context); out: unmap_vtd_domain_page(root_entries); spin_unlock_irqrestore(&iommu->lock, flags); return ret;}static u64 addr_to_dma_page_maddr(struct domain *domain, u64 addr, int alloc){ struct hvm_iommu *hd = domain_hvm_iommu(domain); int addr_width = agaw_to_width(hd->agaw); struct dma_pte *parent, *pte = NULL; int level = agaw_to_level(hd->agaw); int offset; unsigned long flags; u64 pte_maddr = 0, maddr; u64 *vaddr = NULL; addr &= (((u64)1) << addr_width) - 1; spin_lock_irqsave(&hd->mapping_lock, flags); if ( hd->pgd_maddr == 0 ) if ( !alloc || ((hd->pgd_maddr = alloc_pgtable_maddr()) == 0) ) goto out; parent = (struct dma_pte *)map_vtd_domain_page(hd->pgd_maddr); while ( level > 1 ) { offset = address_level_offset(addr, level); pte = &parent[offset]; if ( dma_pte_addr(*pte) == 0 ) { if ( !alloc ) break; maddr = alloc_pgtable_maddr(); dma_set_pte_addr(*pte, maddr); vaddr = map_vtd_domain_page(maddr); if ( !vaddr ) break; /* * high level table always sets r/w, last level * page table control read/write */ dma_set_pte_readable(*pte); dma_set_pte_writable(*pte); iommu_flush_cache_entry(pte); } else { vaddr = map_vtd_domain_page(pte->val); if ( !vaddr ) break; } if ( level == 2 ) { pte_maddr = pte->val & PAGE_MASK_4K; unmap_vtd_domain_page(vaddr); break; } unmap_vtd_domain_page(parent); parent = (struct dma_pte *)vaddr; vaddr = NULL; level--; } unmap_vtd_domain_page(parent); out: spin_unlock_irqrestore(&hd->mapping_lock, flags); return pte_maddr;}static void iommu_flush_write_buffer(struct iommu *iommu){ u32 val; unsigned long flag; s_time_t start_time; if ( !cap_rwbf(iommu->cap) ) return; val = iommu->gcmd | DMA_GCMD_WBF; spin_lock_irqsave(&iommu->register_lock, flag); dmar_writel(iommu->reg, DMAR_GCMD_REG, val); /* Make sure hardware complete it */ start_time = NOW(); for ( ; ; ) { val = dmar_readl(iommu->reg, DMAR_GSTS_REG); if ( !(val & DMA_GSTS_WBFS) ) break; if ( NOW() > start_time + DMAR_OPERATION_TIMEOUT ) panic("%s: DMAR hardware is malfunctional," " please disable IOMMU\n", __func__); cpu_relax(); } spin_unlock_irqrestore(&iommu->register_lock, flag);}/* return value determine if we need a write buffer flush */static int flush_context_reg( void *_iommu, u16 did, u16 source_id, u8 function_mask, u64 type, int non_present_entry_flush){ struct iommu *iommu = (struct iommu *) _iommu; u64 val = 0; unsigned long flag; s_time_t start_time; /* * In the non-present entry flush case, if hardware doesn't cache * non-present entry we do nothing and if hardware cache non-present * entry, we flush entries of domain 0 (the domain id is used to cache * any non-present entries) */ if ( non_present_entry_flush ) { if ( !cap_caching_mode(iommu->cap) ) return 1; else did = 0; } /* use register invalidation */ switch ( type ) { case DMA_CCMD_GLOBAL_INVL: val = DMA_CCMD_GLOBAL_INVL; break; case DMA_CCMD_DOMAIN_INVL: val = DMA_CCMD_DOMAIN_INVL|DMA_CCMD_DID(did); break; case DMA_CCMD_DEVICE_INVL: val = DMA_CCMD_DEVICE_INVL|DMA_CCMD_DID(did) |DMA_CCMD_SID(source_id)|DMA_CCMD_FM(function_mask); break; default: BUG(); } val |= DMA_CCMD_ICC; spin_lock_irqsave(&iommu->register_lock, flag); dmar_writeq(iommu->reg, DMAR_CCMD_REG, val); /* Make sure hardware complete it */ start_time = NOW(); for ( ; ; ) { val = dmar_readq(iommu->reg, DMAR_CCMD_REG); if ( !(val & DMA_CCMD_ICC) ) break; if ( NOW() > start_time + DMAR_OPERATION_TIMEOUT ) panic("%s: DMAR hardware is malfunctional," " please disable IOMMU\n", __func__); cpu_relax(); } spin_unlock_irqrestore(&iommu->register_lock, flag); /* flush context entry will implictly flush write buffer */ return 0;}static int inline iommu_flush_context_global( struct iommu *iommu, int non_present_entry_flush){ struct iommu_flush *flush = iommu_get_flush(iommu); return flush->context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL, non_present_entry_flush);}static int inline iommu_flush_context_domain( struct iommu *iommu, u16 did, int non_present_entry_flush){ struct iommu_flush *flush = iommu_get_flush(iommu); return flush->context(iommu, did, 0, 0, DMA_CCMD_DOMAIN_INVL, non_present_entry_flush);}static int inline iommu_flush_context_device( struct iommu *iommu, u16 did, u16 source_id, u8 function_mask, int non_present_entry_flush){ struct iommu_flush *flush = iommu_get_flush(iommu); return flush->context(iommu, did, source_id, function_mask, DMA_CCMD_DEVICE_INVL, non_present_entry_flush);}/* return value determine if we need a write buffer flush */static int flush_iotlb_reg(void *_iommu, u16 did, u64 addr, unsigned int size_order, u64 type, int non_present_entry_flush){ struct iommu *iommu = (struct iommu *) _iommu; int tlb_offset = ecap_iotlb_offset(iommu->ecap); u64 val = 0, val_iva = 0; unsigned long flag; s_time_t start_time; /* * In the non-present entry flush case, if hardware doesn't cache * non-present entry we do nothing and if hardware cache non-present * entry, we flush entries of domain 0 (the domain id is used to cache * any non-present entries) */ if ( non_present_entry_flush ) { if ( !cap_caching_mode(iommu->cap) ) return 1; else did = 0; } /* use register invalidation */ switch ( type ) { case DMA_TLB_GLOBAL_FLUSH: /* global flush doesn't need set IVA_REG */ val = DMA_TLB_GLOBAL_FLUSH|DMA_TLB_IVT; break; case DMA_TLB_DSI_FLUSH: val = DMA_TLB_DSI_FLUSH|DMA_TLB_IVT|DMA_TLB_DID(did); break; case DMA_TLB_PSI_FLUSH: val = DMA_TLB_PSI_FLUSH|DMA_TLB_IVT|DMA_TLB_DID(did); /* Note: always flush non-leaf currently */ val_iva = size_order | addr; break; default: BUG(); } /* Note: set drain read/write */ if ( cap_read_drain(iommu->cap) ) val |= DMA_TLB_READ_DRAIN; if ( cap_write_drain(iommu->cap) ) val |= DMA_TLB_WRITE_DRAIN; spin_lock_irqsave(&iommu->register_lock, flag); /* Note: Only uses first TLB reg currently */ if ( val_iva ) dmar_writeq(iommu->reg, tlb_offset, val_iva); dmar_writeq(iommu->reg, tlb_offset + 8, val); /* Make sure hardware complete it */ start_time = NOW(); for ( ; ; ) { val = dmar_readq(iommu->reg, tlb_offset + 8); if ( !(val & DMA_TLB_IVT) ) break; if ( NOW() > start_time + DMAR_OPERATION_TIMEOUT ) panic("%s: DMAR hardware is malfunctional," " please disable IOMMU\n", __func__); cpu_relax(); } spin_unlock_irqrestore(&iommu->register_lock, flag); /* check IOTLB invalidation granularity */ if ( DMA_TLB_IAIG(val) == 0 ) dprintk(XENLOG_ERR VTDPREFIX, "IOMMU: flush IOTLB failed\n"); if ( DMA_TLB_IAIG(val) != DMA_TLB_IIRG(type) ) dprintk(XENLOG_INFO VTDPREFIX, "IOMMU: tlb flush request %x, actual %x\n", (u32)DMA_TLB_IIRG(type), (u32)DMA_TLB_IAIG(val)); /* flush context entry will implictly flush write buffer */ return 0;}static int inline iommu_flush_iotlb_global(struct iommu *iommu, int non_present_entry_flush){ struct iommu_flush *flush = iommu_get_flush(iommu); return flush->iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH, non_present_entry_flush);}static int inline iommu_flush_iotlb_dsi(struct iommu *iommu, u16 did, int non_present_entry_flush){ struct iommu_flush *flush = iommu_get_flush(iommu); return flush->iotlb(iommu, did, 0, 0, DMA_TLB_DSI_FLUSH, non_present_entry_flush);}static int inline get_alignment(u64 base, unsigned int size){ int t = 0; u64 end; end = base + size - 1; while ( base != end ) { t++; base >>= 1; end >>= 1; } return t;}static int inline iommu_flush_iotlb_psi( struct iommu *iommu, u16 did,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -