|
@@ -45,7 +45,6 @@
|
|
#include <asm/iommu.h>
|
|
#include <asm/iommu.h>
|
|
|
|
|
|
#include "irq_remapping.h"
|
|
#include "irq_remapping.h"
|
|
-#include "pci.h"
|
|
|
|
|
|
|
|
#define ROOT_SIZE VTD_PAGE_SIZE
|
|
#define ROOT_SIZE VTD_PAGE_SIZE
|
|
#define CONTEXT_SIZE VTD_PAGE_SIZE
|
|
#define CONTEXT_SIZE VTD_PAGE_SIZE
|
|
@@ -304,7 +303,7 @@ static inline bool dma_pte_present(struct dma_pte *pte)
|
|
|
|
|
|
static inline bool dma_pte_superpage(struct dma_pte *pte)
|
|
static inline bool dma_pte_superpage(struct dma_pte *pte)
|
|
{
|
|
{
|
|
- return (pte->val & (1 << 7));
|
|
|
|
|
|
+ return (pte->val & DMA_PTE_LARGE_PAGE);
|
|
}
|
|
}
|
|
|
|
|
|
static inline int first_pte_in_page(struct dma_pte *pte)
|
|
static inline int first_pte_in_page(struct dma_pte *pte)
|
|
@@ -321,16 +320,13 @@ static inline int first_pte_in_page(struct dma_pte *pte)
|
|
static struct dmar_domain *si_domain;
|
|
static struct dmar_domain *si_domain;
|
|
static int hw_pass_through = 1;
|
|
static int hw_pass_through = 1;
|
|
|
|
|
|
-/* devices under the same p2p bridge are owned in one domain */
|
|
|
|
-#define DOMAIN_FLAG_P2P_MULTIPLE_DEVICES (1 << 0)
|
|
|
|
-
|
|
|
|
/* domain represents a virtual machine, more than one devices
|
|
/* domain represents a virtual machine, more than one devices
|
|
* across iommus may be owned in one domain, e.g. kvm guest.
|
|
* across iommus may be owned in one domain, e.g. kvm guest.
|
|
*/
|
|
*/
|
|
-#define DOMAIN_FLAG_VIRTUAL_MACHINE (1 << 1)
|
|
|
|
|
|
+#define DOMAIN_FLAG_VIRTUAL_MACHINE (1 << 0)
|
|
|
|
|
|
/* si_domain contains mulitple devices */
|
|
/* si_domain contains mulitple devices */
|
|
-#define DOMAIN_FLAG_STATIC_IDENTITY (1 << 2)
|
|
|
|
|
|
+#define DOMAIN_FLAG_STATIC_IDENTITY (1 << 1)
|
|
|
|
|
|
/* define the limit of IOMMUs supported in each domain */
|
|
/* define the limit of IOMMUs supported in each domain */
|
|
#ifdef CONFIG_X86
|
|
#ifdef CONFIG_X86
|
|
@@ -429,6 +425,8 @@ static void domain_remove_one_dev_info(struct dmar_domain *domain,
|
|
struct device *dev);
|
|
struct device *dev);
|
|
static void iommu_detach_dependent_devices(struct intel_iommu *iommu,
|
|
static void iommu_detach_dependent_devices(struct intel_iommu *iommu,
|
|
struct device *dev);
|
|
struct device *dev);
|
|
|
|
+static int domain_detach_iommu(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu);
|
|
|
|
|
|
#ifdef CONFIG_INTEL_IOMMU_DEFAULT_ON
|
|
#ifdef CONFIG_INTEL_IOMMU_DEFAULT_ON
|
|
int dmar_disabled = 0;
|
|
int dmar_disabled = 0;
|
|
@@ -451,7 +449,7 @@ EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped);
|
|
static DEFINE_SPINLOCK(device_domain_lock);
|
|
static DEFINE_SPINLOCK(device_domain_lock);
|
|
static LIST_HEAD(device_domain_list);
|
|
static LIST_HEAD(device_domain_list);
|
|
|
|
|
|
-static struct iommu_ops intel_iommu_ops;
|
|
|
|
|
|
+static const struct iommu_ops intel_iommu_ops;
|
|
|
|
|
|
static int __init intel_iommu_setup(char *str)
|
|
static int __init intel_iommu_setup(char *str)
|
|
{
|
|
{
|
|
@@ -540,6 +538,24 @@ void free_iova_mem(struct iova *iova)
|
|
kmem_cache_free(iommu_iova_cache, iova);
|
|
kmem_cache_free(iommu_iova_cache, iova);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static inline int domain_type_is_vm(struct dmar_domain *domain)
|
|
|
|
+{
|
|
|
|
+ return domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline int domain_type_is_vm_or_si(struct dmar_domain *domain)
|
|
|
|
+{
|
|
|
|
+ return domain->flags & (DOMAIN_FLAG_VIRTUAL_MACHINE |
|
|
|
|
+ DOMAIN_FLAG_STATIC_IDENTITY);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static inline int domain_pfn_supported(struct dmar_domain *domain,
|
|
|
|
+ unsigned long pfn)
|
|
|
|
+{
|
|
|
|
+ int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
+
|
|
|
|
+ return !(addr_width < BITS_PER_LONG && pfn >> addr_width);
|
|
|
|
+}
|
|
|
|
|
|
static int __iommu_calculate_agaw(struct intel_iommu *iommu, int max_gaw)
|
|
static int __iommu_calculate_agaw(struct intel_iommu *iommu, int max_gaw)
|
|
{
|
|
{
|
|
@@ -580,9 +596,7 @@ static struct intel_iommu *domain_get_iommu(struct dmar_domain *domain)
|
|
int iommu_id;
|
|
int iommu_id;
|
|
|
|
|
|
/* si_domain and vm domain should not get here. */
|
|
/* si_domain and vm domain should not get here. */
|
|
- BUG_ON(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE);
|
|
|
|
- BUG_ON(domain->flags & DOMAIN_FLAG_STATIC_IDENTITY);
|
|
|
|
-
|
|
|
|
|
|
+ BUG_ON(domain_type_is_vm_or_si(domain));
|
|
iommu_id = find_first_bit(domain->iommu_bmp, g_num_of_iommus);
|
|
iommu_id = find_first_bit(domain->iommu_bmp, g_num_of_iommus);
|
|
if (iommu_id < 0 || iommu_id >= g_num_of_iommus)
|
|
if (iommu_id < 0 || iommu_id >= g_num_of_iommus)
|
|
return NULL;
|
|
return NULL;
|
|
@@ -619,50 +633,56 @@ static void domain_update_iommu_coherency(struct dmar_domain *domain)
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
}
|
|
}
|
|
|
|
|
|
-static void domain_update_iommu_snooping(struct dmar_domain *domain)
|
|
|
|
|
|
+static int domain_update_iommu_snooping(struct intel_iommu *skip)
|
|
{
|
|
{
|
|
- int i;
|
|
|
|
-
|
|
|
|
- domain->iommu_snooping = 1;
|
|
|
|
|
|
+ struct dmar_drhd_unit *drhd;
|
|
|
|
+ struct intel_iommu *iommu;
|
|
|
|
+ int ret = 1;
|
|
|
|
|
|
- for_each_set_bit(i, domain->iommu_bmp, g_num_of_iommus) {
|
|
|
|
- if (!ecap_sc_support(g_iommus[i]->ecap)) {
|
|
|
|
- domain->iommu_snooping = 0;
|
|
|
|
- break;
|
|
|
|
|
|
+ rcu_read_lock();
|
|
|
|
+ for_each_active_iommu(iommu, drhd) {
|
|
|
|
+ if (iommu != skip) {
|
|
|
|
+ if (!ecap_sc_support(iommu->ecap)) {
|
|
|
|
+ ret = 0;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ rcu_read_unlock();
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
-static void domain_update_iommu_superpage(struct dmar_domain *domain)
|
|
|
|
|
|
+static int domain_update_iommu_superpage(struct intel_iommu *skip)
|
|
{
|
|
{
|
|
struct dmar_drhd_unit *drhd;
|
|
struct dmar_drhd_unit *drhd;
|
|
- struct intel_iommu *iommu = NULL;
|
|
|
|
|
|
+ struct intel_iommu *iommu;
|
|
int mask = 0xf;
|
|
int mask = 0xf;
|
|
|
|
|
|
if (!intel_iommu_superpage) {
|
|
if (!intel_iommu_superpage) {
|
|
- domain->iommu_superpage = 0;
|
|
|
|
- return;
|
|
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* set iommu_superpage to the smallest common denominator */
|
|
/* set iommu_superpage to the smallest common denominator */
|
|
rcu_read_lock();
|
|
rcu_read_lock();
|
|
for_each_active_iommu(iommu, drhd) {
|
|
for_each_active_iommu(iommu, drhd) {
|
|
- mask &= cap_super_page_val(iommu->cap);
|
|
|
|
- if (!mask) {
|
|
|
|
- break;
|
|
|
|
|
|
+ if (iommu != skip) {
|
|
|
|
+ mask &= cap_super_page_val(iommu->cap);
|
|
|
|
+ if (!mask)
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
|
|
|
|
- domain->iommu_superpage = fls(mask);
|
|
|
|
|
|
+ return fls(mask);
|
|
}
|
|
}
|
|
|
|
|
|
/* Some capabilities may be different across iommus */
|
|
/* Some capabilities may be different across iommus */
|
|
static void domain_update_iommu_cap(struct dmar_domain *domain)
|
|
static void domain_update_iommu_cap(struct dmar_domain *domain)
|
|
{
|
|
{
|
|
domain_update_iommu_coherency(domain);
|
|
domain_update_iommu_coherency(domain);
|
|
- domain_update_iommu_snooping(domain);
|
|
|
|
- domain_update_iommu_superpage(domain);
|
|
|
|
|
|
+ domain->iommu_snooping = domain_update_iommu_snooping(NULL);
|
|
|
|
+ domain->iommu_superpage = domain_update_iommu_superpage(NULL);
|
|
}
|
|
}
|
|
|
|
|
|
static struct intel_iommu *device_to_iommu(struct device *dev, u8 *bus, u8 *devfn)
|
|
static struct intel_iommu *device_to_iommu(struct device *dev, u8 *bus, u8 *devfn)
|
|
@@ -671,7 +691,7 @@ static struct intel_iommu *device_to_iommu(struct device *dev, u8 *bus, u8 *devf
|
|
struct intel_iommu *iommu;
|
|
struct intel_iommu *iommu;
|
|
struct device *tmp;
|
|
struct device *tmp;
|
|
struct pci_dev *ptmp, *pdev = NULL;
|
|
struct pci_dev *ptmp, *pdev = NULL;
|
|
- u16 segment;
|
|
|
|
|
|
+ u16 segment = 0;
|
|
int i;
|
|
int i;
|
|
|
|
|
|
if (dev_is_pci(dev)) {
|
|
if (dev_is_pci(dev)) {
|
|
@@ -816,14 +836,13 @@ out:
|
|
static struct dma_pte *pfn_to_dma_pte(struct dmar_domain *domain,
|
|
static struct dma_pte *pfn_to_dma_pte(struct dmar_domain *domain,
|
|
unsigned long pfn, int *target_level)
|
|
unsigned long pfn, int *target_level)
|
|
{
|
|
{
|
|
- int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
struct dma_pte *parent, *pte = NULL;
|
|
struct dma_pte *parent, *pte = NULL;
|
|
int level = agaw_to_level(domain->agaw);
|
|
int level = agaw_to_level(domain->agaw);
|
|
int offset;
|
|
int offset;
|
|
|
|
|
|
BUG_ON(!domain->pgd);
|
|
BUG_ON(!domain->pgd);
|
|
|
|
|
|
- if (addr_width < BITS_PER_LONG && pfn >> addr_width)
|
|
|
|
|
|
+ if (!domain_pfn_supported(domain, pfn))
|
|
/* Address beyond IOMMU's addressing capabilities. */
|
|
/* Address beyond IOMMU's addressing capabilities. */
|
|
return NULL;
|
|
return NULL;
|
|
|
|
|
|
@@ -849,13 +868,11 @@ static struct dma_pte *pfn_to_dma_pte(struct dmar_domain *domain,
|
|
|
|
|
|
domain_flush_cache(domain, tmp_page, VTD_PAGE_SIZE);
|
|
domain_flush_cache(domain, tmp_page, VTD_PAGE_SIZE);
|
|
pteval = ((uint64_t)virt_to_dma_pfn(tmp_page) << VTD_PAGE_SHIFT) | DMA_PTE_READ | DMA_PTE_WRITE;
|
|
pteval = ((uint64_t)virt_to_dma_pfn(tmp_page) << VTD_PAGE_SHIFT) | DMA_PTE_READ | DMA_PTE_WRITE;
|
|
- if (cmpxchg64(&pte->val, 0ULL, pteval)) {
|
|
|
|
|
|
+ if (cmpxchg64(&pte->val, 0ULL, pteval))
|
|
/* Someone else set it while we were thinking; use theirs. */
|
|
/* Someone else set it while we were thinking; use theirs. */
|
|
free_pgtable_page(tmp_page);
|
|
free_pgtable_page(tmp_page);
|
|
- } else {
|
|
|
|
- dma_pte_addr(pte);
|
|
|
|
|
|
+ else
|
|
domain_flush_cache(domain, pte, sizeof(*pte));
|
|
domain_flush_cache(domain, pte, sizeof(*pte));
|
|
- }
|
|
|
|
}
|
|
}
|
|
if (level == 1)
|
|
if (level == 1)
|
|
break;
|
|
break;
|
|
@@ -892,7 +909,7 @@ static struct dma_pte *dma_pfn_level_pte(struct dmar_domain *domain,
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
- if (pte->val & DMA_PTE_LARGE_PAGE) {
|
|
|
|
|
|
+ if (dma_pte_superpage(pte)) {
|
|
*large_page = total;
|
|
*large_page = total;
|
|
return pte;
|
|
return pte;
|
|
}
|
|
}
|
|
@@ -908,12 +925,11 @@ static void dma_pte_clear_range(struct dmar_domain *domain,
|
|
unsigned long start_pfn,
|
|
unsigned long start_pfn,
|
|
unsigned long last_pfn)
|
|
unsigned long last_pfn)
|
|
{
|
|
{
|
|
- int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
unsigned int large_page = 1;
|
|
unsigned int large_page = 1;
|
|
struct dma_pte *first_pte, *pte;
|
|
struct dma_pte *first_pte, *pte;
|
|
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width);
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width);
|
|
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, start_pfn));
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, last_pfn));
|
|
BUG_ON(start_pfn > last_pfn);
|
|
BUG_ON(start_pfn > last_pfn);
|
|
|
|
|
|
/* we don't need lock here; nobody else touches the iova range */
|
|
/* we don't need lock here; nobody else touches the iova range */
|
|
@@ -974,12 +990,12 @@ static void dma_pte_free_pagetable(struct dmar_domain *domain,
|
|
unsigned long start_pfn,
|
|
unsigned long start_pfn,
|
|
unsigned long last_pfn)
|
|
unsigned long last_pfn)
|
|
{
|
|
{
|
|
- int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
-
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width);
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width);
|
|
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, start_pfn));
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, last_pfn));
|
|
BUG_ON(start_pfn > last_pfn);
|
|
BUG_ON(start_pfn > last_pfn);
|
|
|
|
|
|
|
|
+ dma_pte_clear_range(domain, start_pfn, last_pfn);
|
|
|
|
+
|
|
/* We don't need lock here; nobody else touches the iova range */
|
|
/* We don't need lock here; nobody else touches the iova range */
|
|
dma_pte_free_level(domain, agaw_to_level(domain->agaw),
|
|
dma_pte_free_level(domain, agaw_to_level(domain->agaw),
|
|
domain->pgd, 0, start_pfn, last_pfn);
|
|
domain->pgd, 0, start_pfn, last_pfn);
|
|
@@ -1077,11 +1093,10 @@ struct page *domain_unmap(struct dmar_domain *domain,
|
|
unsigned long start_pfn,
|
|
unsigned long start_pfn,
|
|
unsigned long last_pfn)
|
|
unsigned long last_pfn)
|
|
{
|
|
{
|
|
- int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
struct page *freelist = NULL;
|
|
struct page *freelist = NULL;
|
|
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && start_pfn >> addr_width);
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && last_pfn >> addr_width);
|
|
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, start_pfn));
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, last_pfn));
|
|
BUG_ON(start_pfn > last_pfn);
|
|
BUG_ON(start_pfn > last_pfn);
|
|
|
|
|
|
/* we don't need lock here; nobody else touches the iova range */
|
|
/* we don't need lock here; nobody else touches the iova range */
|
|
@@ -1275,7 +1290,8 @@ iommu_support_dev_iotlb (struct dmar_domain *domain, struct intel_iommu *iommu,
|
|
|
|
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
list_for_each_entry(info, &domain->devices, link)
|
|
list_for_each_entry(info, &domain->devices, link)
|
|
- if (info->bus == bus && info->devfn == devfn) {
|
|
|
|
|
|
+ if (info->iommu == iommu && info->bus == bus &&
|
|
|
|
+ info->devfn == devfn) {
|
|
found = 1;
|
|
found = 1;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
@@ -1384,7 +1400,7 @@ static void iommu_disable_protect_mem_regions(struct intel_iommu *iommu)
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
-static int iommu_enable_translation(struct intel_iommu *iommu)
|
|
|
|
|
|
+static void iommu_enable_translation(struct intel_iommu *iommu)
|
|
{
|
|
{
|
|
u32 sts;
|
|
u32 sts;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
@@ -1398,10 +1414,9 @@ static int iommu_enable_translation(struct intel_iommu *iommu)
|
|
readl, (sts & DMA_GSTS_TES), sts);
|
|
readl, (sts & DMA_GSTS_TES), sts);
|
|
|
|
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
|
|
- return 0;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-static int iommu_disable_translation(struct intel_iommu *iommu)
|
|
|
|
|
|
+static void iommu_disable_translation(struct intel_iommu *iommu)
|
|
{
|
|
{
|
|
u32 sts;
|
|
u32 sts;
|
|
unsigned long flag;
|
|
unsigned long flag;
|
|
@@ -1415,7 +1430,6 @@ static int iommu_disable_translation(struct intel_iommu *iommu)
|
|
readl, (!(sts & DMA_GSTS_TES)), sts);
|
|
readl, (!(sts & DMA_GSTS_TES)), sts);
|
|
|
|
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flag);
|
|
raw_spin_unlock_irqrestore(&iommu->register_lock, flag);
|
|
- return 0;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1462,8 +1476,7 @@ static int iommu_init_domains(struct intel_iommu *iommu)
|
|
static void free_dmar_iommu(struct intel_iommu *iommu)
|
|
static void free_dmar_iommu(struct intel_iommu *iommu)
|
|
{
|
|
{
|
|
struct dmar_domain *domain;
|
|
struct dmar_domain *domain;
|
|
- int i, count;
|
|
|
|
- unsigned long flags;
|
|
|
|
|
|
+ int i;
|
|
|
|
|
|
if ((iommu->domains) && (iommu->domain_ids)) {
|
|
if ((iommu->domains) && (iommu->domain_ids)) {
|
|
for_each_set_bit(i, iommu->domain_ids, cap_ndoms(iommu->cap)) {
|
|
for_each_set_bit(i, iommu->domain_ids, cap_ndoms(iommu->cap)) {
|
|
@@ -1476,11 +1489,8 @@ static void free_dmar_iommu(struct intel_iommu *iommu)
|
|
|
|
|
|
domain = iommu->domains[i];
|
|
domain = iommu->domains[i];
|
|
clear_bit(i, iommu->domain_ids);
|
|
clear_bit(i, iommu->domain_ids);
|
|
-
|
|
|
|
- spin_lock_irqsave(&domain->iommu_lock, flags);
|
|
|
|
- count = --domain->iommu_count;
|
|
|
|
- spin_unlock_irqrestore(&domain->iommu_lock, flags);
|
|
|
|
- if (count == 0)
|
|
|
|
|
|
+ if (domain_detach_iommu(domain, iommu) == 0 &&
|
|
|
|
+ !domain_type_is_vm(domain))
|
|
domain_exit(domain);
|
|
domain_exit(domain);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1499,7 +1509,7 @@ static void free_dmar_iommu(struct intel_iommu *iommu)
|
|
free_context_table(iommu);
|
|
free_context_table(iommu);
|
|
}
|
|
}
|
|
|
|
|
|
-static struct dmar_domain *alloc_domain(bool vm)
|
|
|
|
|
|
+static struct dmar_domain *alloc_domain(int flags)
|
|
{
|
|
{
|
|
/* domain id for virtual machine, it won't be set in context */
|
|
/* domain id for virtual machine, it won't be set in context */
|
|
static atomic_t vm_domid = ATOMIC_INIT(0);
|
|
static atomic_t vm_domid = ATOMIC_INIT(0);
|
|
@@ -1509,46 +1519,62 @@ static struct dmar_domain *alloc_domain(bool vm)
|
|
if (!domain)
|
|
if (!domain)
|
|
return NULL;
|
|
return NULL;
|
|
|
|
|
|
|
|
+ memset(domain, 0, sizeof(*domain));
|
|
domain->nid = -1;
|
|
domain->nid = -1;
|
|
- domain->iommu_count = 0;
|
|
|
|
- memset(domain->iommu_bmp, 0, sizeof(domain->iommu_bmp));
|
|
|
|
- domain->flags = 0;
|
|
|
|
|
|
+ domain->flags = flags;
|
|
spin_lock_init(&domain->iommu_lock);
|
|
spin_lock_init(&domain->iommu_lock);
|
|
INIT_LIST_HEAD(&domain->devices);
|
|
INIT_LIST_HEAD(&domain->devices);
|
|
- if (vm) {
|
|
|
|
|
|
+ if (flags & DOMAIN_FLAG_VIRTUAL_MACHINE)
|
|
domain->id = atomic_inc_return(&vm_domid);
|
|
domain->id = atomic_inc_return(&vm_domid);
|
|
- domain->flags = DOMAIN_FLAG_VIRTUAL_MACHINE;
|
|
|
|
- }
|
|
|
|
|
|
|
|
return domain;
|
|
return domain;
|
|
}
|
|
}
|
|
|
|
|
|
-static int iommu_attach_domain(struct dmar_domain *domain,
|
|
|
|
- struct intel_iommu *iommu)
|
|
|
|
|
|
+static int __iommu_attach_domain(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu)
|
|
{
|
|
{
|
|
int num;
|
|
int num;
|
|
unsigned long ndomains;
|
|
unsigned long ndomains;
|
|
- unsigned long flags;
|
|
|
|
|
|
|
|
ndomains = cap_ndoms(iommu->cap);
|
|
ndomains = cap_ndoms(iommu->cap);
|
|
-
|
|
|
|
- spin_lock_irqsave(&iommu->lock, flags);
|
|
|
|
-
|
|
|
|
num = find_first_zero_bit(iommu->domain_ids, ndomains);
|
|
num = find_first_zero_bit(iommu->domain_ids, ndomains);
|
|
- if (num >= ndomains) {
|
|
|
|
- spin_unlock_irqrestore(&iommu->lock, flags);
|
|
|
|
- printk(KERN_ERR "IOMMU: no free domain ids\n");
|
|
|
|
- return -ENOMEM;
|
|
|
|
|
|
+ if (num < ndomains) {
|
|
|
|
+ set_bit(num, iommu->domain_ids);
|
|
|
|
+ iommu->domains[num] = domain;
|
|
|
|
+ } else {
|
|
|
|
+ num = -ENOSPC;
|
|
}
|
|
}
|
|
|
|
|
|
- domain->id = num;
|
|
|
|
- domain->iommu_count++;
|
|
|
|
- set_bit(num, iommu->domain_ids);
|
|
|
|
- set_bit(iommu->seq_id, domain->iommu_bmp);
|
|
|
|
- iommu->domains[num] = domain;
|
|
|
|
|
|
+ return num;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int iommu_attach_domain(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu)
|
|
|
|
+{
|
|
|
|
+ int num;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&iommu->lock, flags);
|
|
|
|
+ num = __iommu_attach_domain(domain, iommu);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
|
|
+ if (num < 0)
|
|
|
|
+ pr_err("IOMMU: no free domain ids\n");
|
|
|
|
|
|
- return 0;
|
|
|
|
|
|
+ return num;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int iommu_attach_vm_domain(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu)
|
|
|
|
+{
|
|
|
|
+ int num;
|
|
|
|
+ unsigned long ndomains;
|
|
|
|
+
|
|
|
|
+ ndomains = cap_ndoms(iommu->cap);
|
|
|
|
+ for_each_set_bit(num, iommu->domain_ids, ndomains)
|
|
|
|
+ if (iommu->domains[num] == domain)
|
|
|
|
+ return num;
|
|
|
|
+
|
|
|
|
+ return __iommu_attach_domain(domain, iommu);
|
|
}
|
|
}
|
|
|
|
|
|
static void iommu_detach_domain(struct dmar_domain *domain,
|
|
static void iommu_detach_domain(struct dmar_domain *domain,
|
|
@@ -1558,17 +1584,53 @@ static void iommu_detach_domain(struct dmar_domain *domain,
|
|
int num, ndomains;
|
|
int num, ndomains;
|
|
|
|
|
|
spin_lock_irqsave(&iommu->lock, flags);
|
|
spin_lock_irqsave(&iommu->lock, flags);
|
|
- ndomains = cap_ndoms(iommu->cap);
|
|
|
|
- for_each_set_bit(num, iommu->domain_ids, ndomains) {
|
|
|
|
- if (iommu->domains[num] == domain) {
|
|
|
|
- clear_bit(num, iommu->domain_ids);
|
|
|
|
- iommu->domains[num] = NULL;
|
|
|
|
- break;
|
|
|
|
|
|
+ if (domain_type_is_vm_or_si(domain)) {
|
|
|
|
+ ndomains = cap_ndoms(iommu->cap);
|
|
|
|
+ for_each_set_bit(num, iommu->domain_ids, ndomains) {
|
|
|
|
+ if (iommu->domains[num] == domain) {
|
|
|
|
+ clear_bit(num, iommu->domain_ids);
|
|
|
|
+ iommu->domains[num] = NULL;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
+ } else {
|
|
|
|
+ clear_bit(domain->id, iommu->domain_ids);
|
|
|
|
+ iommu->domains[domain->id] = NULL;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void domain_attach_iommu(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu)
|
|
|
|
+{
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&domain->iommu_lock, flags);
|
|
|
|
+ if (!test_and_set_bit(iommu->seq_id, domain->iommu_bmp)) {
|
|
|
|
+ domain->iommu_count++;
|
|
|
|
+ if (domain->iommu_count == 1)
|
|
|
|
+ domain->nid = iommu->node;
|
|
|
|
+ domain_update_iommu_cap(domain);
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_irqrestore(&domain->iommu_lock, flags);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int domain_detach_iommu(struct dmar_domain *domain,
|
|
|
|
+ struct intel_iommu *iommu)
|
|
|
|
+{
|
|
|
|
+ unsigned long flags;
|
|
|
|
+ int count = INT_MAX;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&domain->iommu_lock, flags);
|
|
|
|
+ if (test_and_clear_bit(iommu->seq_id, domain->iommu_bmp)) {
|
|
|
|
+ count = --domain->iommu_count;
|
|
|
|
+ domain_update_iommu_cap(domain);
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_irqrestore(&domain->iommu_lock, flags);
|
|
|
|
+
|
|
|
|
+ return count;
|
|
|
|
+}
|
|
|
|
+
|
|
static struct iova_domain reserved_iova_list;
|
|
static struct iova_domain reserved_iova_list;
|
|
static struct lock_class_key reserved_rbtree_key;
|
|
static struct lock_class_key reserved_rbtree_key;
|
|
|
|
|
|
@@ -1706,9 +1768,7 @@ static void domain_exit(struct dmar_domain *domain)
|
|
/* clear attached or cached domains */
|
|
/* clear attached or cached domains */
|
|
rcu_read_lock();
|
|
rcu_read_lock();
|
|
for_each_active_iommu(iommu, drhd)
|
|
for_each_active_iommu(iommu, drhd)
|
|
- if (domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE ||
|
|
|
|
- test_bit(iommu->seq_id, domain->iommu_bmp))
|
|
|
|
- iommu_detach_domain(domain, iommu);
|
|
|
|
|
|
+ iommu_detach_domain(domain, iommu);
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
|
|
|
|
dma_free_pagelist(freelist);
|
|
dma_free_pagelist(freelist);
|
|
@@ -1723,8 +1783,6 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
|
|
struct context_entry *context;
|
|
struct context_entry *context;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
struct dma_pte *pgd;
|
|
struct dma_pte *pgd;
|
|
- unsigned long num;
|
|
|
|
- unsigned long ndomains;
|
|
|
|
int id;
|
|
int id;
|
|
int agaw;
|
|
int agaw;
|
|
struct device_domain_info *info = NULL;
|
|
struct device_domain_info *info = NULL;
|
|
@@ -1748,31 +1806,14 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
|
|
id = domain->id;
|
|
id = domain->id;
|
|
pgd = domain->pgd;
|
|
pgd = domain->pgd;
|
|
|
|
|
|
- if (domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE ||
|
|
|
|
- domain->flags & DOMAIN_FLAG_STATIC_IDENTITY) {
|
|
|
|
- int found = 0;
|
|
|
|
-
|
|
|
|
- /* find an available domain id for this device in iommu */
|
|
|
|
- ndomains = cap_ndoms(iommu->cap);
|
|
|
|
- for_each_set_bit(num, iommu->domain_ids, ndomains) {
|
|
|
|
- if (iommu->domains[num] == domain) {
|
|
|
|
- id = num;
|
|
|
|
- found = 1;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (found == 0) {
|
|
|
|
- num = find_first_zero_bit(iommu->domain_ids, ndomains);
|
|
|
|
- if (num >= ndomains) {
|
|
|
|
|
|
+ if (domain_type_is_vm_or_si(domain)) {
|
|
|
|
+ if (domain_type_is_vm(domain)) {
|
|
|
|
+ id = iommu_attach_vm_domain(domain, iommu);
|
|
|
|
+ if (id < 0) {
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
- printk(KERN_ERR "IOMMU: no free domain ids\n");
|
|
|
|
|
|
+ pr_err("IOMMU: no free domain ids\n");
|
|
return -EFAULT;
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
-
|
|
|
|
- set_bit(num, iommu->domain_ids);
|
|
|
|
- iommu->domains[num] = domain;
|
|
|
|
- id = num;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/* Skip top levels of page tables for
|
|
/* Skip top levels of page tables for
|
|
@@ -1824,72 +1865,68 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
|
|
(((u16)bus) << 8) | devfn,
|
|
(((u16)bus) << 8) | devfn,
|
|
DMA_CCMD_MASK_NOBIT,
|
|
DMA_CCMD_MASK_NOBIT,
|
|
DMA_CCMD_DEVICE_INVL);
|
|
DMA_CCMD_DEVICE_INVL);
|
|
- iommu->flush.flush_iotlb(iommu, domain->id, 0, 0, DMA_TLB_DSI_FLUSH);
|
|
|
|
|
|
+ iommu->flush.flush_iotlb(iommu, id, 0, 0, DMA_TLB_DSI_FLUSH);
|
|
} else {
|
|
} else {
|
|
iommu_flush_write_buffer(iommu);
|
|
iommu_flush_write_buffer(iommu);
|
|
}
|
|
}
|
|
iommu_enable_dev_iotlb(info);
|
|
iommu_enable_dev_iotlb(info);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
spin_unlock_irqrestore(&iommu->lock, flags);
|
|
|
|
|
|
- spin_lock_irqsave(&domain->iommu_lock, flags);
|
|
|
|
- if (!test_and_set_bit(iommu->seq_id, domain->iommu_bmp)) {
|
|
|
|
- domain->iommu_count++;
|
|
|
|
- if (domain->iommu_count == 1)
|
|
|
|
- domain->nid = iommu->node;
|
|
|
|
- domain_update_iommu_cap(domain);
|
|
|
|
- }
|
|
|
|
- spin_unlock_irqrestore(&domain->iommu_lock, flags);
|
|
|
|
|
|
+ domain_attach_iommu(domain, iommu);
|
|
|
|
+
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+struct domain_context_mapping_data {
|
|
|
|
+ struct dmar_domain *domain;
|
|
|
|
+ struct intel_iommu *iommu;
|
|
|
|
+ int translation;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int domain_context_mapping_cb(struct pci_dev *pdev,
|
|
|
|
+ u16 alias, void *opaque)
|
|
|
|
+{
|
|
|
|
+ struct domain_context_mapping_data *data = opaque;
|
|
|
|
+
|
|
|
|
+ return domain_context_mapping_one(data->domain, data->iommu,
|
|
|
|
+ PCI_BUS_NUM(alias), alias & 0xff,
|
|
|
|
+ data->translation);
|
|
|
|
+}
|
|
|
|
+
|
|
static int
|
|
static int
|
|
domain_context_mapping(struct dmar_domain *domain, struct device *dev,
|
|
domain_context_mapping(struct dmar_domain *domain, struct device *dev,
|
|
int translation)
|
|
int translation)
|
|
{
|
|
{
|
|
- int ret;
|
|
|
|
- struct pci_dev *pdev, *tmp, *parent;
|
|
|
|
struct intel_iommu *iommu;
|
|
struct intel_iommu *iommu;
|
|
u8 bus, devfn;
|
|
u8 bus, devfn;
|
|
|
|
+ struct domain_context_mapping_data data;
|
|
|
|
|
|
iommu = device_to_iommu(dev, &bus, &devfn);
|
|
iommu = device_to_iommu(dev, &bus, &devfn);
|
|
if (!iommu)
|
|
if (!iommu)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
|
|
- ret = domain_context_mapping_one(domain, iommu, bus, devfn,
|
|
|
|
- translation);
|
|
|
|
- if (ret || !dev_is_pci(dev))
|
|
|
|
- return ret;
|
|
|
|
-
|
|
|
|
- /* dependent device mapping */
|
|
|
|
- pdev = to_pci_dev(dev);
|
|
|
|
- tmp = pci_find_upstream_pcie_bridge(pdev);
|
|
|
|
- if (!tmp)
|
|
|
|
- return 0;
|
|
|
|
- /* Secondary interface's bus number and devfn 0 */
|
|
|
|
- parent = pdev->bus->self;
|
|
|
|
- while (parent != tmp) {
|
|
|
|
- ret = domain_context_mapping_one(domain, iommu,
|
|
|
|
- parent->bus->number,
|
|
|
|
- parent->devfn, translation);
|
|
|
|
- if (ret)
|
|
|
|
- return ret;
|
|
|
|
- parent = parent->bus->self;
|
|
|
|
- }
|
|
|
|
- if (pci_is_pcie(tmp)) /* this is a PCIe-to-PCI bridge */
|
|
|
|
- return domain_context_mapping_one(domain, iommu,
|
|
|
|
- tmp->subordinate->number, 0,
|
|
|
|
- translation);
|
|
|
|
- else /* this is a legacy PCI bridge */
|
|
|
|
- return domain_context_mapping_one(domain, iommu,
|
|
|
|
- tmp->bus->number,
|
|
|
|
- tmp->devfn,
|
|
|
|
|
|
+ if (!dev_is_pci(dev))
|
|
|
|
+ return domain_context_mapping_one(domain, iommu, bus, devfn,
|
|
translation);
|
|
translation);
|
|
|
|
+
|
|
|
|
+ data.domain = domain;
|
|
|
|
+ data.iommu = iommu;
|
|
|
|
+ data.translation = translation;
|
|
|
|
+
|
|
|
|
+ return pci_for_each_dma_alias(to_pci_dev(dev),
|
|
|
|
+ &domain_context_mapping_cb, &data);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int domain_context_mapped_cb(struct pci_dev *pdev,
|
|
|
|
+ u16 alias, void *opaque)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = opaque;
|
|
|
|
+
|
|
|
|
+ return !device_context_mapped(iommu, PCI_BUS_NUM(alias), alias & 0xff);
|
|
}
|
|
}
|
|
|
|
|
|
static int domain_context_mapped(struct device *dev)
|
|
static int domain_context_mapped(struct device *dev)
|
|
{
|
|
{
|
|
- int ret;
|
|
|
|
- struct pci_dev *pdev, *tmp, *parent;
|
|
|
|
struct intel_iommu *iommu;
|
|
struct intel_iommu *iommu;
|
|
u8 bus, devfn;
|
|
u8 bus, devfn;
|
|
|
|
|
|
@@ -1897,30 +1934,11 @@ static int domain_context_mapped(struct device *dev)
|
|
if (!iommu)
|
|
if (!iommu)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
|
|
- ret = device_context_mapped(iommu, bus, devfn);
|
|
|
|
- if (!ret || !dev_is_pci(dev))
|
|
|
|
- return ret;
|
|
|
|
|
|
+ if (!dev_is_pci(dev))
|
|
|
|
+ return device_context_mapped(iommu, bus, devfn);
|
|
|
|
|
|
- /* dependent device mapping */
|
|
|
|
- pdev = to_pci_dev(dev);
|
|
|
|
- tmp = pci_find_upstream_pcie_bridge(pdev);
|
|
|
|
- if (!tmp)
|
|
|
|
- return ret;
|
|
|
|
- /* Secondary interface's bus number and devfn 0 */
|
|
|
|
- parent = pdev->bus->self;
|
|
|
|
- while (parent != tmp) {
|
|
|
|
- ret = device_context_mapped(iommu, parent->bus->number,
|
|
|
|
- parent->devfn);
|
|
|
|
- if (!ret)
|
|
|
|
- return ret;
|
|
|
|
- parent = parent->bus->self;
|
|
|
|
- }
|
|
|
|
- if (pci_is_pcie(tmp))
|
|
|
|
- return device_context_mapped(iommu, tmp->subordinate->number,
|
|
|
|
- 0);
|
|
|
|
- else
|
|
|
|
- return device_context_mapped(iommu, tmp->bus->number,
|
|
|
|
- tmp->devfn);
|
|
|
|
|
|
+ return !pci_for_each_dma_alias(to_pci_dev(dev),
|
|
|
|
+ domain_context_mapped_cb, iommu);
|
|
}
|
|
}
|
|
|
|
|
|
/* Returns a number of VTD pages, but aligned to MM page size */
|
|
/* Returns a number of VTD pages, but aligned to MM page size */
|
|
@@ -1965,12 +1983,11 @@ static int __domain_mapping(struct dmar_domain *domain, unsigned long iov_pfn,
|
|
{
|
|
{
|
|
struct dma_pte *first_pte = NULL, *pte = NULL;
|
|
struct dma_pte *first_pte = NULL, *pte = NULL;
|
|
phys_addr_t uninitialized_var(pteval);
|
|
phys_addr_t uninitialized_var(pteval);
|
|
- int addr_width = agaw_to_width(domain->agaw) - VTD_PAGE_SHIFT;
|
|
|
|
unsigned long sg_res;
|
|
unsigned long sg_res;
|
|
unsigned int largepage_lvl = 0;
|
|
unsigned int largepage_lvl = 0;
|
|
unsigned long lvl_pages = 0;
|
|
unsigned long lvl_pages = 0;
|
|
|
|
|
|
- BUG_ON(addr_width < BITS_PER_LONG && (iov_pfn + nr_pages - 1) >> addr_width);
|
|
|
|
|
|
+ BUG_ON(!domain_pfn_supported(domain, iov_pfn + nr_pages - 1));
|
|
|
|
|
|
if ((prot & (DMA_PTE_READ|DMA_PTE_WRITE)) == 0)
|
|
if ((prot & (DMA_PTE_READ|DMA_PTE_WRITE)) == 0)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
@@ -2004,12 +2021,14 @@ static int __domain_mapping(struct dmar_domain *domain, unsigned long iov_pfn,
|
|
/* It is large page*/
|
|
/* It is large page*/
|
|
if (largepage_lvl > 1) {
|
|
if (largepage_lvl > 1) {
|
|
pteval |= DMA_PTE_LARGE_PAGE;
|
|
pteval |= DMA_PTE_LARGE_PAGE;
|
|
- /* Ensure that old small page tables are removed to make room
|
|
|
|
- for superpage, if they exist. */
|
|
|
|
- dma_pte_clear_range(domain, iov_pfn,
|
|
|
|
- iov_pfn + lvl_to_nr_pages(largepage_lvl) - 1);
|
|
|
|
|
|
+ lvl_pages = lvl_to_nr_pages(largepage_lvl);
|
|
|
|
+ /*
|
|
|
|
+ * Ensure that old small page tables are
|
|
|
|
+ * removed to make room for superpage,
|
|
|
|
+ * if they exist.
|
|
|
|
+ */
|
|
dma_pte_free_pagetable(domain, iov_pfn,
|
|
dma_pte_free_pagetable(domain, iov_pfn,
|
|
- iov_pfn + lvl_to_nr_pages(largepage_lvl) - 1);
|
|
|
|
|
|
+ iov_pfn + lvl_pages - 1);
|
|
} else {
|
|
} else {
|
|
pteval &= ~(uint64_t)DMA_PTE_LARGE_PAGE;
|
|
pteval &= ~(uint64_t)DMA_PTE_LARGE_PAGE;
|
|
}
|
|
}
|
|
@@ -2102,31 +2121,20 @@ static inline void unlink_domain_info(struct device_domain_info *info)
|
|
|
|
|
|
static void domain_remove_dev_info(struct dmar_domain *domain)
|
|
static void domain_remove_dev_info(struct dmar_domain *domain)
|
|
{
|
|
{
|
|
- struct device_domain_info *info;
|
|
|
|
- unsigned long flags, flags2;
|
|
|
|
|
|
+ struct device_domain_info *info, *tmp;
|
|
|
|
+ unsigned long flags;
|
|
|
|
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
- while (!list_empty(&domain->devices)) {
|
|
|
|
- info = list_entry(domain->devices.next,
|
|
|
|
- struct device_domain_info, link);
|
|
|
|
|
|
+ list_for_each_entry_safe(info, tmp, &domain->devices, link) {
|
|
unlink_domain_info(info);
|
|
unlink_domain_info(info);
|
|
spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
|
|
|
|
iommu_disable_dev_iotlb(info);
|
|
iommu_disable_dev_iotlb(info);
|
|
iommu_detach_dev(info->iommu, info->bus, info->devfn);
|
|
iommu_detach_dev(info->iommu, info->bus, info->devfn);
|
|
|
|
|
|
- if (domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE) {
|
|
|
|
|
|
+ if (domain_type_is_vm(domain)) {
|
|
iommu_detach_dependent_devices(info->iommu, info->dev);
|
|
iommu_detach_dependent_devices(info->iommu, info->dev);
|
|
- /* clear this iommu in iommu_bmp, update iommu count
|
|
|
|
- * and capabilities
|
|
|
|
- */
|
|
|
|
- spin_lock_irqsave(&domain->iommu_lock, flags2);
|
|
|
|
- if (test_and_clear_bit(info->iommu->seq_id,
|
|
|
|
- domain->iommu_bmp)) {
|
|
|
|
- domain->iommu_count--;
|
|
|
|
- domain_update_iommu_cap(domain);
|
|
|
|
- }
|
|
|
|
- spin_unlock_irqrestore(&domain->iommu_lock, flags2);
|
|
|
|
|
|
+ domain_detach_iommu(domain, info->iommu);
|
|
}
|
|
}
|
|
|
|
|
|
free_devinfo_mem(info);
|
|
free_devinfo_mem(info);
|
|
@@ -2181,8 +2189,6 @@ static struct dmar_domain *dmar_insert_dev_info(struct intel_iommu *iommu,
|
|
info->dev = dev;
|
|
info->dev = dev;
|
|
info->domain = domain;
|
|
info->domain = domain;
|
|
info->iommu = iommu;
|
|
info->iommu = iommu;
|
|
- if (!dev)
|
|
|
|
- domain->flags |= DOMAIN_FLAG_P2P_MULTIPLE_DEVICES;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
spin_lock_irqsave(&device_domain_lock, flags);
|
|
if (dev)
|
|
if (dev)
|
|
@@ -2209,79 +2215,86 @@ static struct dmar_domain *dmar_insert_dev_info(struct intel_iommu *iommu,
|
|
return domain;
|
|
return domain;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int get_last_alias(struct pci_dev *pdev, u16 alias, void *opaque)
|
|
|
|
+{
|
|
|
|
+ *(u16 *)opaque = alias;
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
/* domain is initialized */
|
|
/* domain is initialized */
|
|
static struct dmar_domain *get_domain_for_dev(struct device *dev, int gaw)
|
|
static struct dmar_domain *get_domain_for_dev(struct device *dev, int gaw)
|
|
{
|
|
{
|
|
- struct dmar_domain *domain, *free = NULL;
|
|
|
|
- struct intel_iommu *iommu = NULL;
|
|
|
|
|
|
+ struct dmar_domain *domain, *tmp;
|
|
|
|
+ struct intel_iommu *iommu;
|
|
struct device_domain_info *info;
|
|
struct device_domain_info *info;
|
|
- struct pci_dev *dev_tmp = NULL;
|
|
|
|
|
|
+ u16 dma_alias;
|
|
unsigned long flags;
|
|
unsigned long flags;
|
|
- u8 bus, devfn, bridge_bus, bridge_devfn;
|
|
|
|
|
|
+ u8 bus, devfn;
|
|
|
|
|
|
domain = find_domain(dev);
|
|
domain = find_domain(dev);
|
|
if (domain)
|
|
if (domain)
|
|
return domain;
|
|
return domain;
|
|
|
|
|
|
|
|
+ iommu = device_to_iommu(dev, &bus, &devfn);
|
|
|
|
+ if (!iommu)
|
|
|
|
+ return NULL;
|
|
|
|
+
|
|
if (dev_is_pci(dev)) {
|
|
if (dev_is_pci(dev)) {
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
- u16 segment;
|
|
|
|
|
|
|
|
- segment = pci_domain_nr(pdev->bus);
|
|
|
|
- dev_tmp = pci_find_upstream_pcie_bridge(pdev);
|
|
|
|
- if (dev_tmp) {
|
|
|
|
- if (pci_is_pcie(dev_tmp)) {
|
|
|
|
- bridge_bus = dev_tmp->subordinate->number;
|
|
|
|
- bridge_devfn = 0;
|
|
|
|
- } else {
|
|
|
|
- bridge_bus = dev_tmp->bus->number;
|
|
|
|
- bridge_devfn = dev_tmp->devfn;
|
|
|
|
- }
|
|
|
|
- spin_lock_irqsave(&device_domain_lock, flags);
|
|
|
|
- info = dmar_search_domain_by_dev_info(segment,
|
|
|
|
- bridge_bus,
|
|
|
|
- bridge_devfn);
|
|
|
|
- if (info) {
|
|
|
|
- iommu = info->iommu;
|
|
|
|
- domain = info->domain;
|
|
|
|
- }
|
|
|
|
- spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
|
|
- /* pcie-pci bridge already has a domain, uses it */
|
|
|
|
- if (info)
|
|
|
|
- goto found_domain;
|
|
|
|
|
|
+ pci_for_each_dma_alias(pdev, get_last_alias, &dma_alias);
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&device_domain_lock, flags);
|
|
|
|
+ info = dmar_search_domain_by_dev_info(pci_domain_nr(pdev->bus),
|
|
|
|
+ PCI_BUS_NUM(dma_alias),
|
|
|
|
+ dma_alias & 0xff);
|
|
|
|
+ if (info) {
|
|
|
|
+ iommu = info->iommu;
|
|
|
|
+ domain = info->domain;
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
|
|
|
|
- iommu = device_to_iommu(dev, &bus, &devfn);
|
|
|
|
- if (!iommu)
|
|
|
|
- goto error;
|
|
|
|
|
|
+ /* DMA alias already has a domain, uses it */
|
|
|
|
+ if (info)
|
|
|
|
+ goto found_domain;
|
|
|
|
+ }
|
|
|
|
|
|
/* Allocate and initialize new domain for the device */
|
|
/* Allocate and initialize new domain for the device */
|
|
- domain = alloc_domain(false);
|
|
|
|
|
|
+ domain = alloc_domain(0);
|
|
if (!domain)
|
|
if (!domain)
|
|
- goto error;
|
|
|
|
- if (iommu_attach_domain(domain, iommu)) {
|
|
|
|
|
|
+ return NULL;
|
|
|
|
+ domain->id = iommu_attach_domain(domain, iommu);
|
|
|
|
+ if (domain->id < 0) {
|
|
free_domain_mem(domain);
|
|
free_domain_mem(domain);
|
|
- domain = NULL;
|
|
|
|
- goto error;
|
|
|
|
|
|
+ return NULL;
|
|
}
|
|
}
|
|
- free = domain;
|
|
|
|
- if (domain_init(domain, gaw))
|
|
|
|
- goto error;
|
|
|
|
|
|
+ domain_attach_iommu(domain, iommu);
|
|
|
|
+ if (domain_init(domain, gaw)) {
|
|
|
|
+ domain_exit(domain);
|
|
|
|
+ return NULL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* register PCI DMA alias device */
|
|
|
|
+ if (dev_is_pci(dev)) {
|
|
|
|
+ tmp = dmar_insert_dev_info(iommu, PCI_BUS_NUM(dma_alias),
|
|
|
|
+ dma_alias & 0xff, NULL, domain);
|
|
|
|
+
|
|
|
|
+ if (!tmp || tmp != domain) {
|
|
|
|
+ domain_exit(domain);
|
|
|
|
+ domain = tmp;
|
|
|
|
+ }
|
|
|
|
|
|
- /* register pcie-to-pci device */
|
|
|
|
- if (dev_tmp) {
|
|
|
|
- domain = dmar_insert_dev_info(iommu, bridge_bus, bridge_devfn,
|
|
|
|
- NULL, domain);
|
|
|
|
if (!domain)
|
|
if (!domain)
|
|
- goto error;
|
|
|
|
|
|
+ return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
found_domain:
|
|
found_domain:
|
|
- domain = dmar_insert_dev_info(iommu, bus, devfn, dev, domain);
|
|
|
|
-error:
|
|
|
|
- if (free != domain)
|
|
|
|
- domain_exit(free);
|
|
|
|
|
|
+ tmp = dmar_insert_dev_info(iommu, bus, devfn, dev, domain);
|
|
|
|
+
|
|
|
|
+ if (!tmp || tmp != domain) {
|
|
|
|
+ domain_exit(domain);
|
|
|
|
+ domain = tmp;
|
|
|
|
+ }
|
|
|
|
|
|
return domain;
|
|
return domain;
|
|
}
|
|
}
|
|
@@ -2405,6 +2418,7 @@ static inline void iommu_prepare_isa(void)
|
|
printk(KERN_ERR "IOMMU: Failed to create 0-16MiB identity map; "
|
|
printk(KERN_ERR "IOMMU: Failed to create 0-16MiB identity map; "
|
|
"floppy might not work\n");
|
|
"floppy might not work\n");
|
|
|
|
|
|
|
|
+ pci_dev_put(pdev);
|
|
}
|
|
}
|
|
#else
|
|
#else
|
|
static inline void iommu_prepare_isa(void)
|
|
static inline void iommu_prepare_isa(void)
|
|
@@ -2420,19 +2434,25 @@ static int __init si_domain_init(int hw)
|
|
struct dmar_drhd_unit *drhd;
|
|
struct dmar_drhd_unit *drhd;
|
|
struct intel_iommu *iommu;
|
|
struct intel_iommu *iommu;
|
|
int nid, ret = 0;
|
|
int nid, ret = 0;
|
|
|
|
+ bool first = true;
|
|
|
|
|
|
- si_domain = alloc_domain(false);
|
|
|
|
|
|
+ si_domain = alloc_domain(DOMAIN_FLAG_STATIC_IDENTITY);
|
|
if (!si_domain)
|
|
if (!si_domain)
|
|
return -EFAULT;
|
|
return -EFAULT;
|
|
|
|
|
|
- si_domain->flags = DOMAIN_FLAG_STATIC_IDENTITY;
|
|
|
|
-
|
|
|
|
for_each_active_iommu(iommu, drhd) {
|
|
for_each_active_iommu(iommu, drhd) {
|
|
ret = iommu_attach_domain(si_domain, iommu);
|
|
ret = iommu_attach_domain(si_domain, iommu);
|
|
- if (ret) {
|
|
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ domain_exit(si_domain);
|
|
|
|
+ return -EFAULT;
|
|
|
|
+ } else if (first) {
|
|
|
|
+ si_domain->id = ret;
|
|
|
|
+ first = false;
|
|
|
|
+ } else if (si_domain->id != ret) {
|
|
domain_exit(si_domain);
|
|
domain_exit(si_domain);
|
|
return -EFAULT;
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
+ domain_attach_iommu(si_domain, iommu);
|
|
}
|
|
}
|
|
|
|
|
|
if (md_domain_init(si_domain, DEFAULT_DOMAIN_ADDRESS_WIDTH)) {
|
|
if (md_domain_init(si_domain, DEFAULT_DOMAIN_ADDRESS_WIDTH)) {
|
|
@@ -2523,22 +2543,46 @@ static bool device_has_rmrr(struct device *dev)
|
|
return false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+/*
|
|
|
|
+ * There are a couple cases where we need to restrict the functionality of
|
|
|
|
+ * devices associated with RMRRs. The first is when evaluating a device for
|
|
|
|
+ * identity mapping because problems exist when devices are moved in and out
|
|
|
|
+ * of domains and their respective RMRR information is lost. This means that
|
|
|
|
+ * a device with associated RMRRs will never be in a "passthrough" domain.
|
|
|
|
+ * The second is use of the device through the IOMMU API. This interface
|
|
|
|
+ * expects to have full control of the IOVA space for the device. We cannot
|
|
|
|
+ * satisfy both the requirement that RMRR access is maintained and have an
|
|
|
|
+ * unencumbered IOVA space. We also have no ability to quiesce the device's
|
|
|
|
+ * use of the RMRR space or even inform the IOMMU API user of the restriction.
|
|
|
|
+ * We therefore prevent devices associated with an RMRR from participating in
|
|
|
|
+ * the IOMMU API, which eliminates them from device assignment.
|
|
|
|
+ *
|
|
|
|
+ * In both cases we assume that PCI USB devices with RMRRs have them largely
|
|
|
|
+ * for historical reasons and that the RMRR space is not actively used post
|
|
|
|
+ * boot. This exclusion may change if vendors begin to abuse it.
|
|
|
|
+ */
|
|
|
|
+static bool device_is_rmrr_locked(struct device *dev)
|
|
|
|
+{
|
|
|
|
+ if (!device_has_rmrr(dev))
|
|
|
|
+ return false;
|
|
|
|
+
|
|
|
|
+ if (dev_is_pci(dev)) {
|
|
|
|
+ struct pci_dev *pdev = to_pci_dev(dev);
|
|
|
|
+
|
|
|
|
+ if ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB)
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+}
|
|
|
|
+
|
|
static int iommu_should_identity_map(struct device *dev, int startup)
|
|
static int iommu_should_identity_map(struct device *dev, int startup)
|
|
{
|
|
{
|
|
|
|
|
|
if (dev_is_pci(dev)) {
|
|
if (dev_is_pci(dev)) {
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
|
|
|
|
- /*
|
|
|
|
- * We want to prevent any device associated with an RMRR from
|
|
|
|
- * getting placed into the SI Domain. This is done because
|
|
|
|
- * problems exist when devices are moved in and out of domains
|
|
|
|
- * and their respective RMRR info is lost. We exempt USB devices
|
|
|
|
- * from this process due to their usage of RMRRs that are known
|
|
|
|
- * to not be needed after BIOS hand-off to OS.
|
|
|
|
- */
|
|
|
|
- if (device_has_rmrr(dev) &&
|
|
|
|
- (pdev->class >> 8) != PCI_CLASS_SERIAL_USB)
|
|
|
|
|
|
+ if (device_is_rmrr_locked(dev))
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev))
|
|
if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev))
|
|
@@ -2850,11 +2894,7 @@ static int __init init_dmars(void)
|
|
|
|
|
|
iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
|
|
iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
|
|
iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
|
|
iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
|
|
-
|
|
|
|
- ret = iommu_enable_translation(iommu);
|
|
|
|
- if (ret)
|
|
|
|
- goto free_iommu;
|
|
|
|
-
|
|
|
|
|
|
+ iommu_enable_translation(iommu);
|
|
iommu_disable_protect_mem_regions(iommu);
|
|
iommu_disable_protect_mem_regions(iommu);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -3091,10 +3131,10 @@ static void flush_unmaps(void)
|
|
/* On real hardware multiple invalidations are expensive */
|
|
/* On real hardware multiple invalidations are expensive */
|
|
if (cap_caching_mode(iommu->cap))
|
|
if (cap_caching_mode(iommu->cap))
|
|
iommu_flush_iotlb_psi(iommu, domain->id,
|
|
iommu_flush_iotlb_psi(iommu, domain->id,
|
|
- iova->pfn_lo, iova->pfn_hi - iova->pfn_lo + 1,
|
|
|
|
|
|
+ iova->pfn_lo, iova_size(iova),
|
|
!deferred_flush[i].freelist[j], 0);
|
|
!deferred_flush[i].freelist[j], 0);
|
|
else {
|
|
else {
|
|
- mask = ilog2(mm_to_dma_pfn(iova->pfn_hi - iova->pfn_lo + 1));
|
|
|
|
|
|
+ mask = ilog2(mm_to_dma_pfn(iova_size(iova)));
|
|
iommu_flush_dev_iotlb(deferred_flush[i].domain[j],
|
|
iommu_flush_dev_iotlb(deferred_flush[i].domain[j],
|
|
(uint64_t)iova->pfn_lo << PAGE_SHIFT, mask);
|
|
(uint64_t)iova->pfn_lo << PAGE_SHIFT, mask);
|
|
}
|
|
}
|
|
@@ -3144,9 +3184,7 @@ static void add_unmap(struct dmar_domain *dom, struct iova *iova, struct page *f
|
|
spin_unlock_irqrestore(&async_umap_flush_lock, flags);
|
|
spin_unlock_irqrestore(&async_umap_flush_lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
-static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
|
|
- size_t size, enum dma_data_direction dir,
|
|
|
|
- struct dma_attrs *attrs)
|
|
|
|
|
|
+static void intel_unmap(struct device *dev, dma_addr_t dev_addr)
|
|
{
|
|
{
|
|
struct dmar_domain *domain;
|
|
struct dmar_domain *domain;
|
|
unsigned long start_pfn, last_pfn;
|
|
unsigned long start_pfn, last_pfn;
|
|
@@ -3190,6 +3228,13 @@ static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void intel_unmap_page(struct device *dev, dma_addr_t dev_addr,
|
|
|
|
+ size_t size, enum dma_data_direction dir,
|
|
|
|
+ struct dma_attrs *attrs)
|
|
|
|
+{
|
|
|
|
+ intel_unmap(dev, dev_addr);
|
|
|
|
+}
|
|
|
|
+
|
|
static void *intel_alloc_coherent(struct device *dev, size_t size,
|
|
static void *intel_alloc_coherent(struct device *dev, size_t size,
|
|
dma_addr_t *dma_handle, gfp_t flags,
|
|
dma_addr_t *dma_handle, gfp_t flags,
|
|
struct dma_attrs *attrs)
|
|
struct dma_attrs *attrs)
|
|
@@ -3246,7 +3291,7 @@ static void intel_free_coherent(struct device *dev, size_t size, void *vaddr,
|
|
size = PAGE_ALIGN(size);
|
|
size = PAGE_ALIGN(size);
|
|
order = get_order(size);
|
|
order = get_order(size);
|
|
|
|
|
|
- intel_unmap_page(dev, dma_handle, size, DMA_BIDIRECTIONAL, NULL);
|
|
|
|
|
|
+ intel_unmap(dev, dma_handle);
|
|
if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT))
|
|
if (!dma_release_from_contiguous(dev, page, size >> PAGE_SHIFT))
|
|
__free_pages(page, order);
|
|
__free_pages(page, order);
|
|
}
|
|
}
|
|
@@ -3255,43 +3300,7 @@ static void intel_unmap_sg(struct device *dev, struct scatterlist *sglist,
|
|
int nelems, enum dma_data_direction dir,
|
|
int nelems, enum dma_data_direction dir,
|
|
struct dma_attrs *attrs)
|
|
struct dma_attrs *attrs)
|
|
{
|
|
{
|
|
- struct dmar_domain *domain;
|
|
|
|
- unsigned long start_pfn, last_pfn;
|
|
|
|
- struct iova *iova;
|
|
|
|
- struct intel_iommu *iommu;
|
|
|
|
- struct page *freelist;
|
|
|
|
-
|
|
|
|
- if (iommu_no_mapping(dev))
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- domain = find_domain(dev);
|
|
|
|
- BUG_ON(!domain);
|
|
|
|
-
|
|
|
|
- iommu = domain_get_iommu(domain);
|
|
|
|
-
|
|
|
|
- iova = find_iova(&domain->iovad, IOVA_PFN(sglist[0].dma_address));
|
|
|
|
- if (WARN_ONCE(!iova, "Driver unmaps unmatched sglist at PFN %llx\n",
|
|
|
|
- (unsigned long long)sglist[0].dma_address))
|
|
|
|
- return;
|
|
|
|
-
|
|
|
|
- start_pfn = mm_to_dma_pfn(iova->pfn_lo);
|
|
|
|
- last_pfn = mm_to_dma_pfn(iova->pfn_hi + 1) - 1;
|
|
|
|
-
|
|
|
|
- freelist = domain_unmap(domain, start_pfn, last_pfn);
|
|
|
|
-
|
|
|
|
- if (intel_iommu_strict) {
|
|
|
|
- iommu_flush_iotlb_psi(iommu, domain->id, start_pfn,
|
|
|
|
- last_pfn - start_pfn + 1, !freelist, 0);
|
|
|
|
- /* free iova */
|
|
|
|
- __free_iova(&domain->iovad, iova);
|
|
|
|
- dma_free_pagelist(freelist);
|
|
|
|
- } else {
|
|
|
|
- add_unmap(domain, iova, freelist);
|
|
|
|
- /*
|
|
|
|
- * queue up the release of the unmap to save the 1/6th of the
|
|
|
|
- * cpu used up by the iotlb flush operation...
|
|
|
|
- */
|
|
|
|
- }
|
|
|
|
|
|
+ intel_unmap(dev, sglist[0].dma_address);
|
|
}
|
|
}
|
|
|
|
|
|
static int intel_nontranslate_map_sg(struct device *hddev,
|
|
static int intel_nontranslate_map_sg(struct device *hddev,
|
|
@@ -3355,13 +3364,8 @@ static int intel_map_sg(struct device *dev, struct scatterlist *sglist, int nele
|
|
|
|
|
|
ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
|
|
ret = domain_sg_mapping(domain, start_vpfn, sglist, size, prot);
|
|
if (unlikely(ret)) {
|
|
if (unlikely(ret)) {
|
|
- /* clear the page */
|
|
|
|
- dma_pte_clear_range(domain, start_vpfn,
|
|
|
|
- start_vpfn + size - 1);
|
|
|
|
- /* free page tables */
|
|
|
|
dma_pte_free_pagetable(domain, start_vpfn,
|
|
dma_pte_free_pagetable(domain, start_vpfn,
|
|
start_vpfn + size - 1);
|
|
start_vpfn + size - 1);
|
|
- /* free iova */
|
|
|
|
__free_iova(&domain->iovad, iova);
|
|
__free_iova(&domain->iovad, iova);
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
@@ -3568,10 +3572,8 @@ static int init_iommu_hw(void)
|
|
|
|
|
|
iommu->flush.flush_context(iommu, 0, 0, 0,
|
|
iommu->flush.flush_context(iommu, 0, 0, 0,
|
|
DMA_CCMD_GLOBAL_INVL);
|
|
DMA_CCMD_GLOBAL_INVL);
|
|
- iommu->flush.flush_iotlb(iommu, 0, 0, 0,
|
|
|
|
- DMA_TLB_GLOBAL_FLUSH);
|
|
|
|
- if (iommu_enable_translation(iommu))
|
|
|
|
- return 1;
|
|
|
|
|
|
+ iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
|
|
|
|
+ iommu_enable_translation(iommu);
|
|
iommu_disable_protect_mem_regions(iommu);
|
|
iommu_disable_protect_mem_regions(iommu);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -3873,9 +3875,7 @@ static int device_notifier(struct notifier_block *nb,
|
|
|
|
|
|
down_read(&dmar_global_lock);
|
|
down_read(&dmar_global_lock);
|
|
domain_remove_one_dev_info(domain, dev);
|
|
domain_remove_one_dev_info(domain, dev);
|
|
- if (!(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE) &&
|
|
|
|
- !(domain->flags & DOMAIN_FLAG_STATIC_IDENTITY) &&
|
|
|
|
- list_empty(&domain->devices))
|
|
|
|
|
|
+ if (!domain_type_is_vm_or_si(domain) && list_empty(&domain->devices))
|
|
domain_exit(domain);
|
|
domain_exit(domain);
|
|
up_read(&dmar_global_lock);
|
|
up_read(&dmar_global_lock);
|
|
|
|
|
|
@@ -3935,8 +3935,7 @@ static int intel_iommu_memory_notifier(struct notifier_block *nb,
|
|
rcu_read_lock();
|
|
rcu_read_lock();
|
|
for_each_active_iommu(iommu, drhd)
|
|
for_each_active_iommu(iommu, drhd)
|
|
iommu_flush_iotlb_psi(iommu, si_domain->id,
|
|
iommu_flush_iotlb_psi(iommu, si_domain->id,
|
|
- iova->pfn_lo,
|
|
|
|
- iova->pfn_hi - iova->pfn_lo + 1,
|
|
|
|
|
|
+ iova->pfn_lo, iova_size(iova),
|
|
!freelist, 0);
|
|
!freelist, 0);
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
dma_free_pagelist(freelist);
|
|
dma_free_pagelist(freelist);
|
|
@@ -3955,6 +3954,63 @@ static struct notifier_block intel_iommu_memory_nb = {
|
|
.priority = 0
|
|
.priority = 0
|
|
};
|
|
};
|
|
|
|
|
|
|
|
+
|
|
|
|
+static ssize_t intel_iommu_show_version(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = dev_get_drvdata(dev);
|
|
|
|
+ u32 ver = readl(iommu->reg + DMAR_VER_REG);
|
|
|
|
+ return sprintf(buf, "%d:%d\n",
|
|
|
|
+ DMAR_VER_MAJOR(ver), DMAR_VER_MINOR(ver));
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(version, S_IRUGO, intel_iommu_show_version, NULL);
|
|
|
|
+
|
|
|
|
+static ssize_t intel_iommu_show_address(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = dev_get_drvdata(dev);
|
|
|
|
+ return sprintf(buf, "%llx\n", iommu->reg_phys);
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(address, S_IRUGO, intel_iommu_show_address, NULL);
|
|
|
|
+
|
|
|
|
+static ssize_t intel_iommu_show_cap(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = dev_get_drvdata(dev);
|
|
|
|
+ return sprintf(buf, "%llx\n", iommu->cap);
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(cap, S_IRUGO, intel_iommu_show_cap, NULL);
|
|
|
|
+
|
|
|
|
+static ssize_t intel_iommu_show_ecap(struct device *dev,
|
|
|
|
+ struct device_attribute *attr,
|
|
|
|
+ char *buf)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = dev_get_drvdata(dev);
|
|
|
|
+ return sprintf(buf, "%llx\n", iommu->ecap);
|
|
|
|
+}
|
|
|
|
+static DEVICE_ATTR(ecap, S_IRUGO, intel_iommu_show_ecap, NULL);
|
|
|
|
+
|
|
|
|
+static struct attribute *intel_iommu_attrs[] = {
|
|
|
|
+ &dev_attr_version.attr,
|
|
|
|
+ &dev_attr_address.attr,
|
|
|
|
+ &dev_attr_cap.attr,
|
|
|
|
+ &dev_attr_ecap.attr,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct attribute_group intel_iommu_group = {
|
|
|
|
+ .name = "intel-iommu",
|
|
|
|
+ .attrs = intel_iommu_attrs,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+const struct attribute_group *intel_iommu_groups[] = {
|
|
|
|
+ &intel_iommu_group,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
int __init intel_iommu_init(void)
|
|
int __init intel_iommu_init(void)
|
|
{
|
|
{
|
|
int ret = -ENODEV;
|
|
int ret = -ENODEV;
|
|
@@ -4026,6 +4082,11 @@ int __init intel_iommu_init(void)
|
|
|
|
|
|
init_iommu_pm_ops();
|
|
init_iommu_pm_ops();
|
|
|
|
|
|
|
|
+ for_each_active_iommu(iommu, drhd)
|
|
|
|
+ iommu->iommu_dev = iommu_device_create(NULL, iommu,
|
|
|
|
+ intel_iommu_groups,
|
|
|
|
+ iommu->name);
|
|
|
|
+
|
|
bus_set_iommu(&pci_bus_type, &intel_iommu_ops);
|
|
bus_set_iommu(&pci_bus_type, &intel_iommu_ops);
|
|
bus_register_notifier(&pci_bus_type, &device_nb);
|
|
bus_register_notifier(&pci_bus_type, &device_nb);
|
|
if (si_domain && !hw_pass_through)
|
|
if (si_domain && !hw_pass_through)
|
|
@@ -4044,33 +4105,27 @@ out_free_dmar:
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int iommu_detach_dev_cb(struct pci_dev *pdev, u16 alias, void *opaque)
|
|
|
|
+{
|
|
|
|
+ struct intel_iommu *iommu = opaque;
|
|
|
|
+
|
|
|
|
+ iommu_detach_dev(iommu, PCI_BUS_NUM(alias), alias & 0xff);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * NB - intel-iommu lacks any sort of reference counting for the users of
|
|
|
|
+ * dependent devices. If multiple endpoints have intersecting dependent
|
|
|
|
+ * devices, unbinding the driver from any one of them will possibly leave
|
|
|
|
+ * the others unable to operate.
|
|
|
|
+ */
|
|
static void iommu_detach_dependent_devices(struct intel_iommu *iommu,
|
|
static void iommu_detach_dependent_devices(struct intel_iommu *iommu,
|
|
struct device *dev)
|
|
struct device *dev)
|
|
{
|
|
{
|
|
- struct pci_dev *tmp, *parent, *pdev;
|
|
|
|
-
|
|
|
|
if (!iommu || !dev || !dev_is_pci(dev))
|
|
if (!iommu || !dev || !dev_is_pci(dev))
|
|
return;
|
|
return;
|
|
|
|
|
|
- pdev = to_pci_dev(dev);
|
|
|
|
-
|
|
|
|
- /* dependent device detach */
|
|
|
|
- tmp = pci_find_upstream_pcie_bridge(pdev);
|
|
|
|
- /* Secondary interface's bus number and devfn 0 */
|
|
|
|
- if (tmp) {
|
|
|
|
- parent = pdev->bus->self;
|
|
|
|
- while (parent != tmp) {
|
|
|
|
- iommu_detach_dev(iommu, parent->bus->number,
|
|
|
|
- parent->devfn);
|
|
|
|
- parent = parent->bus->self;
|
|
|
|
- }
|
|
|
|
- if (pci_is_pcie(tmp)) /* this is a PCIe-to-PCI bridge */
|
|
|
|
- iommu_detach_dev(iommu,
|
|
|
|
- tmp->subordinate->number, 0);
|
|
|
|
- else /* this is a legacy PCI bridge */
|
|
|
|
- iommu_detach_dev(iommu, tmp->bus->number,
|
|
|
|
- tmp->devfn);
|
|
|
|
- }
|
|
|
|
|
|
+ pci_for_each_dma_alias(to_pci_dev(dev), &iommu_detach_dev_cb, iommu);
|
|
}
|
|
}
|
|
|
|
|
|
static void domain_remove_one_dev_info(struct dmar_domain *domain,
|
|
static void domain_remove_one_dev_info(struct dmar_domain *domain,
|
|
@@ -4117,20 +4172,9 @@ static void domain_remove_one_dev_info(struct dmar_domain *domain,
|
|
spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
spin_unlock_irqrestore(&device_domain_lock, flags);
|
|
|
|
|
|
if (found == 0) {
|
|
if (found == 0) {
|
|
- unsigned long tmp_flags;
|
|
|
|
- spin_lock_irqsave(&domain->iommu_lock, tmp_flags);
|
|
|
|
- clear_bit(iommu->seq_id, domain->iommu_bmp);
|
|
|
|
- domain->iommu_count--;
|
|
|
|
- domain_update_iommu_cap(domain);
|
|
|
|
- spin_unlock_irqrestore(&domain->iommu_lock, tmp_flags);
|
|
|
|
-
|
|
|
|
- if (!(domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE) &&
|
|
|
|
- !(domain->flags & DOMAIN_FLAG_STATIC_IDENTITY)) {
|
|
|
|
- spin_lock_irqsave(&iommu->lock, tmp_flags);
|
|
|
|
- clear_bit(domain->id, iommu->domain_ids);
|
|
|
|
- iommu->domains[domain->id] = NULL;
|
|
|
|
- spin_unlock_irqrestore(&iommu->lock, tmp_flags);
|
|
|
|
- }
|
|
|
|
|
|
+ domain_detach_iommu(domain, iommu);
|
|
|
|
+ if (!domain_type_is_vm_or_si(domain))
|
|
|
|
+ iommu_detach_domain(domain, iommu);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -4150,7 +4194,6 @@ static int md_domain_init(struct dmar_domain *domain, int guest_width)
|
|
domain->iommu_snooping = 0;
|
|
domain->iommu_snooping = 0;
|
|
domain->iommu_superpage = 0;
|
|
domain->iommu_superpage = 0;
|
|
domain->max_addr = 0;
|
|
domain->max_addr = 0;
|
|
- domain->nid = -1;
|
|
|
|
|
|
|
|
/* always allocate the top pgd */
|
|
/* always allocate the top pgd */
|
|
domain->pgd = (struct dma_pte *)alloc_pgtable_page(domain->nid);
|
|
domain->pgd = (struct dma_pte *)alloc_pgtable_page(domain->nid);
|
|
@@ -4164,7 +4207,7 @@ static int intel_iommu_domain_init(struct iommu_domain *domain)
|
|
{
|
|
{
|
|
struct dmar_domain *dmar_domain;
|
|
struct dmar_domain *dmar_domain;
|
|
|
|
|
|
- dmar_domain = alloc_domain(true);
|
|
|
|
|
|
+ dmar_domain = alloc_domain(DOMAIN_FLAG_VIRTUAL_MACHINE);
|
|
if (!dmar_domain) {
|
|
if (!dmar_domain) {
|
|
printk(KERN_ERR
|
|
printk(KERN_ERR
|
|
"intel_iommu_domain_init: dmar_domain == NULL\n");
|
|
"intel_iommu_domain_init: dmar_domain == NULL\n");
|
|
@@ -4202,14 +4245,18 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,
|
|
int addr_width;
|
|
int addr_width;
|
|
u8 bus, devfn;
|
|
u8 bus, devfn;
|
|
|
|
|
|
|
|
+ if (device_is_rmrr_locked(dev)) {
|
|
|
|
+ dev_warn(dev, "Device is ineligible for IOMMU domain attach due to platform RMRR requirement. Contact your platform vendor.\n");
|
|
|
|
+ return -EPERM;
|
|
|
|
+ }
|
|
|
|
+
|
|
/* normally dev is not mapped */
|
|
/* normally dev is not mapped */
|
|
if (unlikely(domain_context_mapped(dev))) {
|
|
if (unlikely(domain_context_mapped(dev))) {
|
|
struct dmar_domain *old_domain;
|
|
struct dmar_domain *old_domain;
|
|
|
|
|
|
old_domain = find_domain(dev);
|
|
old_domain = find_domain(dev);
|
|
if (old_domain) {
|
|
if (old_domain) {
|
|
- if (dmar_domain->flags & DOMAIN_FLAG_VIRTUAL_MACHINE ||
|
|
|
|
- dmar_domain->flags & DOMAIN_FLAG_STATIC_IDENTITY)
|
|
|
|
|
|
+ if (domain_type_is_vm_or_si(dmar_domain))
|
|
domain_remove_one_dev_info(old_domain, dev);
|
|
domain_remove_one_dev_info(old_domain, dev);
|
|
else
|
|
else
|
|
domain_remove_dev_info(old_domain);
|
|
domain_remove_dev_info(old_domain);
|
|
@@ -4373,99 +4420,42 @@ static int intel_iommu_domain_has_cap(struct iommu_domain *domain,
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
-#define REQ_ACS_FLAGS (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
|
|
|
|
-
|
|
|
|
static int intel_iommu_add_device(struct device *dev)
|
|
static int intel_iommu_add_device(struct device *dev)
|
|
{
|
|
{
|
|
- struct pci_dev *pdev = to_pci_dev(dev);
|
|
|
|
- struct pci_dev *bridge, *dma_pdev = NULL;
|
|
|
|
|
|
+ struct intel_iommu *iommu;
|
|
struct iommu_group *group;
|
|
struct iommu_group *group;
|
|
- int ret;
|
|
|
|
u8 bus, devfn;
|
|
u8 bus, devfn;
|
|
|
|
|
|
- if (!device_to_iommu(dev, &bus, &devfn))
|
|
|
|
|
|
+ iommu = device_to_iommu(dev, &bus, &devfn);
|
|
|
|
+ if (!iommu)
|
|
return -ENODEV;
|
|
return -ENODEV;
|
|
|
|
|
|
- bridge = pci_find_upstream_pcie_bridge(pdev);
|
|
|
|
- if (bridge) {
|
|
|
|
- if (pci_is_pcie(bridge))
|
|
|
|
- dma_pdev = pci_get_domain_bus_and_slot(
|
|
|
|
- pci_domain_nr(pdev->bus),
|
|
|
|
- bridge->subordinate->number, 0);
|
|
|
|
- if (!dma_pdev)
|
|
|
|
- dma_pdev = pci_dev_get(bridge);
|
|
|
|
- } else
|
|
|
|
- dma_pdev = pci_dev_get(pdev);
|
|
|
|
-
|
|
|
|
- /* Account for quirked devices */
|
|
|
|
- swap_pci_ref(&dma_pdev, pci_get_dma_source(dma_pdev));
|
|
|
|
|
|
+ iommu_device_link(iommu->iommu_dev, dev);
|
|
|
|
|
|
- /*
|
|
|
|
- * If it's a multifunction device that does not support our
|
|
|
|
- * required ACS flags, add to the same group as lowest numbered
|
|
|
|
- * function that also does not suport the required ACS flags.
|
|
|
|
- */
|
|
|
|
- if (dma_pdev->multifunction &&
|
|
|
|
- !pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)) {
|
|
|
|
- u8 i, slot = PCI_SLOT(dma_pdev->devfn);
|
|
|
|
-
|
|
|
|
- for (i = 0; i < 8; i++) {
|
|
|
|
- struct pci_dev *tmp;
|
|
|
|
|
|
+ group = iommu_group_get_for_dev(dev);
|
|
|
|
|
|
- tmp = pci_get_slot(dma_pdev->bus, PCI_DEVFN(slot, i));
|
|
|
|
- if (!tmp)
|
|
|
|
- continue;
|
|
|
|
-
|
|
|
|
- if (!pci_acs_enabled(tmp, REQ_ACS_FLAGS)) {
|
|
|
|
- swap_pci_ref(&dma_pdev, tmp);
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- pci_dev_put(tmp);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * Devices on the root bus go through the iommu. If that's not us,
|
|
|
|
- * find the next upstream device and test ACS up to the root bus.
|
|
|
|
- * Finding the next device may require skipping virtual buses.
|
|
|
|
- */
|
|
|
|
- while (!pci_is_root_bus(dma_pdev->bus)) {
|
|
|
|
- struct pci_bus *bus = dma_pdev->bus;
|
|
|
|
-
|
|
|
|
- while (!bus->self) {
|
|
|
|
- if (!pci_is_root_bus(bus))
|
|
|
|
- bus = bus->parent;
|
|
|
|
- else
|
|
|
|
- goto root_bus;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS))
|
|
|
|
- break;
|
|
|
|
-
|
|
|
|
- swap_pci_ref(&dma_pdev, pci_dev_get(bus->self));
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
-root_bus:
|
|
|
|
- group = iommu_group_get(&dma_pdev->dev);
|
|
|
|
- pci_dev_put(dma_pdev);
|
|
|
|
- if (!group) {
|
|
|
|
- group = iommu_group_alloc();
|
|
|
|
- if (IS_ERR(group))
|
|
|
|
- return PTR_ERR(group);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ret = iommu_group_add_device(group, dev);
|
|
|
|
|
|
+ if (IS_ERR(group))
|
|
|
|
+ return PTR_ERR(group);
|
|
|
|
|
|
iommu_group_put(group);
|
|
iommu_group_put(group);
|
|
- return ret;
|
|
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void intel_iommu_remove_device(struct device *dev)
|
|
static void intel_iommu_remove_device(struct device *dev)
|
|
{
|
|
{
|
|
|
|
+ struct intel_iommu *iommu;
|
|
|
|
+ u8 bus, devfn;
|
|
|
|
+
|
|
|
|
+ iommu = device_to_iommu(dev, &bus, &devfn);
|
|
|
|
+ if (!iommu)
|
|
|
|
+ return;
|
|
|
|
+
|
|
iommu_group_remove_device(dev);
|
|
iommu_group_remove_device(dev);
|
|
|
|
+
|
|
|
|
+ iommu_device_unlink(iommu->iommu_dev, dev);
|
|
}
|
|
}
|
|
|
|
|
|
-static struct iommu_ops intel_iommu_ops = {
|
|
|
|
|
|
+static const struct iommu_ops intel_iommu_ops = {
|
|
.domain_init = intel_iommu_domain_init,
|
|
.domain_init = intel_iommu_domain_init,
|
|
.domain_destroy = intel_iommu_domain_destroy,
|
|
.domain_destroy = intel_iommu_domain_destroy,
|
|
.attach_dev = intel_iommu_attach_device,
|
|
.attach_dev = intel_iommu_attach_device,
|