2015-06-13 06:47:46

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

Hi,

as David Woodhouse pointed out, my fixes and cleanups for
the original patch-set turned out to be a complete rewrite.
So to have a cleaner history of the feature and to make
backporting easier, here is a rewrite of my changes based on
v4.1-rc7.

Some additional issues have been fixed by this rewrite, like
a kdump-kernel boot panic with 'iommu=pt' and support for
copying the extended root-entry and context table formats
has been added.

I plan to rebuild the x86/vt-d branch of the iommu tree with
these patches.

Regards,

Joerg

Joerg Roedel (19):
iommu/vt-d: Cleanup log messages
iommu/vt-d: Init QI before root entry is allocated
iommu/vt-d: Make root entry visible for hardware right after
allocation
iommu/vt-d: Detect pre enabled translation
iommu/vt-d: Copy translation tables from old kernel
iommu/vt-d: Do not re-use domain-ids from the old kernel
iommu/vt-d: Mark copied context entries
iommu/vt-d: Allocate si_domain in init_dmars()
iommu/vt-d: Don't do early domain assignment if kdump kernel
iommu/vt-d: Don't copy translation tables if RTT bit needs to be
changed
iommu/vt-d: Don't disable translation prior to OS handover
iommu/vt-d: Enable Translation only if it was previously disabled
iommu/vt-d: Move EIM detection to intel_prepare_irq_remapping
iommu/vt-d: Move QI initializationt to intel_setup_irq_remapping
iommu/vt-d: Disable IRQ remapping in intel_prepare_irq_remapping
iommu/vt-d: Set IRTA in intel_setup_irq_remapping
iommu/vt-d: Copy IR table from old kernel when in kdump mode
iommu/vt-d: Make sure copied over IR entries are not reused
iommu/vt-d: Don't disable IR when it was previously enabled

drivers/iommu/dmar.c | 28 +-
drivers/iommu/intel-iommu.c | 495 ++++++++++++++++++++++++++++--------
drivers/iommu/intel_irq_remapping.c | 255 ++++++++++++-------
include/linux/intel-iommu.h | 5 +
4 files changed, 573 insertions(+), 210 deletions(-)

--
1.9.1


2015-06-13 06:52:12

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 01/19] iommu/vt-d: Cleanup log messages

From: Joerg Roedel <[email protected]>

Give them a common prefix that can be grepped for and
improve the wording here and there.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/dmar.c | 28 +++----
drivers/iommu/intel-iommu.c | 154 +++++++++++++++++-------------------
drivers/iommu/intel_irq_remapping.c | 29 ++++---
3 files changed, 101 insertions(+), 110 deletions(-)

diff --git a/drivers/iommu/dmar.c b/drivers/iommu/dmar.c
index 9847613..c588658 100644
--- a/drivers/iommu/dmar.c
+++ b/drivers/iommu/dmar.c
@@ -26,7 +26,7 @@
* These routines are used by both DMA-remapping and Interrupt-remapping
*/

-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt /* has to precede printk.h */
+#define pr_fmt(fmt) "DMAR: " fmt

#include <linux/pci.h>
#include <linux/dmar.h>
@@ -555,7 +555,7 @@ static int dmar_walk_remapping_entries(struct acpi_dmar_header *start,
break;
} else if (next > end) {
/* Avoid passing table end */
- pr_warn(FW_BUG "record passes table end\n");
+ pr_warn(FW_BUG "Record passes table end\n");
ret = -EINVAL;
break;
}
@@ -802,7 +802,7 @@ int __init dmar_table_init(void)
ret = parse_dmar_table();
if (ret < 0) {
if (ret != -ENODEV)
- pr_info("parse DMAR table failure.\n");
+ pr_info("Parse DMAR table failure.\n");
} else if (list_empty(&dmar_drhd_units)) {
pr_info("No DMAR devices found\n");
ret = -ENODEV;
@@ -847,7 +847,7 @@ dmar_validate_one_drhd(struct acpi_dmar_header *entry, void *arg)
else
addr = early_ioremap(drhd->address, VTD_PAGE_SIZE);
if (!addr) {
- pr_warn("IOMMU: can't validate: %llx\n", drhd->address);
+ pr_warn("Can't validate DRHD address: %llx\n", drhd->address);
return -EINVAL;
}

@@ -921,14 +921,14 @@ static int map_iommu(struct intel_iommu *iommu, u64 phys_addr)
iommu->reg_size = VTD_PAGE_SIZE;

if (!request_mem_region(iommu->reg_phys, iommu->reg_size, iommu->name)) {
- pr_err("IOMMU: can't reserve memory\n");
+ pr_err("Can't reserve memory\n");
err = -EBUSY;
goto out;
}

iommu->reg = ioremap(iommu->reg_phys, iommu->reg_size);
if (!iommu->reg) {
- pr_err("IOMMU: can't map the region\n");
+ pr_err("Can't map the region\n");
err = -ENOMEM;
goto release;
}
@@ -952,13 +952,13 @@ static int map_iommu(struct intel_iommu *iommu, u64 phys_addr)
iommu->reg_size = map_size;
if (!request_mem_region(iommu->reg_phys, iommu->reg_size,
iommu->name)) {
- pr_err("IOMMU: can't reserve memory\n");
+ pr_err("Can't reserve memory\n");
err = -EBUSY;
goto out;
}
iommu->reg = ioremap(iommu->reg_phys, iommu->reg_size);
if (!iommu->reg) {
- pr_err("IOMMU: can't map the region\n");
+ pr_err("Can't map the region\n");
err = -ENOMEM;
goto release;
}
@@ -1014,14 +1014,14 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
return -ENOMEM;

if (dmar_alloc_seq_id(iommu) < 0) {
- pr_err("IOMMU: failed to allocate seq_id\n");
+ pr_err("Failed to allocate seq_id\n");
err = -ENOSPC;
goto error;
}

err = map_iommu(iommu, drhd->reg_base_addr);
if (err) {
- pr_err("IOMMU: failed to map %s\n", iommu->name);
+ pr_err("Failed to map %s\n", iommu->name);
goto error_free_seq_id;
}

@@ -1045,8 +1045,8 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
iommu->node = -1;

ver = readl(iommu->reg + DMAR_VER_REG);
- pr_info("IOMMU %d: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
- iommu->seq_id,
+ pr_info("%s: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
+ iommu->name,
(unsigned long long)drhd->reg_base_addr,
DMAR_VER_MAJOR(ver), DMAR_VER_MINOR(ver),
(unsigned long long)iommu->cap,
@@ -1644,7 +1644,7 @@ int dmar_set_interrupt(struct intel_iommu *iommu)

irq = dmar_alloc_hwirq();
if (irq <= 0) {
- pr_err("IOMMU: no free vectors\n");
+ pr_err("No free IRQ vectors\n");
return -EINVAL;
}

@@ -1661,7 +1661,7 @@ int dmar_set_interrupt(struct intel_iommu *iommu)

ret = request_irq(irq, dmar_fault, IRQF_NO_THREAD, iommu->name, iommu);
if (ret)
- pr_err("IOMMU: can't request irq\n");
+ pr_err("Can't request irq\n");
return ret;
}

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 68d43be..14af51d 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -15,8 +15,11 @@
* Shaohua Li <[email protected]>,
* Anil S Keshavamurthy <[email protected]>,
* Fenghua Yu <[email protected]>
+ * Joerg Roedel <[email protected]>
*/

+#define pr_fmt(fmt) "DMAR: " fmt
+
#include <linux/init.h>
#include <linux/bitmap.h>
#include <linux/debugfs.h>
@@ -445,25 +448,21 @@ static int __init intel_iommu_setup(char *str)
while (*str) {
if (!strncmp(str, "on", 2)) {
dmar_disabled = 0;
- printk(KERN_INFO "Intel-IOMMU: enabled\n");
+ pr_info("IOMMU enabled\n");
} else if (!strncmp(str, "off", 3)) {
dmar_disabled = 1;
- printk(KERN_INFO "Intel-IOMMU: disabled\n");
+ pr_info("IOMMU disabled\n");
} else if (!strncmp(str, "igfx_off", 8)) {
dmar_map_gfx = 0;
- printk(KERN_INFO
- "Intel-IOMMU: disable GFX device mapping\n");
+ pr_info("Disable GFX device mapping\n");
} else if (!strncmp(str, "forcedac", 8)) {
- printk(KERN_INFO
- "Intel-IOMMU: Forcing DAC for PCI devices\n");
+ pr_info("Forcing DAC for PCI devices\n");
dmar_forcedac = 1;
} else if (!strncmp(str, "strict", 6)) {
- printk(KERN_INFO
- "Intel-IOMMU: disable batched IOTLB flush\n");
+ pr_info("Disable batched IOTLB flush\n");
intel_iommu_strict = 1;
} else if (!strncmp(str, "sp_off", 6)) {
- printk(KERN_INFO
- "Intel-IOMMU: disable supported super page\n");
+ pr_info("Disable supported super page\n");
intel_iommu_superpage = 0;
}

@@ -1112,7 +1111,7 @@ static int iommu_alloc_root_entry(struct intel_iommu *iommu)

root = (struct root_entry *)alloc_pgtable_page(iommu->node);
if (!root) {
- pr_err("IOMMU: allocating root entry for %s failed\n",
+ pr_err("Allocating root entry for %s failed\n",
iommu->name);
return -ENOMEM;
}
@@ -1250,9 +1249,9 @@ static void __iommu_flush_iotlb(struct intel_iommu *iommu, u16 did,

/* check IOTLB invalidation granularity */
if (DMA_TLB_IAIG(val) == 0)
- printk(KERN_ERR"IOMMU: flush IOTLB failed\n");
+ pr_err("Flush IOTLB failed\n");
if (DMA_TLB_IAIG(val) != DMA_TLB_IIRG(type))
- pr_debug("IOMMU: tlb flush request %Lx, actual %Lx\n",
+ pr_debug("TLB flush request %Lx, actual %Lx\n",
(unsigned long long)DMA_TLB_IIRG(type),
(unsigned long long)DMA_TLB_IAIG(val));
}
@@ -1423,8 +1422,8 @@ static int iommu_init_domains(struct intel_iommu *iommu)
unsigned long nlongs;

ndomains = cap_ndoms(iommu->cap);
- pr_debug("IOMMU%d: Number of Domains supported <%ld>\n",
- iommu->seq_id, ndomains);
+ pr_debug("%s: Number of Domains supported <%ld>\n",
+ iommu->name, ndomains);
nlongs = BITS_TO_LONGS(ndomains);

spin_lock_init(&iommu->lock);
@@ -1434,15 +1433,15 @@ static int iommu_init_domains(struct intel_iommu *iommu)
*/
iommu->domain_ids = kcalloc(nlongs, sizeof(unsigned long), GFP_KERNEL);
if (!iommu->domain_ids) {
- pr_err("IOMMU%d: allocating domain id array failed\n",
- iommu->seq_id);
+ pr_err("%s: Allocating domain id array failed\n",
+ iommu->name);
return -ENOMEM;
}
iommu->domains = kcalloc(ndomains, sizeof(struct dmar_domain *),
GFP_KERNEL);
if (!iommu->domains) {
- pr_err("IOMMU%d: allocating domain array failed\n",
- iommu->seq_id);
+ pr_err("%s: Allocating domain array failed\n",
+ iommu->name);
kfree(iommu->domain_ids);
iommu->domain_ids = NULL;
return -ENOMEM;
@@ -1547,7 +1546,7 @@ static int iommu_attach_domain(struct dmar_domain *domain,
num = __iommu_attach_domain(domain, iommu);
spin_unlock_irqrestore(&iommu->lock, flags);
if (num < 0)
- pr_err("IOMMU: no free domain ids\n");
+ pr_err("%s: No free domain ids\n", iommu->name);

return num;
}
@@ -1639,7 +1638,7 @@ static int dmar_init_reserved_ranges(void)
iova = reserve_iova(&reserved_iova_list, IOVA_PFN(IOAPIC_RANGE_START),
IOVA_PFN(IOAPIC_RANGE_END));
if (!iova) {
- printk(KERN_ERR "Reserve IOAPIC range failed\n");
+ pr_err("Reserve IOAPIC range failed\n");
return -ENODEV;
}

@@ -1655,7 +1654,7 @@ static int dmar_init_reserved_ranges(void)
IOVA_PFN(r->start),
IOVA_PFN(r->end));
if (!iova) {
- printk(KERN_ERR "Reserve iova failed\n");
+ pr_err("Reserve iova failed\n");
return -ENODEV;
}
}
@@ -1702,7 +1701,7 @@ static int domain_init(struct dmar_domain *domain, int guest_width)
sagaw = cap_sagaw(iommu->cap);
if (!test_bit(agaw, &sagaw)) {
/* hardware doesn't support it, choose a bigger one */
- pr_debug("IOMMU: hardware doesn't support agaw %d\n", agaw);
+ pr_debug("Hardware doesn't support agaw %d\n", agaw);
agaw = find_next_bit(&sagaw, 5, agaw);
if (agaw >= 5)
return -ENODEV;
@@ -1803,7 +1802,7 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
id = iommu_attach_vm_domain(domain, iommu);
if (id < 0) {
spin_unlock_irqrestore(&iommu->lock, flags);
- pr_err("IOMMU: no free domain ids\n");
+ pr_err("%s: No free domain ids\n", iommu->name);
return -EFAULT;
}
}
@@ -2030,8 +2029,8 @@ static int __domain_mapping(struct dmar_domain *domain, unsigned long iov_pfn,
tmp = cmpxchg64_local(&pte->val, 0ULL, pteval);
if (tmp) {
static int dumps = 5;
- printk(KERN_CRIT "ERROR: DMA PTE for vPFN 0x%lx already set (to %llx not %llx)\n",
- iov_pfn, tmp, (unsigned long long)pteval);
+ pr_crit("ERROR: DMA PTE for vPFN 0x%lx already set (to %llx not %llx)\n",
+ iov_pfn, tmp, (unsigned long long)pteval);
if (dumps) {
dumps--;
debug_dma_dump_mappings(NULL);
@@ -2303,7 +2302,7 @@ static int iommu_domain_identity_map(struct dmar_domain *domain,

if (!reserve_iova(&domain->iovad, dma_to_mm_pfn(first_vpfn),
dma_to_mm_pfn(last_vpfn))) {
- printk(KERN_ERR "IOMMU: reserve iova failed\n");
+ pr_err("Reserving iova failed\n");
return -ENOMEM;
}

@@ -2336,15 +2335,14 @@ static int iommu_prepare_identity_map(struct device *dev,
range which is reserved in E820, so which didn't get set
up to start with in si_domain */
if (domain == si_domain && hw_pass_through) {
- printk("Ignoring identity map for HW passthrough device %s [0x%Lx - 0x%Lx]\n",
- dev_name(dev), start, end);
+ pr_warn("Ignoring identity map for HW passthrough device %s [0x%Lx - 0x%Lx]\n",
+ dev_name(dev), start, end);
return 0;
}

- printk(KERN_INFO
- "IOMMU: Setting identity map for device %s [0x%Lx - 0x%Lx]\n",
- dev_name(dev), start, end);
-
+ pr_info("Setting identity map for device %s [0x%Lx - 0x%Lx]\n",
+ dev_name(dev), start, end);
+
if (end < start) {
WARN(1, "Your BIOS is broken; RMRR ends before it starts!\n"
"BIOS vendor: %s; Ver: %s; Product Version: %s\n",
@@ -2401,12 +2399,11 @@ static inline void iommu_prepare_isa(void)
if (!pdev)
return;

- printk(KERN_INFO "IOMMU: Prepare 0-16MiB unity mapping for LPC\n");
+ pr_info("Prepare 0-16MiB unity mapping for LPC\n");
ret = iommu_prepare_identity_map(&pdev->dev, 0, 16*1024*1024 - 1);

if (ret)
- printk(KERN_ERR "IOMMU: Failed to create 0-16MiB identity map; "
- "floppy might not work\n");
+ pr_err("Failed to create 0-16MiB identity map - floppy might not work\n");

pci_dev_put(pdev);
}
@@ -2450,7 +2447,7 @@ static int __init si_domain_init(int hw)
return -EFAULT;
}

- pr_debug("IOMMU: identity mapping domain is domain %d\n",
+ pr_debug("Identity mapping domain is domain %d\n",
si_domain->id);

if (hw)
@@ -2650,8 +2647,8 @@ static int __init dev_prepare_static_identity_mapping(struct device *dev, int hw
hw ? CONTEXT_TT_PASS_THROUGH :
CONTEXT_TT_MULTI_LEVEL);
if (!ret)
- pr_info("IOMMU: %s identity mapping for device %s\n",
- hw ? "hardware" : "software", dev_name(dev));
+ pr_info("%s identity mapping for device %s\n",
+ hw ? "Hardware" : "Software", dev_name(dev));
else if (ret == -ENODEV)
/* device not associated with an iommu */
ret = 0;
@@ -2728,12 +2725,12 @@ static void intel_iommu_init_qi(struct intel_iommu *iommu)
*/
iommu->flush.flush_context = __iommu_flush_context;
iommu->flush.flush_iotlb = __iommu_flush_iotlb;
- pr_info("IOMMU: %s using Register based invalidation\n",
+ pr_info("%s: Using Register based invalidation\n",
iommu->name);
} else {
iommu->flush.flush_context = qi_flush_context;
iommu->flush.flush_iotlb = qi_flush_iotlb;
- pr_info("IOMMU: %s using Queued invalidation\n", iommu->name);
+ pr_info("%s: Using Queued invalidation\n", iommu->name);
}
}

@@ -2761,8 +2758,7 @@ static int __init init_dmars(void)
g_num_of_iommus++;
continue;
}
- printk_once(KERN_ERR "intel-iommu: exceeded %d IOMMUs\n",
- DMAR_UNITS_SUPPORTED);
+ pr_err_once("Exceeded %d IOMMUs\n", DMAR_UNITS_SUPPORTED);
}

/* Preallocate enough resources for IOMMU hot-addition */
@@ -2772,7 +2768,7 @@ static int __init init_dmars(void)
g_iommus = kcalloc(g_num_of_iommus, sizeof(struct intel_iommu *),
GFP_KERNEL);
if (!g_iommus) {
- printk(KERN_ERR "Allocating global iommu array failed\n");
+ pr_err("Allocating global iommu array failed\n");
ret = -ENOMEM;
goto error;
}
@@ -2823,7 +2819,7 @@ static int __init init_dmars(void)
if (iommu_identity_mapping) {
ret = iommu_prepare_static_identity_mapping(hw_pass_through);
if (ret) {
- printk(KERN_CRIT "Failed to setup IOMMU pass-through\n");
+ pr_crit("Failed to setup IOMMU pass-through\n");
goto free_iommu;
}
}
@@ -2841,15 +2837,14 @@ static int __init init_dmars(void)
* endfor
* endfor
*/
- printk(KERN_INFO "IOMMU: Setting RMRR:\n");
+ pr_info("Setting RMRR:\n");
for_each_rmrr_units(rmrr) {
/* some BIOS lists non-exist devices in DMAR table. */
for_each_active_dev_scope(rmrr->devices, rmrr->devices_cnt,
i, dev) {
ret = iommu_prepare_rmrr_dev(rmrr, dev);
if (ret)
- printk(KERN_ERR
- "IOMMU: mapping reserved region failed\n");
+ pr_err("Mapping reserved region failed\n");
}
}

@@ -2924,7 +2919,7 @@ static struct iova *intel_alloc_iova(struct device *dev,
}
iova = alloc_iova(&domain->iovad, nrpages, IOVA_PFN(dma_mask), 1);
if (unlikely(!iova)) {
- printk(KERN_ERR "Allocating %ld-page iova for %s failed",
+ pr_err("Allocating %ld-page iova for %s failed",
nrpages, dev_name(dev));
return NULL;
}
@@ -2939,7 +2934,7 @@ static struct dmar_domain *__get_valid_domain_for_dev(struct device *dev)

domain = get_domain_for_dev(dev, DEFAULT_DOMAIN_ADDRESS_WIDTH);
if (!domain) {
- printk(KERN_ERR "Allocating domain for %s failed",
+ pr_err("Allocating domain for %s failed\n",
dev_name(dev));
return NULL;
}
@@ -2948,7 +2943,7 @@ static struct dmar_domain *__get_valid_domain_for_dev(struct device *dev)
if (unlikely(!domain_context_mapped(dev))) {
ret = domain_context_mapping(domain, dev, CONTEXT_TT_MULTI_LEVEL);
if (ret) {
- printk(KERN_ERR "Domain context map for %s failed",
+ pr_err("Domain context map for %s failed\n",
dev_name(dev));
return NULL;
}
@@ -2995,8 +2990,8 @@ static int iommu_no_mapping(struct device *dev)
* to non-identity mapping.
*/
domain_remove_one_dev_info(si_domain, dev);
- printk(KERN_INFO "32bit %s uses non-identity mapping\n",
- dev_name(dev));
+ pr_info("32bit %s uses non-identity mapping\n",
+ dev_name(dev));
return 0;
}
} else {
@@ -3011,8 +3006,8 @@ static int iommu_no_mapping(struct device *dev)
CONTEXT_TT_PASS_THROUGH :
CONTEXT_TT_MULTI_LEVEL);
if (!ret) {
- printk(KERN_INFO "64bit %s uses identity mapping\n",
- dev_name(dev));
+ pr_info("64bit %s uses identity mapping\n",
+ dev_name(dev));
return 1;
}
}
@@ -3081,7 +3076,7 @@ static dma_addr_t __intel_map_single(struct device *dev, phys_addr_t paddr,
error:
if (iova)
__free_iova(&domain->iovad, iova);
- printk(KERN_ERR"Device %s request: %zx@%llx dir %d --- failed\n",
+ pr_err("Device %s request: %zx@%llx dir %d --- failed\n",
dev_name(dev), size, (unsigned long long)paddr, dir);
return 0;
}
@@ -3396,7 +3391,7 @@ static inline int iommu_domain_cache_init(void)

NULL);
if (!iommu_domain_cache) {
- printk(KERN_ERR "Couldn't create iommu_domain cache\n");
+ pr_err("Couldn't create iommu_domain cache\n");
ret = -ENOMEM;
}

@@ -3413,7 +3408,7 @@ static inline int iommu_devinfo_cache_init(void)
SLAB_HWCACHE_ALIGN,
NULL);
if (!iommu_devinfo_cache) {
- printk(KERN_ERR "Couldn't create devinfo cache\n");
+ pr_err("Couldn't create devinfo cache\n");
ret = -ENOMEM;
}

@@ -3790,19 +3785,19 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
return 0;

if (hw_pass_through && !ecap_pass_through(iommu->ecap)) {
- pr_warn("IOMMU: %s doesn't support hardware pass through.\n",
+ pr_warn("%s: Doesn't support hardware pass through.\n",
iommu->name);
return -ENXIO;
}
if (!ecap_sc_support(iommu->ecap) &&
domain_update_iommu_snooping(iommu)) {
- pr_warn("IOMMU: %s doesn't support snooping.\n",
+ pr_warn("%s: Doesn't support snooping.\n",
iommu->name);
return -ENXIO;
}
sp = domain_update_iommu_superpage(iommu) - 1;
if (sp >= 0 && !(cap_super_page_val(iommu->cap) & (1 << sp))) {
- pr_warn("IOMMU: %s doesn't support large page.\n",
+ pr_warn("%s: Doesn't support large page.\n",
iommu->name);
return -ENXIO;
}
@@ -4033,7 +4028,7 @@ static int intel_iommu_memory_notifier(struct notifier_block *nb,
start = mhp->start_pfn << PAGE_SHIFT;
end = ((mhp->start_pfn + mhp->nr_pages) << PAGE_SHIFT) - 1;
if (iommu_domain_identity_map(si_domain, start, end)) {
- pr_warn("dmar: failed to build identity map for [%llx-%llx]\n",
+ pr_warn("Failed to build identity map for [%llx-%llx]\n",
start, end);
return NOTIFY_BAD;
}
@@ -4051,7 +4046,7 @@ static int intel_iommu_memory_notifier(struct notifier_block *nb,

iova = find_iova(&si_domain->iovad, start_vpfn);
if (iova == NULL) {
- pr_debug("dmar: failed get IOVA for PFN %lx\n",
+ pr_debug("Failed get IOVA for PFN %lx\n",
start_vpfn);
break;
}
@@ -4059,7 +4054,7 @@ static int intel_iommu_memory_notifier(struct notifier_block *nb,
iova = split_and_remove_iova(&si_domain->iovad, iova,
start_vpfn, last_vpfn);
if (iova == NULL) {
- pr_warn("dmar: failed to split IOVA PFN [%lx-%lx]\n",
+ pr_warn("Failed to split IOVA PFN [%lx-%lx]\n",
start_vpfn, last_vpfn);
return NOTIFY_BAD;
}
@@ -4185,10 +4180,10 @@ int __init intel_iommu_init(void)
goto out_free_dmar;

if (list_empty(&dmar_rmrr_units))
- printk(KERN_INFO "DMAR: No RMRR found\n");
+ pr_info("No RMRR found\n");

if (list_empty(&dmar_atsr_units))
- printk(KERN_INFO "DMAR: No ATSR found\n");
+ pr_info("No ATSR found\n");

if (dmar_init_reserved_ranges()) {
if (force_on)
@@ -4202,12 +4197,11 @@ int __init intel_iommu_init(void)
if (ret) {
if (force_on)
panic("tboot: Failed to initialize DMARs\n");
- printk(KERN_ERR "IOMMU: dmar init failed\n");
+ pr_err("Initialization failed\n");
goto out_free_reserved_range;
}
up_write(&dmar_global_lock);
- printk(KERN_INFO
- "PCI-DMA: Intel(R) Virtualization Technology for Directed I/O\n");
+ pr_info("Intel(R) Virtualization Technology for Directed I/O\n");

init_timer(&unmap_timer);
#ifdef CONFIG_SWIOTLB
@@ -4349,13 +4343,11 @@ static struct iommu_domain *intel_iommu_domain_alloc(unsigned type)

dmar_domain = alloc_domain(DOMAIN_FLAG_VIRTUAL_MACHINE);
if (!dmar_domain) {
- printk(KERN_ERR
- "intel_iommu_domain_init: dmar_domain == NULL\n");
+ pr_err("Can't allocate dmar_domain\n");
return NULL;
}
if (md_domain_init(dmar_domain, DEFAULT_DOMAIN_ADDRESS_WIDTH)) {
- printk(KERN_ERR
- "intel_iommu_domain_init() failed\n");
+ pr_err("Domain initialization failed\n");
domain_exit(dmar_domain);
return NULL;
}
@@ -4414,7 +4406,7 @@ static int intel_iommu_attach_device(struct iommu_domain *domain,
addr_width = cap_mgaw(iommu->cap);

if (dmar_domain->max_addr > (1LL << addr_width)) {
- printk(KERN_ERR "%s: iommu width (%d) is not "
+ pr_err("%s: iommu width (%d) is not "
"sufficient for the mapped address (%llx)\n",
__func__, addr_width, dmar_domain->max_addr);
return -EFAULT;
@@ -4468,7 +4460,7 @@ static int intel_iommu_map(struct iommu_domain *domain,
/* check if minimum agaw is sufficient for mapped address */
end = __DOMAIN_MAX_ADDR(dmar_domain->gaw) + 1;
if (end < max_addr) {
- printk(KERN_ERR "%s: iommu width (%d) is not "
+ pr_err("%s: iommu width (%d) is not "
"sufficient for the mapped address (%llx)\n",
__func__, dmar_domain->gaw, max_addr);
return -EFAULT;
@@ -4609,7 +4601,7 @@ static const struct iommu_ops intel_iommu_ops = {
static void quirk_iommu_g4x_gfx(struct pci_dev *dev)
{
/* G4x/GM45 integrated gfx dmar support is totally busted. */
- printk(KERN_INFO "DMAR: Disabling IOMMU for graphics on this chipset\n");
+ pr_info("Disabling IOMMU for graphics on this chipset\n");
dmar_map_gfx = 0;
}

@@ -4627,7 +4619,7 @@ static void quirk_iommu_rwbf(struct pci_dev *dev)
* Mobile 4 Series Chipset neglects to set RWBF capability,
* but needs it. Same seems to hold for the desktop versions.
*/
- printk(KERN_INFO "DMAR: Forcing write-buffer flush capability\n");
+ pr_info("Forcing write-buffer flush capability\n");
rwbf_quirk = 1;
}

@@ -4657,11 +4649,11 @@ static void quirk_calpella_no_shadow_gtt(struct pci_dev *dev)
return;

if (!(ggc & GGC_MEMORY_VT_ENABLED)) {
- printk(KERN_INFO "DMAR: BIOS has allocated no shadow GTT; disabling IOMMU for graphics\n");
+ pr_info("BIOS has allocated no shadow GTT; disabling IOMMU for graphics\n");
dmar_map_gfx = 0;
} else if (dmar_map_gfx) {
/* we have to ensure the gfx device is idle before we flush */
- printk(KERN_INFO "DMAR: Disabling batched IOTLB flush on Ironlake\n");
+ pr_info("Disabling batched IOTLB flush on Ironlake\n");
intel_iommu_strict = 1;
}
}
@@ -4723,7 +4715,7 @@ static void __init check_tylersburg_isoch(void)
iommu_identity_mapping |= IDENTMAP_AZALIA;
return;
}
-
- printk(KERN_WARNING "DMAR: Recommended TLB entries for ISOCH unit is 16; your BIOS set %d\n",
+
+ pr_warn("Recommended TLB entries for ISOCH unit is 16; your BIOS set %d\n",
vtisochctrl);
}
diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 5709ae9..3fe3fc7 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -1,3 +1,6 @@
+
+#define pr_fmt(fmt) "DMAR-IR: " fmt
+
#include <linux/interrupt.h>
#include <linux/dmar.h>
#include <linux/spinlock.h>
@@ -100,8 +103,7 @@ static int alloc_irte(struct intel_iommu *iommu, int irq, u16 count)
}

if (mask > ecap_max_handle_mask(iommu->ecap)) {
- printk(KERN_ERR
- "Requested mask %x exceeds the max invalidation handle"
+ pr_err("Requested mask %x exceeds the max invalidation handle"
" mask value %Lx\n", mask,
ecap_max_handle_mask(iommu->ecap));
return -1;
@@ -333,7 +335,7 @@ static int set_ioapic_sid(struct irte *irte, int apic)
up_read(&dmar_global_lock);

if (sid == 0) {
- pr_warning("Failed to set source-id of IOAPIC (%d)\n", apic);
+ pr_warn("Failed to set source-id of IOAPIC (%d)\n", apic);
return -1;
}

@@ -360,7 +362,7 @@ static int set_hpet_sid(struct irte *irte, u8 id)
up_read(&dmar_global_lock);

if (sid == 0) {
- pr_warning("Failed to set source-id of HPET block (%d)\n", id);
+ pr_warn("Failed to set source-id of HPET block (%d)\n", id);
return -1;
}

@@ -580,7 +582,7 @@ static void __init intel_cleanup_irq_remapping(void)
}

if (x2apic_supported())
- pr_warn("Failed to enable irq remapping. You are vulnerable to irq-injection attacks.\n");
+ pr_warn("Failed to enable irq remapping. You are vulnerable to irq-injection attacks.\n");
}

static int __init intel_prepare_irq_remapping(void)
@@ -589,8 +591,7 @@ static int __init intel_prepare_irq_remapping(void)
struct intel_iommu *iommu;

if (irq_remap_broken) {
- printk(KERN_WARNING
- "This system BIOS has enabled interrupt remapping\n"
+ pr_warn("This system BIOS has enabled interrupt remapping\n"
"on a chipset that contains an erratum making that\n"
"feature unstable. To maintain system stability\n"
"interrupt remapping is being disabled. Please\n"
@@ -606,7 +607,7 @@ static int __init intel_prepare_irq_remapping(void)
return -ENODEV;

if (parse_ioapics_under_ir() != 1) {
- printk(KERN_INFO "Not enabling interrupt remapping\n");
+ pr_info("Not enabling interrupt remapping\n");
goto error;
}

@@ -667,8 +668,8 @@ static int __init intel_enable_irq_remapping(void)
*/
for_each_iommu(iommu, drhd)
if (eim && !ecap_eim_support(iommu->ecap)) {
- printk(KERN_INFO "DRHD %Lx: EIM not supported by DRHD, "
- " ecap %Lx\n", drhd->reg_base_addr, iommu->ecap);
+ pr_info("DRHD %Lx: EIM not supported by DRHD, "
+ " ecap %Lx\n", drhd->reg_base_addr, iommu->ecap);
eim = 0;
}
eim_mode = eim;
@@ -682,7 +683,7 @@ static int __init intel_enable_irq_remapping(void)
int ret = dmar_enable_qi(iommu);

if (ret) {
- printk(KERN_ERR "DRHD %Lx: failed to enable queued, "
+ pr_err("DRHD %Lx: failed to enable queued, "
" invalidation, ecap %Lx, ret %d\n",
drhd->reg_base_addr, iommu->ecap, ret);
goto error;
@@ -1145,14 +1146,12 @@ static int intel_msi_alloc_irq(struct pci_dev *dev, int irq, int nvec)
down_read(&dmar_global_lock);
iommu = map_dev_to_ir(dev);
if (!iommu) {
- printk(KERN_ERR
- "Unable to map PCI %s to iommu\n", pci_name(dev));
+ pr_err("Unable to map PCI %s to iommu\n", pci_name(dev));
index = -ENOENT;
} else {
index = alloc_irte(iommu, irq, nvec);
if (index < 0) {
- printk(KERN_ERR
- "Unable to allocate %d IRTE for PCI %s\n",
+ pr_err("Unable to allocate %d IRTE for PCI %s\n",
nvec, pci_name(dev));
index = -ENOSPC;
}
--
1.9.1

2015-06-13 06:47:56

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 02/19] iommu/vt-d: Init QI before root entry is allocated

From: Joerg Roedel <[email protected]>

QI needs to be available when we write the root entry into
hardware because flushes might be necessary after this.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 14af51d..b380fd1 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -2783,6 +2783,8 @@ static int __init init_dmars(void)
for_each_active_iommu(iommu, drhd) {
g_iommus[iommu->seq_id] = iommu;

+ intel_iommu_init_qi(iommu);
+
ret = iommu_init_domains(iommu);
if (ret)
goto free_iommu;
@@ -2799,9 +2801,6 @@ static int __init init_dmars(void)
hw_pass_through = 0;
}

- for_each_active_iommu(iommu, drhd)
- intel_iommu_init_qi(iommu);
-
if (iommu_pass_through)
iommu_identity_mapping |= IDENTMAP_ALL;

--
1.9.1

2015-06-13 06:52:00

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 03/19] iommu/vt-d: Make root entry visible for hardware right after allocation

From: Joerg Roedel <[email protected]>

In case there was an old root entry, make our new one
visible immediately after it was allocated.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index b380fd1..f82918e 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -2797,6 +2797,12 @@ static int __init init_dmars(void)
ret = iommu_alloc_root_entry(iommu);
if (ret)
goto free_iommu;
+
+ iommu_flush_write_buffer(iommu);
+ iommu_set_root_entry(iommu);
+ iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
+ iommu->flush.flush_iotlb(iommu, 0, 0, 0, DMA_TLB_GLOBAL_FLUSH);
+
if (!ecap_pass_through(iommu->ecap))
hw_pass_through = 0;
}
@@ -2873,10 +2879,6 @@ static int __init init_dmars(void)
if (ret)
goto free_iommu;

- iommu_set_root_entry(iommu);
-
- 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_enable_translation(iommu);
iommu_disable_protect_mem_regions(iommu);
}
--
1.9.1

2015-06-13 06:52:06

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 04/19] iommu/vt-d: Detect pre enabled translation

From: Joerg Roedel <[email protected]>

Add code to detect whether translation is already enabled in
the IOMMU. Save this state in a flags field added to
struct intel_iommu.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 19 +++++++++++++++++++
include/linux/intel-iommu.h | 4 ++++
2 files changed, 23 insertions(+)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index f82918e..467414b 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -435,6 +435,20 @@ static LIST_HEAD(device_domain_list);

static const struct iommu_ops intel_iommu_ops;

+static bool translation_pre_enabled(struct intel_iommu *iommu)
+{
+ return (iommu->flags & VTD_FLAG_TRANS_PRE_ENABLED);
+}
+
+static void init_translation_status(struct intel_iommu *iommu)
+{
+ u32 gsts;
+
+ gsts = readl(iommu->reg + DMAR_GSTS_REG);
+ if (gsts & DMA_GSTS_TES)
+ iommu->flags |= VTD_FLAG_TRANS_PRE_ENABLED;
+}
+
/* Convert generic 'struct iommu_domain to private struct dmar_domain */
static struct dmar_domain *to_dmar_domain(struct iommu_domain *dom)
{
@@ -2789,6 +2803,11 @@ static int __init init_dmars(void)
if (ret)
goto free_iommu;

+ init_translation_status(iommu);
+
+ if (translation_pre_enabled(iommu))
+ pr_info("Translation already enabled - trying to copy translation structures\n");
+
/*
* TBD:
* we could share the same root & context tables
diff --git a/include/linux/intel-iommu.h b/include/linux/intel-iommu.h
index 796ef96..fff87f1 100644
--- a/include/linux/intel-iommu.h
+++ b/include/linux/intel-iommu.h
@@ -319,6 +319,9 @@ enum {
MAX_SR_DMAR_REGS
};

+#define VTD_FLAG_TRANS_PRE_ENABLED (1 << 0)
+#define VTD_FLAG_IRQ_REMAP_PRE_ENABLED (1 << 1)
+
struct intel_iommu {
void __iomem *reg; /* Pointer to hardware regs, virtual addr */
u64 reg_phys; /* physical address of hw register set */
@@ -350,6 +353,7 @@ struct intel_iommu {
#endif
struct device *iommu_dev; /* IOMMU-sysfs device */
int node;
+ u32 flags; /* Software defined flags */
};

static inline void __iommu_flush_cache(
--
1.9.1

2015-06-13 06:50:35

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 05/19] iommu/vt-d: Copy translation tables from old kernel

From: Joerg Roedel <[email protected]>

If we are in a kdump kernel and find translation enabled in
the iommu, try to copy the translation tables from the old
kernel to preserve the mappings until the device driver
takes over.
This supports old and the extended root-entry and
context-table formats.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 207 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 205 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 467414b..a755f94 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -43,6 +43,7 @@
#include <linux/pci-ats.h>
#include <linux/memblock.h>
#include <linux/dma-contiguous.h>
+#include <linux/crash_dump.h>
#include <asm/irq_remapping.h>
#include <asm/cacheflush.h>
#include <asm/iommu.h>
@@ -193,7 +194,29 @@ struct root_entry {
};
#define ROOT_ENTRY_NR (VTD_PAGE_SIZE/sizeof(struct root_entry))

+/*
+ * Take a root_entry and return the Lower Context Table Pointer (LCTP)
+ * if marked present.
+ */
+static phys_addr_t root_entry_lctp(struct root_entry *re)
+{
+ if (!(re->lo & 1))
+ return 0;
+
+ return re->lo & VTD_PAGE_MASK;
+}
+
+/*
+ * Take a root_entry and return the Upper Context Table Pointer (UCTP)
+ * if marked present.
+ */
+static phys_addr_t root_entry_uctp(struct root_entry *re)
+{
+ if (!(re->hi & 1))
+ return 0;

+ return re->hi & VTD_PAGE_MASK;
+}
/*
* low 64 bits:
* 0: present
@@ -440,6 +463,11 @@ static bool translation_pre_enabled(struct intel_iommu *iommu)
return (iommu->flags & VTD_FLAG_TRANS_PRE_ENABLED);
}

+static void clear_translation_pre_enabled(struct intel_iommu *iommu)
+{
+ iommu->flags &= ~VTD_FLAG_TRANS_PRE_ENABLED;
+}
+
static void init_translation_status(struct intel_iommu *iommu)
{
u32 gsts;
@@ -2748,6 +2776,153 @@ static void intel_iommu_init_qi(struct intel_iommu *iommu)
}
}

+static int copy_context_table(struct intel_iommu *iommu,
+ struct root_entry *old_re,
+ struct context_entry **tbl,
+ int bus, bool ext)
+{
+ struct context_entry *old_ce = NULL, *new_ce = NULL, ce;
+ int tbl_idx, pos = 0, idx, devfn, ret = 0;
+ phys_addr_t old_ce_phys;
+
+ tbl_idx = ext ? bus * 2 : bus;
+
+ for (devfn = 0; devfn < 256; devfn++) {
+ /* First calculate the correct index */
+ idx = (ext ? devfn * 2 : devfn) % 256;
+
+ if (idx == 0) {
+ /* First save what we may have and clean up */
+ if (new_ce) {
+ tbl[tbl_idx] = new_ce;
+ __iommu_flush_cache(iommu, new_ce,
+ VTD_PAGE_SIZE);
+ pos = 1;
+ }
+
+ if (old_ce)
+ iounmap(old_ce);
+
+ ret = 0;
+ if (devfn < 0x80)
+ old_ce_phys = root_entry_lctp(old_re);
+ else
+ old_ce_phys = root_entry_uctp(old_re);
+
+ if (!old_ce_phys) {
+ if (ext && devfn == 0) {
+ /* No LCTP, try UCTP */
+ devfn = 0x7f;
+ continue;
+ } else {
+ goto out;
+ }
+ }
+
+ ret = -ENOMEM;
+ old_ce = ioremap_cache(old_ce_phys, PAGE_SIZE);
+ if (!old_ce)
+ goto out;
+
+ new_ce = alloc_pgtable_page(iommu->node);
+ if (!new_ce)
+ goto out_unmap;
+
+ ret = 0;
+ }
+
+ /* Now copy the context entry */
+ ce = old_ce[idx];
+
+ if (!context_present(&ce))
+ continue;
+
+ new_ce[idx] = ce;
+ }
+
+ tbl[tbl_idx + pos] = new_ce;
+
+ __iommu_flush_cache(iommu, new_ce, VTD_PAGE_SIZE);
+
+out_unmap:
+ iounmap(old_ce);
+
+out:
+ return ret;
+}
+
+static int copy_translation_tables(struct intel_iommu *iommu)
+{
+ struct context_entry **ctxt_tbls;
+ struct root_entry *old_rt;
+ phys_addr_t old_rt_phys;
+ int ctxt_table_entries;
+ unsigned long flags;
+ u64 rtaddr_reg;
+ int bus, ret;
+ bool ext;
+
+ rtaddr_reg = dmar_readq(iommu->reg + DMAR_RTADDR_REG);
+ ext = !!(rtaddr_reg & DMA_RTADDR_RTT);
+
+ old_rt_phys = rtaddr_reg & VTD_PAGE_MASK;
+ if (!old_rt_phys)
+ return -EINVAL;
+
+ old_rt = ioremap_cache(old_rt_phys, PAGE_SIZE);
+ if (!old_rt)
+ return -ENOMEM;
+
+ /* This is too big for the stack - allocate it from slab */
+ ctxt_table_entries = ext ? 512 : 256;
+ ret = -ENOMEM;
+ ctxt_tbls = kzalloc(ctxt_table_entries * sizeof(void *), GFP_KERNEL);
+ if (!ctxt_tbls)
+ goto out_unmap;
+
+ for (bus = 0; bus < 256; bus++) {
+ ret = copy_context_table(iommu, &old_rt[bus],
+ ctxt_tbls, bus, ext);
+ if (ret) {
+ pr_err("%s: Failed to copy context table for bus %d\n",
+ iommu->name, bus);
+ continue;
+ }
+ }
+
+ spin_lock_irqsave(&iommu->lock, flags);
+
+ /* Context tables are copied, now write them to the root_entry table */
+ for (bus = 0; bus < 256; bus++) {
+ int idx = ext ? bus * 2 : bus;
+ u64 val;
+
+ if (ctxt_tbls[idx]) {
+ val = virt_to_phys(ctxt_tbls[idx]) | 1;
+ iommu->root_entry[bus].lo = val;
+ }
+
+ if (!ext || !ctxt_tbls[idx + 1])
+ continue;
+
+ val = virt_to_phys(ctxt_tbls[idx + 1]) | 1;
+ iommu->root_entry[bus].hi = val;
+ }
+
+ spin_unlock_irqrestore(&iommu->lock, flags);
+
+ kfree(ctxt_tbls);
+
+ __iommu_flush_cache(iommu, iommu->root_entry, PAGE_SIZE);
+
+ ret = 0;
+
+out_unmap:
+ iounmap(old_rt);
+
+ return ret;
+}
+
static int __init init_dmars(void)
{
struct dmar_drhd_unit *drhd;
@@ -2805,8 +2980,12 @@ static int __init init_dmars(void)

init_translation_status(iommu);

- if (translation_pre_enabled(iommu))
- pr_info("Translation already enabled - trying to copy translation structures\n");
+ if (translation_pre_enabled(iommu) && !is_kdump_kernel()) {
+ iommu_disable_translation(iommu);
+ clear_translation_pre_enabled(iommu);
+ pr_warn("Translation was enabled for %s but we are not in kdump mode\n",
+ iommu->name);
+ }

/*
* TBD:
@@ -2817,6 +2996,30 @@ static int __init init_dmars(void)
if (ret)
goto free_iommu;

+ if (translation_pre_enabled(iommu)) {
+ pr_info("Translation already enabled - trying to copy translation structures\n");
+
+ ret = copy_translation_tables(iommu);
+ if (ret) {
+ /*
+ * We found the IOMMU with translation
+ * enabled - but failed to copy over the
+ * old root-entry table. Try to proceed
+ * by disabling translation now and
+ * allocating a clean root-entry table.
+ * This might cause DMAR faults, but
+ * probably the dump will still succeed.
+ */
+ pr_err("Failed to copy translation tables from previous kernel for %s\n",
+ iommu->name);
+ iommu_disable_translation(iommu);
+ clear_translation_pre_enabled(iommu);
+ } else {
+ pr_info("Copied translation tables from previous kernel for %s\n",
+ iommu->name);
+ }
+ }
+
iommu_flush_write_buffer(iommu);
iommu_set_root_entry(iommu);
iommu->flush.flush_context(iommu, 0, 0, 0, DMA_CCMD_GLOBAL_INVL);
--
1.9.1

2015-06-13 06:50:43

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 06/19] iommu/vt-d: Do not re-use domain-ids from the old kernel

From: Joerg Roedel <[email protected]>

Mark all domain-ids we find as reserved, so that there could
be no collision between domains from the previous kernel and
our domains in the IOMMU TLB.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index a755f94..1d56696 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -273,6 +273,11 @@ static inline void context_set_domain_id(struct context_entry *context,
context->hi |= (value & ((1 << 16) - 1)) << 8;
}

+static inline int context_domain_id(struct context_entry *c)
+{
+ return((c->hi >> 8) & 0xffff);
+}
+
static inline void context_clear_entry(struct context_entry *context)
{
context->lo = 0;
@@ -2782,7 +2787,7 @@ static int copy_context_table(struct intel_iommu *iommu,
int bus, bool ext)
{
struct context_entry *old_ce = NULL, *new_ce = NULL, ce;
- int tbl_idx, pos = 0, idx, devfn, ret = 0;
+ int tbl_idx, pos = 0, idx, devfn, ret = 0, did;
phys_addr_t old_ce_phys;

tbl_idx = ext ? bus * 2 : bus;
@@ -2837,6 +2842,10 @@ static int copy_context_table(struct intel_iommu *iommu,
if (!context_present(&ce))
continue;

+ did = context_domain_id(&ce);
+ if (did >= 0 && did < cap_ndoms(iommu->cap))
+ set_bit(did, iommu->domain_ids);
+
new_ce[idx] = ce;
}

--
1.9.1

2015-06-13 06:50:20

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 07/19] iommu/vt-d: Mark copied context entries

From: Joerg Roedel <[email protected]>

Mark the context entries we copied over from the old kernel,
so that we don't detect them as present in other code paths.
This makes sure we safely overwrite old context entries when
a new domain is assigned.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 53 +++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 51 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 1d56696..1fdff58 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -233,10 +233,38 @@ struct context_entry {
u64 hi;
};

-static inline bool context_present(struct context_entry *context)
+static inline void context_clear_pasid_enable(struct context_entry *context)
+{
+ context->lo &= ~(1ULL << 11);
+}
+
+static inline bool context_pasid_enabled(struct context_entry *context)
+{
+ return !!(context->lo & (1ULL << 11));
+}
+
+static inline void context_set_copied(struct context_entry *context)
+{
+ context->hi |= (1ull << 3);
+}
+
+static inline bool context_copied(struct context_entry *context)
+{
+ return !!(context->hi & (1ULL << 3));
+}
+
+static inline bool __context_present(struct context_entry *context)
{
return (context->lo & 1);
}
+
+static inline bool context_present(struct context_entry *context)
+{
+ return context_pasid_enabled(context) ?
+ __context_present(context) :
+ __context_present(context) && !context_copied(context);
+}
+
static inline void context_set_present(struct context_entry *context)
{
context->lo |= 1;
@@ -1841,6 +1869,8 @@ static int domain_context_mapping_one(struct dmar_domain *domain,
return 0;
}

+ context_clear_entry(context);
+
id = domain->id;
pgd = domain->pgd;

@@ -2839,13 +2869,32 @@ static int copy_context_table(struct intel_iommu *iommu,
/* Now copy the context entry */
ce = old_ce[idx];

- if (!context_present(&ce))
+ if (!__context_present(&ce))
continue;

did = context_domain_id(&ce);
if (did >= 0 && did < cap_ndoms(iommu->cap))
set_bit(did, iommu->domain_ids);

+ /*
+ * We need a marker for copied context entries. This
+ * marker needs to work for the old format as well as
+ * for extended context entries.
+ *
+ * Bit 67 of the context entry is used. In the old
+ * format this bit is available to software, in the
+ * extended format it is the PGE bit, but PGE is ignored
+ * by HW if PASIDs are disabled (and thus still
+ * available).
+ *
+ * So disable PASIDs first and then mark the entry
+ * copied. This means that we don't copy PASID
+ * translations from the old kernel, but this is fine as
+ * faults there are not fatal.
+ */
+ context_clear_pasid_enable(&ce);
+ context_set_copied(&ce);
+
new_ce[idx] = ce;
}

--
1.9.1

2015-06-13 06:50:27

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 08/19] iommu/vt-d: Allocate si_domain in init_dmars()

From: Joerg Roedel <[email protected]>

This seperates the allocation of the si_domain from its
assignment to devices. It makes sure that the iommu=pt case
still works in the kdump kernel, when we have to defer the
assignment of devices to domains to device driver
initialization time.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 1fdff58..bf87236 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -2743,10 +2743,6 @@ static int __init iommu_prepare_static_identity_mapping(int hw)
int i;
int ret = 0;

- ret = si_domain_init(hw);
- if (ret)
- return -EFAULT;
-
for_each_pci_dev(pdev) {
ret = dev_prepare_static_identity_mapping(&pdev->dev, hw);
if (ret)
@@ -2760,7 +2756,7 @@ static int __init iommu_prepare_static_identity_mapping(int hw)

if (dev->bus != &acpi_bus_type)
continue;
-
+
adev= to_acpi_device(dev);
mutex_lock(&adev->physical_node_lock);
list_for_each_entry(pn, &adev->physical_node_list, node) {
@@ -3094,6 +3090,12 @@ static int __init init_dmars(void)
iommu_identity_mapping |= IDENTMAP_GFX;
#endif

+ if (iommu_identity_mapping) {
+ ret = si_domain_init(hw_pass_through);
+ if (ret)
+ goto free_iommu;
+ }
+
check_tylersburg_isoch();

/*
--
1.9.1

2015-06-13 06:48:19

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 09/19] iommu/vt-d: Don't do early domain assignment if kdump kernel

From: Joerg Roedel <[email protected]>

When we copied over context tables from an old kernel, we
need to defer assignment of devices to domains until the
device driver takes over. So skip this part of
initialization when we copied over translation tables from
the old kernel.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index bf87236..39bc3d6 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -2981,6 +2981,7 @@ static int __init init_dmars(void)
{
struct dmar_drhd_unit *drhd;
struct dmar_rmrr_unit *rmrr;
+ bool copied_tables = false;
struct device *dev;
struct intel_iommu *iommu;
int i, ret;
@@ -3071,6 +3072,7 @@ static int __init init_dmars(void)
} else {
pr_info("Copied translation tables from previous kernel for %s\n",
iommu->name);
+ copied_tables = true;
}
}

@@ -3099,6 +3101,15 @@ static int __init init_dmars(void)
check_tylersburg_isoch();

/*
+ * If we copied translations from a previous kernel in the kdump
+ * case, we can not assign the devices to domains now, as that
+ * would eliminate the old mappings. So skip this part and defer
+ * the assignment to device driver initialization time.
+ */
+ if (copied_tables)
+ goto domains_done;
+
+ /*
* If pass through is not set or not enabled, setup context entries for
* identity mappings for rmrr, gfx, and isa and may fall back to static
* identity mapping if iommu_identity_mapping is set.
@@ -3137,6 +3148,8 @@ static int __init init_dmars(void)

iommu_prepare_isa();

+domains_done:
+
/*
* for each drhd
* enable fault log
--
1.9.1

2015-06-13 06:48:35

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 10/19] iommu/vt-d: Don't copy translation tables if RTT bit needs to be changed

From: Joerg Roedel <[email protected]>

We can't change the RTT bit when translation is enabled, so
don't copy translation tables when we would change the bit
with our new root entry.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index 39bc3d6..e23d5b4 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -2914,10 +2914,20 @@ static int copy_translation_tables(struct intel_iommu *iommu)
unsigned long flags;
u64 rtaddr_reg;
int bus, ret;
- bool ext;
+ bool new_ext, ext;

rtaddr_reg = dmar_readq(iommu->reg + DMAR_RTADDR_REG);
ext = !!(rtaddr_reg & DMA_RTADDR_RTT);
+ new_ext = !!ecap_ecs(iommu->ecap);
+
+ /*
+ * The RTT bit can only be changed when translation is disabled,
+ * but disabling translation means to open a window for data
+ * corruption. So bail out and don't copy anything if we would
+ * have to change the bit.
+ */
+ if (new_ext != ext)
+ return -EINVAL;

old_rt_phys = rtaddr_reg & VTD_PAGE_MASK;
if (!old_rt_phys)
--
1.9.1

2015-06-13 06:48:10

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 11/19] iommu/vt-d: Don't disable translation prior to OS handover

From: Joerg Roedel <[email protected]>

For all the copy-translation code to run, we have to keep
translation enabled in intel_iommu_init(). So remove the
code disabling it.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 7 -------
1 file changed, 7 deletions(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index e23d5b4..b47def6 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -4469,13 +4469,6 @@ int __init intel_iommu_init(void)
goto out_free_dmar;
}

- /*
- * Disable translation if already enabled prior to OS handover.
- */
- for_each_active_iommu(iommu, drhd)
- if (iommu->gcmd & DMA_GCMD_TE)
- iommu_disable_translation(iommu);
-
if (dmar_dev_scope_init() < 0) {
if (force_on)
panic("tboot: Failed to initialize DMAR device scope\n");
--
1.9.1

2015-06-13 06:48:47

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 12/19] iommu/vt-d: Enable Translation only if it was previously disabled

From: Joerg Roedel <[email protected]>

Do not touch the TE bit unless we know translation is
disabled.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel-iommu.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c
index b47def6..ad72c6d 100644
--- a/drivers/iommu/intel-iommu.c
+++ b/drivers/iommu/intel-iommu.c
@@ -3184,7 +3184,9 @@ domains_done:
if (ret)
goto free_iommu;

- iommu_enable_translation(iommu);
+ if (!translation_pre_enabled(iommu))
+ iommu_enable_translation(iommu);
+
iommu_disable_protect_mem_regions(iommu);
}

--
1.9.1

2015-06-13 06:50:08

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 13/19] iommu/vt-d: Move EIM detection to intel_prepare_irq_remapping

From: Joerg Roedel <[email protected]>

We need this to be detected already when we program the irq
remapping table pointer to hardware.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 47 +++++++++++++++++++------------------
1 file changed, 24 insertions(+), 23 deletions(-)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 3fe3fc7..12250f7 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -589,6 +589,7 @@ static int __init intel_prepare_irq_remapping(void)
{
struct dmar_drhd_unit *drhd;
struct intel_iommu *iommu;
+ int eim = 0;

if (irq_remap_broken) {
pr_warn("This system BIOS has enabled interrupt remapping\n"
@@ -616,6 +617,26 @@ static int __init intel_prepare_irq_remapping(void)
if (!ecap_ir_support(iommu->ecap))
goto error;

+ /* Detect remapping mode: lapic or x2apic */
+ if (x2apic_supported()) {
+ eim = !dmar_x2apic_optout();
+ if (!eim) {
+ pr_info("x2apic is disabled because BIOS sets x2apic opt out bit.");
+ pr_info("Use 'intremap=no_x2apic_optout' to override the BIOS setting.\n");
+ }
+ }
+
+ for_each_iommu(iommu, drhd) {
+ if (eim && !ecap_eim_support(iommu->ecap)) {
+ pr_info("%s does not support EIM\n", iommu->name);
+ eim = 0;
+ }
+ }
+
+ eim_mode = eim;
+ if (eim)
+ pr_info("Queued invalidation will be enabled to support x2apic and Intr-remapping.\n");
+
/* Do the allocations early */
for_each_iommu(iommu, drhd)
if (intel_setup_irq_remapping(iommu))
@@ -633,13 +654,6 @@ static int __init intel_enable_irq_remapping(void)
struct dmar_drhd_unit *drhd;
struct intel_iommu *iommu;
bool setup = false;
- int eim = 0;
-
- if (x2apic_supported()) {
- eim = !dmar_x2apic_optout();
- if (!eim)
- pr_info("x2apic is disabled because BIOS sets x2apic opt out bit. You can use 'intremap=no_x2apic_optout' to override the BIOS setting.\n");
- }

for_each_iommu(iommu, drhd) {
/*
@@ -664,19 +678,6 @@ static int __init intel_enable_irq_remapping(void)
}

/*
- * check for the Interrupt-remapping support
- */
- for_each_iommu(iommu, drhd)
- if (eim && !ecap_eim_support(iommu->ecap)) {
- pr_info("DRHD %Lx: EIM not supported by DRHD, "
- " ecap %Lx\n", drhd->reg_base_addr, iommu->ecap);
- eim = 0;
- }
- eim_mode = eim;
- if (eim)
- pr_info("Queued invalidation will be enabled to support x2apic and Intr-remapping.\n");
-
- /*
* Enable queued invalidation for all the DRHD's.
*/
for_each_iommu(iommu, drhd) {
@@ -694,7 +695,7 @@ static int __init intel_enable_irq_remapping(void)
* Setup Interrupt-remapping for all the DRHD's now.
*/
for_each_iommu(iommu, drhd) {
- iommu_set_irq_remapping(iommu, eim);
+ iommu_set_irq_remapping(iommu, eim_mode);
setup = true;
}

@@ -710,9 +711,9 @@ static int __init intel_enable_irq_remapping(void)
*/
x86_io_apic_ops.print_entries = intel_ir_io_apic_print_entries;

- pr_info("Enabled IRQ remapping in %s mode\n", eim ? "x2apic" : "xapic");
+ pr_info("Enabled IRQ remapping in %s mode\n", eim_mode ? "x2apic" : "xapic");

- return eim ? IRQ_REMAP_X2APIC_MODE : IRQ_REMAP_XAPIC_MODE;
+ return eim_mode ? IRQ_REMAP_X2APIC_MODE : IRQ_REMAP_XAPIC_MODE;

error:
intel_cleanup_irq_remapping();
--
1.9.1

2015-06-13 06:50:15

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 14/19] iommu/vt-d: Move QI initializationt to intel_setup_irq_remapping

From: Joerg Roedel <[email protected]>

QI needs to be enabled when we program the irq remapping
table to hardware in the prepare phase later.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 92 ++++++++++++++-----------------------
1 file changed, 35 insertions(+), 57 deletions(-)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 12250f7..46d17e1 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -507,12 +507,35 @@ static int intel_setup_irq_remapping(struct intel_iommu *iommu)
ir_table->base = page_address(pages);
ir_table->bitmap = bitmap;
iommu->ir_table = ir_table;
+
+ /*
+ * If the queued invalidation is already initialized,
+ * shouldn't disable it.
+ */
+ if (!iommu->qi) {
+ /*
+ * Clear previous faults.
+ */
+ dmar_fault(-1, iommu);
+ dmar_disable_qi(iommu);
+
+ if (dmar_enable_qi(iommu)) {
+ pr_err("Failed to enable queued invalidation\n");
+ goto out_free_bitmap;
+ }
+ }
+
return 0;

+out_free_bitmap:
+ kfree(bitmap);
out_free_pages:
__free_pages(pages, INTR_REMAP_PAGE_ORDER);
out_free_table:
kfree(ir_table);
+
+ iommu->ir_table = NULL;
+
return -ENOMEM;
}

@@ -637,10 +660,14 @@ static int __init intel_prepare_irq_remapping(void)
if (eim)
pr_info("Queued invalidation will be enabled to support x2apic and Intr-remapping.\n");

- /* Do the allocations early */
- for_each_iommu(iommu, drhd)
- if (intel_setup_irq_remapping(iommu))
+ /* Do the initializations early */
+ for_each_iommu(iommu, drhd) {
+ if (intel_setup_irq_remapping(iommu)) {
+ pr_err("Failed to setup irq remapping for %s\n",
+ iommu->name);
goto error;
+ }
+ }

return 0;

@@ -655,42 +682,9 @@ static int __init intel_enable_irq_remapping(void)
struct intel_iommu *iommu;
bool setup = false;

- for_each_iommu(iommu, drhd) {
- /*
- * If the queued invalidation is already initialized,
- * shouldn't disable it.
- */
- if (iommu->qi)
- continue;
-
- /*
- * Clear previous faults.
- */
- dmar_fault(-1, iommu);
-
- /*
- * Disable intr remapping and queued invalidation, if already
- * enabled prior to OS handover.
- */
+ for_each_iommu(iommu, drhd)
iommu_disable_irq_remapping(iommu);

- dmar_disable_qi(iommu);
- }
-
- /*
- * Enable queued invalidation for all the DRHD's.
- */
- for_each_iommu(iommu, drhd) {
- int ret = dmar_enable_qi(iommu);
-
- if (ret) {
- pr_err("DRHD %Lx: failed to enable queued, "
- " invalidation, ecap %Lx, ret %d\n",
- drhd->reg_base_addr, iommu->ecap, ret);
- goto error;
- }
- }
-
/*
* Setup Interrupt-remapping for all the DRHD's now.
*/
@@ -1242,28 +1236,12 @@ static int dmar_ir_add(struct dmar_drhd_unit *dmaru, struct intel_iommu *iommu)
/* Setup Interrupt-remapping now. */
ret = intel_setup_irq_remapping(iommu);
if (ret) {
- pr_err("DRHD %Lx: failed to allocate resource\n",
- iommu->reg_phys);
- ir_remove_ioapic_hpet_scope(iommu);
- return ret;
- }
-
- if (!iommu->qi) {
- /* Clear previous faults. */
- dmar_fault(-1, iommu);
- iommu_disable_irq_remapping(iommu);
- dmar_disable_qi(iommu);
- }
-
- /* Enable queued invalidation */
- ret = dmar_enable_qi(iommu);
- if (!ret) {
- iommu_set_irq_remapping(iommu, eim);
- } else {
- pr_err("DRHD %Lx: failed to enable queued invalidation, ecap %Lx, ret %d\n",
- iommu->reg_phys, iommu->ecap, ret);
+ pr_err("Failed to setup irq remapping for %s\n",
+ iommu->name);
intel_teardown_irq_remapping(iommu);
ir_remove_ioapic_hpet_scope(iommu);
+ } else {
+ iommu_set_irq_remapping(iommu, eim);
}

return ret;
--
1.9.1

2015-06-13 06:49:35

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 15/19] iommu/vt-d: Disable IRQ remapping in intel_prepare_irq_remapping

From: Joerg Roedel <[email protected]>

Move it to this function for now, so that the copy routines
for irq remapping take no effect yet.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 46d17e1..f1711836 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -654,6 +654,9 @@ static int __init intel_prepare_irq_remapping(void)
pr_info("%s does not support EIM\n", iommu->name);
eim = 0;
}
+
+ /* Disable IRQ remapping if it is already enabled */
+ iommu_disable_irq_remapping(iommu);
}

eim_mode = eim;
@@ -682,9 +685,6 @@ static int __init intel_enable_irq_remapping(void)
struct intel_iommu *iommu;
bool setup = false;

- for_each_iommu(iommu, drhd)
- iommu_disable_irq_remapping(iommu);
-
/*
* Setup Interrupt-remapping for all the DRHD's now.
*/
--
1.9.1

2015-06-13 06:49:54

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 16/19] iommu/vt-d: Set IRTA in intel_setup_irq_remapping

From: Joerg Roedel <[email protected]>

This way we can give the hardware the new IR table right
after it has been allocated and initialized.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index f1711836..8497028 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -428,9 +428,9 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev)

static void iommu_set_irq_remapping(struct intel_iommu *iommu, int mode)
{
+ unsigned long flags;
u64 addr;
u32 sts;
- unsigned long flags;

addr = virt_to_phys((void *)iommu->ir_table->base);

@@ -447,10 +447,16 @@ static void iommu_set_irq_remapping(struct intel_iommu *iommu, int mode)
raw_spin_unlock_irqrestore(&iommu->register_lock, flags);

/*
- * global invalidation of interrupt entry cache before enabling
- * interrupt-remapping.
+ * Global invalidation of interrupt entry cache to make sure the
+ * hardware uses the new irq remapping table.
*/
qi_global_iec(iommu);
+}
+
+static void iommu_enable_irq_remapping(struct intel_iommu *iommu)
+{
+ unsigned long flags;
+ u32 sts;

raw_spin_lock_irqsave(&iommu->register_lock, flags);

@@ -525,6 +531,8 @@ static int intel_setup_irq_remapping(struct intel_iommu *iommu)
}
}

+ iommu_set_irq_remapping(iommu, eim_mode);
+
return 0;

out_free_bitmap:
@@ -689,7 +697,7 @@ static int __init intel_enable_irq_remapping(void)
* Setup Interrupt-remapping for all the DRHD's now.
*/
for_each_iommu(iommu, drhd) {
- iommu_set_irq_remapping(iommu, eim_mode);
+ iommu_enable_irq_remapping(iommu);
setup = true;
}

@@ -926,6 +934,7 @@ static int reenable_irq_remapping(int eim)

/* Set up interrupt remapping for iommu.*/
iommu_set_irq_remapping(iommu, eim);
+ iommu_enable_irq_remapping(iommu);
setup = true;
}

@@ -1241,7 +1250,7 @@ static int dmar_ir_add(struct dmar_drhd_unit *dmaru, struct intel_iommu *iommu)
intel_teardown_irq_remapping(iommu);
ir_remove_ioapic_hpet_scope(iommu);
} else {
- iommu_set_irq_remapping(iommu, eim);
+ iommu_enable_irq_remapping(iommu);
}

return ret;
--
1.9.1

2015-06-13 06:49:45

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 17/19] iommu/vt-d: Copy IR table from old kernel when in kdump mode

From: Joerg Roedel <[email protected]>

When we are booting into a kdump kernel and find IR enabled,
copy over the contents of the previous IR table so that
spurious interrupts will not be target aborted.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 70 +++++++++++++++++++++++++++++++++++++
include/linux/intel-iommu.h | 1 +
2 files changed, 71 insertions(+)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 8497028..2a90121 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -11,6 +11,7 @@
#include <linux/irq.h>
#include <linux/intel-iommu.h>
#include <linux/acpi.h>
+#include <linux/crash_dump.h>
#include <asm/io_apic.h>
#include <asm/smp.h>
#include <asm/cpu.h>
@@ -54,8 +55,28 @@ static struct hpet_scope ir_hpet[MAX_HPET_TBS];
*/
static DEFINE_RAW_SPINLOCK(irq_2_ir_lock);

+static void iommu_disable_irq_remapping(struct intel_iommu *iommu);
static int __init parse_ioapics_under_ir(void);

+static bool ir_pre_enabled(struct intel_iommu *iommu)
+{
+ return (iommu->flags & VTD_FLAG_IRQ_REMAP_PRE_ENABLED);
+}
+
+static void clear_ir_pre_enabled(struct intel_iommu *iommu)
+{
+ iommu->flags &= ~VTD_FLAG_IRQ_REMAP_PRE_ENABLED;
+}
+
+static void init_ir_status(struct intel_iommu *iommu)
+{
+ u32 gsts;
+
+ gsts = readl(iommu->reg + DMAR_GSTS_REG);
+ if (gsts & DMA_GSTS_IRES)
+ iommu->flags |= VTD_FLAG_IRQ_REMAP_PRE_ENABLED;
+}
+
static struct irq_2_iommu *irq_2_iommu(unsigned int irq)
{
struct irq_cfg *cfg = irq_cfg(irq);
@@ -426,6 +447,44 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev)
return 0;
}

+static int iommu_load_old_irte(struct intel_iommu *iommu)
+{
+ struct irte *old_ir_table;
+ phys_addr_t irt_phys;
+ size_t size;
+ u64 irta;
+
+ if (!is_kdump_kernel()) {
+ pr_warn("IRQ remapping was enabled on %s but we are not in kdump mode\n",
+ iommu->name);
+ clear_ir_pre_enabled(iommu);
+ iommu_disable_irq_remapping(iommu);
+ return -EINVAL;
+ }
+
+ /* Check whether the old ir-table has the same size as ours */
+ irta = dmar_readq(iommu->reg + DMAR_IRTA_REG);
+ if ((irta & INTR_REMAP_TABLE_REG_SIZE_MASK)
+ != INTR_REMAP_TABLE_REG_SIZE)
+ return -EINVAL;
+
+ irt_phys = irta & VTD_PAGE_MASK;
+ size = INTR_REMAP_TABLE_ENTRIES*sizeof(struct irte);
+
+ /* Map the old IR table */
+ old_ir_table = ioremap_cache(irt_phys, size);
+ if (!old_ir_table)
+ return -ENOMEM;
+
+ /* Copy data over */
+ memcpy(iommu->ir_table->base, old_ir_table, size);
+
+ __iommu_flush_cache(iommu, iommu->ir_table->base, size);
+
+ return 0;
+}
+
+
static void iommu_set_irq_remapping(struct intel_iommu *iommu, int mode)
{
unsigned long flags;
@@ -531,6 +590,17 @@ static int intel_setup_irq_remapping(struct intel_iommu *iommu)
}
}

+ init_ir_status(iommu);
+
+ if (ir_pre_enabled(iommu)) {
+ if (iommu_load_old_irte(iommu))
+ pr_err("Failed to copy IR table for %s from previous kernel\n",
+ iommu->name);
+ else
+ pr_info("Copied IR table for %s from previous kernel\n",
+ iommu->name);
+ }
+
iommu_set_irq_remapping(iommu, eim_mode);

return 0;
diff --git a/include/linux/intel-iommu.h b/include/linux/intel-iommu.h
index fff87f1..9aedf21 100644
--- a/include/linux/intel-iommu.h
+++ b/include/linux/intel-iommu.h
@@ -295,6 +295,7 @@ struct q_inval {
/* 1MB - maximum possible interrupt remapping table size */
#define INTR_REMAP_PAGE_ORDER 8
#define INTR_REMAP_TABLE_REG_SIZE 0xf
+#define INTR_REMAP_TABLE_REG_SIZE_MASK 0xf

#define INTR_REMAP_TABLE_ENTRIES 65536

--
1.9.1

2015-06-13 06:49:23

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 18/19] iommu/vt-d: Make sure copied over IR entries are not reused

From: Joerg Roedel <[email protected]>

Walk over the copied entries and mark the present ones as
allocated.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 10 ++++++++++
1 file changed, 10 insertions(+)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 2a90121..14e10de 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -451,6 +451,7 @@ static int iommu_load_old_irte(struct intel_iommu *iommu)
{
struct irte *old_ir_table;
phys_addr_t irt_phys;
+ unsigned int i;
size_t size;
u64 irta;

@@ -481,6 +482,15 @@ static int iommu_load_old_irte(struct intel_iommu *iommu)

__iommu_flush_cache(iommu, iommu->ir_table->base, size);

+ /*
+ * Now check the table for used entries and mark those as
+ * allocated in the bitmap
+ */
+ for (i = 0; i < INTR_REMAP_TABLE_ENTRIES; i++) {
+ if (iommu->ir_table->base[i].present)
+ bitmap_set(iommu->ir_table->bitmap, i, 1);
+ }
+
return 0;
}

--
1.9.1

2015-06-13 06:49:12

by Joerg Roedel

[permalink] [raw]
Subject: [PATCH 19/19] iommu/vt-d: Don't disable IR when it was previously enabled

From: Joerg Roedel <[email protected]>

Keep it enabled in kdump kernel to guarantee interrupt
delivery.

Signed-off-by: Joerg Roedel <[email protected]>
---
drivers/iommu/intel_irq_remapping.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c
index 14e10de..47fcebf 100644
--- a/drivers/iommu/intel_irq_remapping.c
+++ b/drivers/iommu/intel_irq_remapping.c
@@ -742,9 +742,6 @@ static int __init intel_prepare_irq_remapping(void)
pr_info("%s does not support EIM\n", iommu->name);
eim = 0;
}
-
- /* Disable IRQ remapping if it is already enabled */
- iommu_disable_irq_remapping(iommu);
}

eim_mode = eim;
@@ -777,7 +774,8 @@ static int __init intel_enable_irq_remapping(void)
* Setup Interrupt-remapping for all the DRHD's now.
*/
for_each_iommu(iommu, drhd) {
- iommu_enable_irq_remapping(iommu);
+ if (!ir_pre_enabled(iommu))
+ iommu_enable_irq_remapping(iommu);
setup = true;
}

--
1.9.1

2015-06-23 13:31:42

by David Woodhouse

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

On Sat, 2015-06-13 at 08:47 +0200, Joerg Roedel wrote:
> Hi,
>
> as David Woodhouse pointed out, my fixes and cleanups for
> the original patch-set turned out to be a complete rewrite.
> So to have a cleaner history of the feature and to make
> backporting easier, here is a rewrite of my changes based on
> v4.1-rc7.
>
> Some additional issues have been fixed by this rewrite, like
> a kdump-kernel boot panic with 'iommu=pt' and support for
> copying the extended root-entry and context table formats
> has been added.

This looks much better than before; thanks.

However, it's still fairly gratuitous for all non-broken hardware, and
will tend to hide hardware and driver bugs during testing of new
hardware.

I'd much rather see this limited to a blacklist of known-broken
devices, an accompanied by a kernel message along the lines of

'Preserving VT-d page tables for broken HP device xxxx:xxxx'

For *any* device which isn't so broken that it craps itself on taking a
DMA fault and cannot be reset, this page table copy shouldn't be
needed, right?


--
David Woodhouse Open Source Technology Centre
[email protected] Intel Corporation


Attachments:
smime.p7s (5.56 kB)

2015-06-23 14:06:44

by Joerg Roedel

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

On Tue, Jun 23, 2015 at 02:31:30PM +0100, David Woodhouse wrote:
> However, it's still fairly gratuitous for all non-broken hardware, and
> will tend to hide hardware and driver bugs during testing of new
> hardware.
>
> I'd much rather see this limited to a blacklist of known-broken
> devices, an accompanied by a kernel message along the lines of
>
> 'Preserving VT-d page tables for broken HP device xxxx:xxxx'
>
> For *any* device which isn't so broken that it craps itself on taking
> a DMA fault and cannot be reset, this page table copy shouldn't be
> needed, right?

In theory yes, but as it came to my mind recently, there is this BIOS
"value-add" called APEI (ACPI Platform Error Interface) which has a
'Firmware first' mode.

So when this is active the firmware handles any errors happening in the
system and reports them to the OS with a severity it can decide on its
own.

Such errors could be DMA target aborts, for example. And I have seen
systems where at least rejected interrupt requests were reported to the
OS as fatal errors, causing a kernel panic in Linux. But the firmware is
also free to report ordinary DMA failures as fatal errors, who knows...

So while you are right that these changes might hide hardware and driver
bugs, I think it is still the best to try avoiding such faults at all
costs in the kdump kernel to actually get a dump, even if the device
would actually be able to recover from the master abort.



Joerg

2015-06-23 14:39:04

by David Woodhouse

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

On Tue, 2015-06-23 at 16:06 +0200, Joerg Roedel wrote:
> On Tue, Jun 23, 2015 at 02:31:30PM +0100, David Woodhouse wrote:
> > However, it's still fairly gratuitous for all non-broken hardware, and
> > will tend to hide hardware and driver bugs during testing of new
> > hardware.
> >
> > I'd much rather see this limited to a blacklist of known-broken
> > devices, an accompanied by a kernel message along the lines of
> >
> > 'Preserving VT-d page tables for broken HP device xxxx:xxxx'
> >
> > For *any* device which isn't so broken that it craps itself on taking
> > a DMA fault and cannot be reset, this page table copy shouldn't be
> > needed, right?
>
> In theory yes, but as it came to my mind recently, there is this BIOS
> "value-add" called APEI (ACPI Platform Error Interface) which has a
> 'Firmware first' mode.
>
> So when this is active the firmware handles any errors happening in the
> system and reports them to the OS with a severity it can decide on its
> own.
>
> Such errors could be DMA target aborts, for example. And I have seen
> systems where at least rejected interrupt requests were reported to the
> OS as fatal errors, causing a kernel panic in Linux. But the firmware is
> also free to report ordinary DMA failures as fatal errors, who knows...

Yay for BIOS value subtract.

The thing is, this would be utterly broken. The IOMMU is supposed to
protect us from rogue devices. In this hypothetical scenario, a device
can bring the entire system down and we have no chance to isolate it
and recover. It means that assigning devices to guests should be
*disallowed* because it can't be done securely.

On this kind of system, we might as well turn off the IOMMU entirely as
in a lot of respects, it's only making things *worse*.

> So while you are right that these changes might hide hardware and driver
> bugs, I think it is still the best to try avoiding such faults at all
> costs in the kdump kernel to actually get a dump, even if the device
> would actually be able to recover from the master abort.

How about an *option* to do it for all devices (which in turn can
perhaps be triggered by a system-level blacklist for things like APEI,
or perhaps just a system DMI match on "HP").

--
dwmw2


Attachments:
smime.p7s (5.56 kB)

2015-06-25 06:37:32

by Li, ZhenHua

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

On 06/23/2015 10:38 PM, David Woodhouse wrote:
> On Tue, 2015-06-23 at 16:06 +0200, Joerg Roedel wrote:
>> On Tue, Jun 23, 2015 at 02:31:30PM +0100, David Woodhouse wrote:
>>> However, it's still fairly gratuitous for all non-broken hardware, and
>>> will tend to hide hardware and driver bugs during testing of new
>>> hardware.
>>>
>>> I'd much rather see this limited to a blacklist of known-broken
>>> devices, an accompanied by a kernel message along the lines of
>>>
>>> 'Preserving VT-d page tables for broken HP device xxxx:xxxx'
>>>
>>> For *any* device which isn't so broken that it craps itself on taking
>>> a DMA fault and cannot be reset, this page table copy shouldn't be
>>> needed, right?
>>
>> In theory yes, but as it came to my mind recently, there is this BIOS
>> "value-add" called APEI (ACPI Platform Error Interface) which has a
>> 'Firmware first' mode.
>>
>> So when this is active the firmware handles any errors happening in the
>> system and reports them to the OS with a severity it can decide on its
>> own.
>>
>> Such errors could be DMA target aborts, for example. And I have seen
>> systems where at least rejected interrupt requests were reported to the
>> OS as fatal errors, causing a kernel panic in Linux. But the firmware is
>> also free to report ordinary DMA failures as fatal errors, who knows...
>
> Yay for BIOS value subtract.
>
> The thing is, this would be utterly broken. The IOMMU is supposed to
> protect us from rogue devices. In this hypothetical scenario, a device
> can bring the entire system down and we have no chance to isolate it
> and recover. It means that assigning devices to guests should be
> *disallowed* because it can't be done securely.
>
> On this kind of system, we might as well turn off the IOMMU entirely as
> in a lot of respects, it's only making things *worse*.
>
>> So while you are right that these changes might hide hardware and driver
>> bugs, I think it is still the best to try avoiding such faults at all
>> costs in the kdump kernel to actually get a dump, even if the device
>> would actually be able to recover from the master abort.
>
> How about an *option* to do it for all devices (which in turn can
> perhaps be triggered by a system-level blacklist for things like APEI,
> or perhaps just a system DMI match on "HP").
>
Hi David,
It is a bad idea to check the DMI match on "HP". Though I have not see
any similar problems on other systems, I believe there are. Also not
all HP systems have such problem.
I agree with a blacklist for devices.

Thanks
Zhenhua

2015-06-25 06:41:59

by Li, ZhenHua

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

Hi all,
I see Joerg has backported it to sles12 in the commoent of novel
bugzilla 856382, so I will only backport it to redhat el.

Thanks
Zhenhua

On 06/13/2015 02:47 PM, Joerg Roedel wrote:
> Hi,
>
> as David Woodhouse pointed out, my fixes and cleanups for
> the original patch-set turned out to be a complete rewrite.
> So to have a cleaner history of the feature and to make
> backporting easier, here is a rewrite of my changes based on
> v4.1-rc7.
>
> Some additional issues have been fixed by this rewrite, like
> a kdump-kernel boot panic with 'iommu=pt' and support for
> copying the extended root-entry and context table formats
> has been added.
>
> I plan to rebuild the x86/vt-d branch of the iommu tree with
> these patches.
>
> Regards,
>
> Joerg
>
> Joerg Roedel (19):
> iommu/vt-d: Cleanup log messages
> iommu/vt-d: Init QI before root entry is allocated
> iommu/vt-d: Make root entry visible for hardware right after
> allocation
> iommu/vt-d: Detect pre enabled translation
> iommu/vt-d: Copy translation tables from old kernel
> iommu/vt-d: Do not re-use domain-ids from the old kernel
> iommu/vt-d: Mark copied context entries
> iommu/vt-d: Allocate si_domain in init_dmars()
> iommu/vt-d: Don't do early domain assignment if kdump kernel
> iommu/vt-d: Don't copy translation tables if RTT bit needs to be
> changed
> iommu/vt-d: Don't disable translation prior to OS handover
> iommu/vt-d: Enable Translation only if it was previously disabled
> iommu/vt-d: Move EIM detection to intel_prepare_irq_remapping
> iommu/vt-d: Move QI initializationt to intel_setup_irq_remapping
> iommu/vt-d: Disable IRQ remapping in intel_prepare_irq_remapping
> iommu/vt-d: Set IRTA in intel_setup_irq_remapping
> iommu/vt-d: Copy IR table from old kernel when in kdump mode
> iommu/vt-d: Make sure copied over IR entries are not reused
> iommu/vt-d: Don't disable IR when it was previously enabled
>
> drivers/iommu/dmar.c | 28 +-
> drivers/iommu/intel-iommu.c | 495 ++++++++++++++++++++++++++++--------
> drivers/iommu/intel_irq_remapping.c | 255 ++++++++++++-------
> include/linux/intel-iommu.h | 5 +
> 4 files changed, 573 insertions(+), 210 deletions(-)
>

2015-06-25 08:06:59

by David Woodhouse

[permalink] [raw]
Subject: Re: [PATCH 00/19] Fix Intel IOMMU breakage in kdump kernel

On Thu, 2015-06-25 at 14:35 +0800, Li, ZhenHua wrote:
> Hi David,
> It is a bad idea to check the DMI match on "HP". Though I have not see
> any similar problems on other systems, I believe there are. Also not
> all HP systems have such problem.

Yeah, the HP suggestion was a little tongue-in-cheek. Although it does
seem to be HP who is the main offender in this area — both the BIOS
"value subtract" and the habit of doing utterly insane things with the
IOMMU.

> I agree with a blacklist for devices.

Do you have a list of the problematic ones? I think a blacklist *and*
an option to enable it for all devices might be the best solution.

--
dwmw2