Fix crashdump failure caused by legacy DMA/IO
Subsystems (4)
cover Fix crashdump failure caused by legacy DMA/IO
The following series implements a fix for:
A kdump problem about DMA that has been discussed for a long time. That is, when a kernel panics and boots into the kdump kernel, DMA that was started by the panicked kernel is not stopped before the kdump kernel is booted; and the kdump kernel disables the IOMMU while this DMA continues.
This causes the IOMMU to stop translating the DMA addresses as IOVAs and begin to treat them as physical memory addresses -- which causes the DMA to either:
1. generate DMAR errors or
2. generate PCI SERR errors or
3. transfer data to or from incorrect areas of memory.
Often this causes the dump to fail.
This patch set modifies the behavior of the Intel iommu in the crashdump kernel:
1. to accept the iommu hardware in an active state,
2. to leave the current translations in-place so that legacy DMA will continue using its current buffers until the device drivers in the crashdump kernel initialize and initialize their devices,
3. to use different portions of the iova address ranges for the device drivers in the crashdump kernel than the iova ranges that were in-use at the time of the panic.
Advantages of this approach:
1. All manipulation of the IO-device is done by the Linux device-driver for that device.
2. This approach behaves in a manner very similar to operation without an active iommu.
3. Any activity between the IO-device and its RMRR areas is handled by the device-driver in the same manner as during a non-kdump boot.
4. If an IO-device has no driver in the kdump kernel, it is simply left alone. This supports the practice of creating a special kdump kernel without drivers for any devices that are not required for taking a crashdump.
5. Minimal code-changes among the existing mainline intel-iommu code.
Summary of changes in this patch set since the previous posting:
1. Returned to the structure of a patch-set
2. Enabled the intel-iommu driver to consist of multiple *.c files by moving many of the #defines, prototypes, and inline functions into a new file: intel-iommu-private.h (First three patches implement only this enhancement -- could be applied independent of the last 5)
3. Moved the new "crashdump fix" code into a new file: intel-iommu-kdump.c
4. Removed the pr_debug constructs from the new code that implements the "crashdump fix" -- making the code much cleaner and easier to read.
5. Miscellaneous cleanups such as enum-values for return-codes.
6. Simplified the code that retrieves the values needed to initialize a new domain by using the linked-list of previously-collected values instead of stepping back into the tree of translation tables.
Bill Sumner (8):
Fix a few existing lines that checkpatch.pl will complain about
later. #86: FILE: drivers/iommu/intel-iommu.c:390: +static
int force_on = 0;
Consolidate all .h lines at front of intel-iommu.c file In
intel-iommu.c, move downward the few lines near the front that
should not move to an intel-iommu-private.h file (mostly
data-item definitions) This leaves a consolidated block of the
lines that would move to an intel-iommu-private.h file at the
front of the file.
Create intel-iommu-private.h Move the single block of #define,
static inline ... ; struct definitions to intel-iommu-private.h
from intel-iommu.c Replace them with #include
"intel-iommu-private.h"
Update iommu_attach_domain() and its callers Allow specification
of domain-id for the new domain.
Add new definitions & prototypes to intel-iommu-private.h Items
needed for fixing crashdump.
Create intel-iommu-kdump.c Populate with "Copy iommu translation
tables" function set. Edit Makefile to add intel-iommu-kdump.o
Add domain-id functions to intel-iommu-kdump.c
intel_iommu_did_to_domain_values_entry() intel_iommu_get_dids_from_old_kernel()
Add remaining changes to intel-iommu.c to fix crashdump Add
"#include <linux/crash_dump.h>" Chg device_to_domain_id Chg
get_domain_for_dev Chg init_dmars Chg intel_iommu_init
drivers/iommu/Makefile | 2 +- drivers/iommu/intel-iommu-kdump.c | 629 ++++++++++++++++++++++++++++++++++++ drivers/iommu/intel-iommu-private.h | 443 +++++++++++++++++++++++++ drivers/iommu/intel-iommu.c | 565 ++++++++++---------------------- 4 files changed, 1249 insertions(+), 390 deletions(-) create mode 100644 drivers/iommu/intel-iommu-kdump.c create mode 100644 drivers/iommu/intel-iommu-private.h
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 1/8 Fix a few existing lines that checkpatch.pl will complain about later.
Things like:
1. no space before tabs
2. no initialization of static variables to 0 or NULL
3. no extra parentheses around the value on the 'return' statement
4. no line over 80-characters
--- drivers/iommu/intel-iommu.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-)
drivers/iommu/intel-iommu.c +7 -7
@@ -72,7 +72,7 @@to match. That way, we can use 'unsigned long' for PFNs with impunity. */ #define DOMAIN_MAX_PFN (gaw) ((unsigned long) min_t(uint64_t, \ __DOMAIN_MAX_PFN(gaw), (unsigned long)-1)) -#define DOMAIN_MAX_ADDR (gaw) (((uint64_t)__DOMAIN_MAX_PFN(gaw)) << VTD_PAGE_SHIFT) +#define DOMAIN_MAX_ADDR (gaw) (((uint64_t)__DOMAIN_MAX_PFN(gaw)) << VTD_PAGE_SHIFT)#define IOVA_PFN (addr) ((addr) >> PAGE_SHIFT)#define DMA_32BIT_PFN IOVA_PFN(DMA_BIT_MASK(32))
@@ -175,7 +175,7 @@ static int rwbf_quirk;* set to 1 to panic kernel if can't successfully enable VT-d * (used when kernel is launched w/ TXT) */ -static int force_on =0 ; +static int force_on;/* * 0: Present
@@ -190,7 +190,7 @@ struct root_entry {#define ROOT_ENTRY_NR (VTD_PAGE_SIZE/sizeof(struct root_entry))static inline bool root_present (struct root_entry *root) { -return (root->val &1 ); +return root->val &1 ; }static inline void set_root_present (struct root_entry *root) {
@@ -228,7 +228,7 @@ struct context_entry {static inline bool context_present (struct context_entry *context) { -return (context->lo &1 ); +return context->lo &1 ; }static inline void context_set_present (struct context_entry *context) {
@@ -306,7 +306,7 @@ static inline bool dma_pte_present(struct dma_pte *pte)static inline bool dma_pte_superpage (struct dma_pte *pte) { -return (pte->val & (1 <<7 )); +return pte->val & (1 <<7 ); }static inline int first_pte_in_page (struct dma_pte *pte)
@@ -317,7 +317,7 @@ static inline int first_pte_in_page(struct dma_pte *pte)/* * This domain is a statically identity mapping domain. * 1. This domain creats a static 1:1 mapping to all usable memory. -* 2. It maps to each iommu if successful. +* 2. It maps to each iommu if successful. * 3. Each iommu mapps to this domain if successful. */ static struct dmar_domain *si_domain;
@@ -347,7 +347,7 @@ struct dmar_domain {DECLARE_BITMAP (iommu_bmp,IOMMU_UNITS_SUPPORTED );/* bitmap of iommus this domain uses*/ -struct list_head devices;/* all devices' list */ +struct list_head devices;/* all devices' list */ struct iova_domain iovad;/* iova's that belong to this domain */ struct dma_pte *pgd;/* virtual address */
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 2/8 Consolidate all .h lines at front of intel-iommu.c file
In reply to Bill Sumner In intel-iommu.c, move downward the few lines near the front that should not move to an intel-iommu-private.h file (mostly data-item definitions) This leaves a consolidated block of the lines that would move to an intel-iommu-private.h file at the front of the file.
--- drivers/iommu/intel-iommu.c | 74 +++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 36 deletions(-)
drivers/iommu/intel-iommu.c +38 -36
@@ -165,17 +165,6 @@ static inline unsigned long virt_to_dma_pfn(void *p)return page_to_dma_pfn (virt_to_page (p)); } -/* global iommu list, set NULL for ignored DMAR units */ -static struct intel_iommu **g_iommus; - -static void __init check_tylersburg_isoch (void ); -static int rwbf_quirk; - -/* -* set to 1 to panic kernel if can't successfully enable VT-d -* (used when kernel is launched w/ TXT) -*/ -static int force_on;/* * 0: Present
@@ -314,15 +303,6 @@ static inline int first_pte_in_page(struct dma_pte *pte)return !((unsigned long )pte & ~VTD_PAGE_MASK ); } -/* -* This domain is a statically identity mapping domain. -* 1. This domain creats a static 1:1 mapping to all usable memory. -* 2. It maps to each iommu if successful. -* 3. Each iommu mapps to this domain if successful. -*/ -static struct dmar_domain *si_domain; -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)
@@ -380,6 +360,44 @@ struct device_domain_info {struct dmar_domain *domain;/* pointer to domain */ }; +static inline void *alloc_pgtable_page (int node) +{ +struct page *page; +void *vaddr =NULL ; + + page =alloc_pages_node (node,GFP_ATOMIC | __GFP_ZERO,0 ); +if (page) + vaddr =page_address (page); +return vaddr; +} + +static inline void free_pgtable_page (void *vaddr) +{ +free_page ((unsigned long )vaddr); +} + + +static void __init check_tylersburg_isoch (void ); + +/* global iommu list, set NULL for ignored DMAR units */ +static struct intel_iommu **g_iommus; +static int rwbf_quirk; + +/* +* set to 1 to panic kernel if can't successfully enable VT-d +* (used when kernel is launched w/ TXT) +*/ +static int force_on; + +/* +* This domain is a statically identity mapping domain. +* 1. This domain creats a static 1:1 mapping to all usable memory. +* 2. It maps to each iommu if successful. +* 3. Each iommu mapps to this domain if successful. +*/ +static struct dmar_domain *si_domain; +static int hw_pass_through =1 ; +static void flush_unmaps_timeout (unsigned long data); static DEFINE_TIMER(unmap_timer, flush_unmaps_timeout, 0, 0);
@@ -468,22 +486,6 @@ static struct kmem_cache *iommu_domain_cache;static struct kmem_cache *iommu_devinfo_cache;static struct kmem_cache *iommu_iova_cache; -static inline void *alloc_pgtable_page (int node) -{ -struct page *page; -void *vaddr =NULL ; - - page =alloc_pages_node (node,GFP_ATOMIC | __GFP_ZERO,0 ); -if (page) - vaddr =page_address (page); -return vaddr; -} - -static inline void free_pgtable_page (void *vaddr) -{ -free_page ((unsigned long )vaddr); -} -static inline void *alloc_domain_mem (void ) {return kmem_cache_alloc (iommu_domain_cache,GFP_ATOMIC );
Signature · 1 line
Bill Sumner <[email protected]>
Re: [PATCH 2/8] Consolidate all .h lines at front of intel-iommu.c file
Hi Bill,
Could you rebase on latest linus's kernel tree, since there are several changes in intel-iommu.c. This patch can't be applied because of below commits. I think patch need be reabsed on latest linus's tree before post, people will apply your patch without conflict.
commit b94e4117f8c4ffb591b1e462364d725e3a1c63c4
Author: Jiang Liu <jiang.liu-VuQAYsv1563Yd54FQh9/[email protected]> Date: Wed Feb 19 14:07:25 2014 +0800
iommu/vt-d: Move private structures and variables into intel-iommu.c
Move private structures and variables into intel-iommu.c, which will help to simplify locking policy for hotplug. Also delete redundant declarations.
Thanks
Baoquan
Quoted 121 lines · Bill Sumner
In
intel-iommu.c, move downward the few lines near the front that should not move to anintel-iommu-private.hfile (mostly data-item definitions) This leaves a consolidated block of the lines that would move to anintel-iommu-private.hfile at the front of the file.Signed-off-by Bill Sumner <[email protected]>--- drivers/iommu/intel-iommu.c | 74 +++++++++++++++++++++++---------------------- 1 file changed, 38 insertions(+), 36 deletions(-)drivers/iommu/intel-iommu.c +38
-36@@ -165,17 +165,6 @@ static inline unsigned long virt_to_dma_pfn(void *p)return page_to_dma_pfn (virt_to_page (p)); } -/* global iommu list, set NULL for ignored DMAR units */ -static struct intel_iommu **g_iommus; - -static void __init check_tylersburg_isoch (void ); -static int rwbf_quirk; - -/* -* set to 1 to panic kernel if can't successfully enable VT-d -* (used when kernel is launched w/ TXT) -*/ -static int force_on;/* * 0: Present @@ -314,15 +303,6 @@ static inline int first_pte_in_page(struct dma_pte *pte)return !((unsigned long )pte & ~VTD_PAGE_MASK ); } -/* -* This domain is a statically identity mapping domain. -* 1. This domain creats a static 1:1 mapping to all usable memory. -* 2. It maps to each iommu if successful. -* 3. Each iommu mapps to this domain if successful. -*/ -static struct dmar_domain *si_domain; -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)@@ -380,6 +360,44 @@ struct device_domain_info {struct dmar_domain *domain;/* pointer to domain */ }; +static inline void *alloc_pgtable_page (int node) +{ +struct page *page; +void *vaddr =NULL ; + + page =alloc_pages_node (node,GFP_ATOMIC | __GFP_ZERO,0 ); +if (page) + vaddr =page_address (page); +return vaddr; +} + +static inline void free_pgtable_page (void *vaddr) +{ +free_page ((unsigned long )vaddr); +} + + +static void __init check_tylersburg_isoch (void ); + +/* global iommu list, set NULL for ignored DMAR units */ +static struct intel_iommu **g_iommus; +static int rwbf_quirk; + +/* +* set to 1 to panic kernel if can't successfully enable VT-d +* (used when kernel is launched w/ TXT) +*/ +static int force_on; + +/* +* This domain is a statically identity mapping domain. +* 1. This domain creats a static 1:1 mapping to all usable memory. +* 2. It maps to each iommu if successful. +* 3. Each iommu mapps to this domain if successful. +*/ +static struct dmar_domain *si_domain; +static int hw_pass_through =1 ; +static void flush_unmaps_timeout (unsigned long data); static DEFINE_TIMER(unmap_timer, flush_unmaps_timeout, 0, 0);@@ -468,22 +486,6 @@ static struct kmem_cache *iommu_domain_cache;static struct kmem_cache *iommu_devinfo_cache;static struct kmem_cache *iommu_iova_cache; -static inline void *alloc_pgtable_page (int node) -{ -struct page *page; -void *vaddr =NULL ; - - page =alloc_pages_node (node,GFP_ATOMIC | __GFP_ZERO,0 ); -if (page) - vaddr =page_address (page); -return vaddr; -} - -static inline void free_pgtable_page (void *vaddr) -{ -free_page ((unsigned long )vaddr); -} -static inline void *alloc_domain_mem (void ) {return kmem_cache_alloc (iommu_domain_cache,GFP_ATOMIC );Signature · 1 line
Bill Sumner <[email protected]>
PATCH 3/8 Create intel-iommu-private.h
In reply to Bill Sumner Move the single block of #define, static inline ... ; struct definitions to intel-iommu-private.h from intel-iommu.c
Replace them with #include "intel-iommu-private.h"
--- drivers/iommu/intel-iommu-private.h | 351 ++++++++++++++++++++++++++++++++++++ drivers/iommu/intel-iommu.c | 328 +-------------------------------- 2 files changed, 352 insertions(+), 327 deletions(-) create mode 100644 drivers/iommu/intel-iommu-private.h
drivers/iommu/intel-iommu-private.h new file +351 -0
@@ -0,0 +1,351 @@ +/* +* Copyright (c) 2006, Intel Corporation. +* +* This program is free software; you can redistribute it and/or modify it +* under the terms and conditions of the GNU General Public License, +* version 2, as published by the Free Software Foundation. +* +* This program is distributed in the hope it will be useful, but WITHOUT +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +* more details. +* +* +* +* +* +* Copyright (C) 2006-2008 Intel Corporation +* Author: Ashok Raj <[email protected]> +* Author: Shaohua Li <[email protected]> +* Author: Anil S Keshavamurthy <[email protected]> +* Author: Fenghua Yu <[email protected]> +*/ + + +#define ROOT_SIZE VTD_PAGE_SIZE +#define CONTEXT_SIZE VTD_PAGE_SIZE + +#define IS_GFX_DEVICE (pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) +#define IS_ISA_DEVICE (pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_AZALIA (pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + +#define IOAPIC_RANGE_START (0xfee00000) +#define IOAPIC_RANGE_END (0xfeefffff) +#define IOVA_START_ADDR (0x1000) + +#define DEFAULT_DOMAIN_ADDRESS_WIDTH 48 + +#define MAX_AGAW_WIDTH 64 +#define MAX_AGAW_PFN_WIDTH (MAX_AGAW_WIDTH - VTD_PAGE_SHIFT) + +#define __DOMAIN_MAX_PFN (gaw) ((((uint64_t)1) << (gaw-VTD_PAGE_SHIFT)) - 1) +#define __DOMAIN_MAX_ADDR (gaw) ((((uint64_t)1) << gaw) - 1) + +/* We limit DOMAIN_MAX_PFN to fit in an unsigned long, and DOMAIN_MAX_ADDR +to match. That way, we can use 'unsigned long' for PFNs with impunity. */ +#define DOMAIN_MAX_PFN (gaw) ((unsigned long) min_t(uint64_t, \ + __DOMAIN_MAX_PFN(gaw), (unsigned long)-1)) +#define DOMAIN_MAX_ADDR (gaw) (((uint64_t)__DOMAIN_MAX_PFN(gaw)) << VTD_PAGE_SHIFT) + +#define IOVA_PFN (addr) ((addr) >> PAGE_SHIFT) +#define DMA_32BIT_PFN IOVA_PFN(DMA_BIT_MASK(32)) +#define DMA_64BIT_PFN IOVA_PFN(DMA_BIT_MASK(64)) + +/* page table handling */ +#define LEVEL_STRIDE (9) +#define LEVEL_MASK (((u64)1 << LEVEL_STRIDE) - 1) + +/* +* This bitmap is used to advertise the page sizes our hardware support +* to the IOMMU core, which will then use this information to split +* physically contiguous memory regions it is mapping into page sizes +* that we support. +* +* Traditionally the IOMMU core just handed us the mappings directly, +* after making sure the size is an order of a 4KiB page and that the +* mapping has natural alignment. +* +* To retain this behavior, we currently advertise that we support +* all page sizes that are an order of 4KiB. +* +* If at some point we'd like to utilize the IOMMU core's new behavior, +* we could change this to advertise the real page sizes we support. +*/ +#define INTEL_IOMMU_PGSIZES (~0xFFFUL) + +static inline int agaw_to_level (int agaw) +{ +return agaw +2 ; +} + +static inline int agaw_to_width (int agaw) +{ +return min_t (int,30 + agaw *LEVEL_STRIDE ,MAX_AGAW_WIDTH ); +} + +static inline int width_to_agaw (int width) +{ +return DIV_ROUND_UP (width -30 ,LEVEL_STRIDE ); +} + +static inline unsigned int level_to_offset_bits (int level) +{ +return (level -1 ) *LEVEL_STRIDE ; +} + +static inline int pfn_level_offset (unsigned long pfn,int level) +{ +return (pfn >>level_to_offset_bits (level)) &LEVEL_MASK ; +} + +static inline unsigned long level_mask (int level) +{ +return -1UL <<level_to_offset_bits (level); +} + +static inline unsigned long level_size (int level) +{ +return 1UL <<level_to_offset_bits (level); +} + +static inline unsigned long align_to_level (unsigned long pfn,int level) +{ +return (pfn +level_size (level) -1 ) &level_mask (level); +} + +static inline unsigned long lvl_to_nr_pages (unsigned int lvl) +{ +return 1 <<min_t (int, (lvl -1 ) *LEVEL_STRIDE ,MAX_AGAW_PFN_WIDTH ); +} + +/* VT-d pages must always be _smaller_ than MM pages. Otherwise things +are never going to work. */ +static inline unsigned long dma_to_mm_pfn (unsigned long dma_pfn) +{ +return dma_pfn >> (PAGE_SHIFT -VTD_PAGE_SHIFT ); +} + +static inline unsigned long mm_to_dma_pfn (unsigned long mm_pfn) +{ +return mm_pfn << (PAGE_SHIFT -VTD_PAGE_SHIFT ); +} +static inline unsigned long page_to_dma_pfn (struct page *pg) +{ +return mm_to_dma_pfn (page_to_pfn (pg)); +} +static inline unsigned long virt_to_dma_pfn (void *p) +{ +return page_to_dma_pfn (virt_to_page (p)); +} + + +/* +* 0: Present +* 1-11: Reserved +* 12-63: Context Ptr (12 - (haw-1)) +* 64-127: Reserved +*/ +struct root_entry { +u64 val; +u64 rsvd1; +}; +#define ROOT_ENTRY_NR (VTD_PAGE_SIZE/sizeof(struct root_entry)) +static inline bool root_present (struct root_entry *root) +{ +return root->val &1 ; +} +static inline void set_root_present (struct root_entry *root) +{ + root->val |=1 ; +} +static inline void set_root_value (struct root_entry *root,unsigned long value) +{ + root->val |= value &VTD_PAGE_MASK ; +} + +static inline struct context_entry * +get_context_addr_from_root (struct root_entry *root) +{ +return (struct context_entry *) + (root_present (root)?phys_to_virt ( + root->val &VTD_PAGE_MASK ) : +NULL ); +} + +/* +* low 64 bits: +* 0: present +* 1: fault processing disable +* 2-3: translation type +* 12-63: address space root +* high 64 bits: +* 0-2: address width +* 3-6: aval +* 8-23: domain id +*/ +struct context_entry { +u64 lo; +u64 hi; +}; + +static inline bool context_present (struct context_entry *context) +{ +return context->lo &1 ; +} +static inline void context_set_present (struct context_entry *context) +{ + context->lo |=1 ; +} + +static inline void context_set_fault_enable (struct context_entry *context) +{ + context->lo &= (((u64)-1 ) <<2 ) |1 ; +} + +static inline void context_set_translation_type (struct context_entry *context, +unsigned long value) +{ + context->lo &= (((u64)-1 ) <<4 ) |3 ; + context->lo |= (value &3 ) <<2 ; +} + +static inline void context_set_address_root (struct context_entry *context, +unsigned long value) +{ + context->lo |= value &VTD_PAGE_MASK ; +} + +static inline void context_set_address_width (struct context_entry *context, +unsigned long value) +{ + context->hi |= value &7 ; +} + +static inline void context_set_domain_id (struct context_entry *context, +unsigned long value) +{ + context->hi |= (value & ((1 <<16 ) -1 )) <<8 ; +} + +static inline void context_clear_entry (struct context_entry *context) +{ + context->lo =0 ; + context->hi =0 ; +} + +/* +* 0: readable +* 1: writable +* 2-6: reserved +* 7: super page +* 8-10: available +* 11: snoop behavior +* 12-63: Host physcial address +*/ +struct dma_pte { +u64 val; +}; + +static inline void dma_clear_pte (struct dma_pte *pte) +{ + pte->val =0 ; +} + +static inline u64 dma_pte_addr (struct dma_pte *pte) +{ +#ifdef CONFIG_64BIT +return pte->val &VTD_PAGE_MASK ; +#else +/* Must have a full atomic 64-bit read */ +return __cmpxchg64 (&pte->val,0ULL ,0ULL ) &VTD_PAGE_MASK ; +#endif +} + +static inline bool dma_pte_present (struct dma_pte *pte) +{ +return (pte->val &3 ) !=0 ; +} + +static inline bool dma_pte_superpage (struct dma_pte *pte) +{ +return pte->val & (1 <<7 ); +} + +static inline int first_pte_in_page (struct dma_pte *pte) +{ +return !((unsigned long )pte & ~VTD_PAGE_MASK ); +} + +/* 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 +* across iommus may be owned in one domain, e.g. kvm guest. +*/ +#define DOMAIN_FLAG_VIRTUAL_MACHINE (1 << 1) + +/* si_domain contains mulitple devices */ +#define DOMAIN_FLAG_STATIC_IDENTITY (1 << 2) + +/* define the limit of IOMMUs supported in each domain */ +#ifdef CONFIG_X86 +# define IOMMU_UNITS_SUPPORTED MAX_IO_APICS +#else +# define IOMMU_UNITS_SUPPORTED 64 +#endif + +struct dmar_domain { +int id;/* domain id */ +int nid;/* node id */ + DECLARE_BITMAP(iommu_bmp, IOMMU_UNITS_SUPPORTED); +/* bitmap of iommus this domain uses*/ + +struct list_head devices;/* all devices' list */ +struct iova_domain iovad;/* iova's that belong to this domain */ + +struct dma_pte *pgd;/* virtual address */ +int gaw;/* max guest address width */ + +/* adjusted guest address width, 0 is level 2 30-bit */ +int agaw; + +int flags;/* flags to find out type of domain */ + +int iommu_coherency;/* indicate coherency of iommu access */ +int iommu_snooping;/* indicate snooping control feature*/ +int iommu_count;/* reference count of iommu */ +int iommu_superpage;/* Level of superpages supported: +0 == 4KiB (no superpages), 1 == 2MiB, +2 == 1GiB, 3 == 512GiB, 4 == 1TiB */ +spinlock_t iommu_lock;/* protect iommu set in domain */ +u64 max_addr;/* maximum mapped address */ +}; + +/* PCI domain-device relationship */ +struct device_domain_info { +struct list_head link;/* link to domain siblings */ +struct list_head global;/* link to global list */ +int segment;/* PCI domain */ +u8 bus;/* PCI bus number */ +u8 devfn;/* PCI devfn number */ +struct pci_dev *dev;/* it's NULL for PCIe-to-PCI bridge */ +struct intel_iommu *iommu;/* IOMMU used by this device */ +struct dmar_domain *domain;/* pointer to domain */ +}; + +static inline void *alloc_pgtable_page (int node) +{ +struct page *page; +void *vaddr =NULL ; + + page =alloc_pages_node (node,GFP_ATOMIC | __GFP_ZERO,0 ); +if (page) + vaddr =page_address (page); +return vaddr; +} + +static inline void free_pgtable_page (void *vaddr) +{ +free_page ((unsigned long )vaddr); +} +
drivers/iommu/intel-iommu.c +1 -327
334 lines not on this page · raw message
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 4/8 Update iommu_attach_domain() and its callers
In reply to Bill SumnerAllow specification of the domain-id for the new domain.
--- drivers/iommu/intel-iommu.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-)
drivers/iommu/intel-iommu.c +14 -9
@@ -1003,7 +1003,8 @@ static struct dmar_domain *alloc_domain(void) }static int iommu_attach_domain (struct dmar_domain *domain, -struct intel_iommu *iommu) +struct intel_iommu *iommu, +int domain_number) {int num;unsigned long ndomains;
@@ -1013,12 +1014,15 @@ static int iommu_attach_domain(struct dmar_domain *domain,spin_lock_irqsave (&iommu->lock, flags); - 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 (domain_number <0 ) { + num =find_first_zero_bit (iommu->domain_ids, ndomains); +if (num >= ndomains) { +spin_unlock_irqrestore (&iommu->lock, flags); +pr_err ("IOMMU: no free domain ids\n" ); +return -ENOMEM ; + } + }else + num = domain_number; domain->id = num;set_bit (num, iommu->domain_ids);
@@ -1629,6 +1633,7 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)int bus =0 , devfn =0 ;int segment;int ret; +int did =-1 ;/* Default to "no domain_id supplied" */ domain =find_domain (pdev);if (domain)
@@ -1675,7 +1680,7 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw) } iommu = drhd->iommu; - ret =iommu_attach_domain (domain, iommu); + ret =iommu_attach_domain (domain, iommu, did);if (ret) {free_domain_mem (domain); goto error;
@@ -1894,7 +1899,7 @@ static int __init si_domain_init(int hw)return -EFAULT ;for_each_active_iommu (iommu, drhd) { - ret =iommu_attach_domain (si_domain, iommu); + ret =iommu_attach_domain (si_domain, iommu, (int )-1 );if (ret) {domain_exit (si_domain);return -EFAULT ;
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 5/8 Add new definitions & prototypes to intel-iommu-private.h
In reply to Bill SumnerItems needed for fixing crashdump.
--- drivers/iommu/intel-iommu-private.h | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
drivers/iommu/intel-iommu-private.h +92 -0
@@ -349,3 +349,95 @@ static inline void free_pgtable_page(void *vaddr)free_page ((unsigned long )vaddr); } +#ifdef CONFIG_CRASH_DUMP +/* +* Fix Crashdump failure caused by leftover DMA through a hardware IOMMU +* +* Fixes the crashdump kernel to deal with an active iommu and legacy +* DMA from the (old) panicked kernel in a manner similar to how legacy +* DMA is handled when no hardware iommu was in use by the old kernel -- +* allow the legacy DMA to continue into its current buffers. +* +* In the crashdump kernel, this code: +* 1. skips disabling the IOMMU's translating of IO Virtual Addresses (IOVA) +* 2. leaves the current translations in-place so that legacy DMA will +* continue to use its current buffers, +* 3. allocates to the device drivers in the crashdump kernel +* portions of the iova address ranges that are different +* from the iova address ranges that were being used by the old kernel +* at the time of the panic. +* +*/ + +struct domain_values_entry { +struct list_head link;/* link entries into a list */ +struct iova_domain iovad;/* iova's that belong to this domain */ +struct dma_pte *pgd;/* virtual address */ +int did;/* domain id */ +int gaw;/* max guest address width */ +int iommu_superpage;/* Level of superpages supported: +0 == 4KiB (no superpages), 1 == 2MiB, +2 == 1GiB, 3 == 512GiB, 4 == 1TiB */ +}; + + + +/* ------------------------------------------------------------------------ +* Prototypes of interface functions +* ------------------------------------------------------------------------ +*/ + +extern int +intel_iommu_copy_translation_tables (struct dmar_drhd_unit *drhd, +struct root_entry **root_old_p,struct root_entry **root_new_p, +int g_num_of_iommus); + +extern int +intel_iommu_get_dids_from_old_kernel (struct intel_iommu *iommu); + +extern struct domain_values_entry * +intel_iommu_did_to_domain_values_entry (int did,struct intel_iommu *iommu); + + +/* ------------------------------------------------------------------------ +* Utility functions for accessing the iommu Translation Tables +* ------------------------------------------------------------------------ +*/ + +static inline struct context_entry * +get_context_phys_from_root (struct root_entry *root) +{ +return (struct context_entry *) + (root_present (root) ? (void *) (root->val &VTD_PAGE_MASK ) + :NULL ); +} + +static inline int +context_get_p (struct context_entry *c) {return ((c->lo >>0 ) &0x1 ); } + +static inline int +context_get_fpdi (struct context_entry *c) {return ((c->lo >>1 ) &0x1 ); } + +static inline int +context_get_t (struct context_entry *c) {return ((c->lo >>2 ) &0x3 ); } + +static inline u64 +context_get_asr (struct context_entry *c) {return ((c->lo >>12 )); } + +static inline int +context_get_aw (struct context_entry *c) {return ((c->hi >>0 ) &0x7 ); } + +static inline int +context_get_aval (struct context_entry *c) {return ((c->hi >>3 ) &0xf ); } + +static inline int +context_get_did (struct context_entry *c) {return ((c->hi >>8 ) &0xffff ); } + +static inline void +context_put_asr (struct context_entry *c,unsigned long asr) +{ + c->lo &= (~VTD_PAGE_MASK ); + c->lo |= (asr <<VTD_PAGE_SHIFT ); +} + +#endif /* CONFIG_CRASH_DUMP */
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 6/8 Create intel-iommu-kdump.c
In reply to Bill SumnerPopulate with "Copy iommu translation tables" function set. Edit Makefile to add intel-iommu-kdump.o
--- drivers/iommu/Makefile | 2 +- drivers/iommu/intel-iommu-kdump.c | 590 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 591 insertions(+), 1 deletion(-) create mode 100644 drivers/iommu/intel-iommu-kdump.c
drivers/iommu/Makefile +1 -1
@@ -6,7 +6,7 @@ obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o obj-$(CONFIG_AMD_IOMMU_V2) += amd_iommu_v2.o obj-$(CONFIG_ARM_SMMU) += arm-smmu.o obj-$(CONFIG_DMAR_TABLE) += dmar.o -obj-$(CONFIG_INTEL_IOMMU) += iova.o intel-iommu.o +obj-$(CONFIG_INTEL_IOMMU) += iova.o intel-iommu.o intel-iommu-kdump.o obj-$(CONFIG_IRQ_REMAP) += intel_irq_remapping.o irq_remapping.o obj-$(CONFIG_OMAP_IOMMU) += omap-iommu.o obj-$(CONFIG_OMAP_IOMMU) += omap-iommu2.o
drivers/iommu/intel-iommu-kdump.c new file +590 -0
590 lines not on this page · raw message
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 7/8 Add domain-id functions to intel-iommu-kdump.c
In reply to Bill Sumner intel_iommu_did_to_domain_values_entry()
intel_iommu_get_dids_from_old_kernel()
--- drivers/iommu/intel-iommu-kdump.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
drivers/iommu/intel-iommu-kdump.c +39 -0
@@ -53,6 +53,45 @@ static struct list_head *domain_values_list;/* ======================================================================== +* Interfaces for when a new domain in the crashdump kernel needs some +* values from the panicked kernel's context entries +* ------------------------------------------------------------------------ +*/ + + +struct domain_values_entry *intel_iommu_did_to_domain_values_entry (int did, +struct intel_iommu *iommu) +{ +struct domain_values_entry *dve;/* iterator */ + +list_for_each_entry (dve, &domain_values_list[iommu->seq_id], link) +if (dve->did == did) +return dve; +return NULL ; +} + + +/* Mark domain-id's from old kernel as in-use on this iommu so that a new +* domain-id is allocated in the case where there is a device in the new kernel +* that was not in the old kernel -- and therefore a new domain-id is needed. +*/ +int intel_iommu_get_dids_from_old_kernel (struct intel_iommu *iommu) +{ +struct domain_values_entry *dve;/* iterator */ + +pr_info ("IOMMU:%d Domain ids from panicked kernel:\n" , iommu->seq_id); + +list_for_each_entry (dve, &domain_values_list[iommu->seq_id], link) { +set_bit (dve->did, iommu->domain_ids); +pr_info ("DID did:%d(0x%4.4x)\n" , dve->did, dve->did); + } + +pr_info ("----------------------------------------\n" ); +return 0 ; +} + + +/* ======================================================================== * Copy iommu translation tables from old kernel into new kernel. * Entry to this set of functions is: intel_iommu_copy_translation_tables() * ------------------------------------------------------------------------
Signature · 1 line
Bill Sumner <[email protected]>
PATCH 8/8 Add remaining changes to intel-iommu.c to fix crashdump
In reply to Bill Sumner Add "#include <linux/crash_dump.h>"
Chg device_to_domain_id
Chg get_domain_for_dev
Chg init_dmars
Chg intel_iommu_init
--- drivers/iommu/intel-iommu.c | 210 +++++++++++++++++++++++++++++++++----------- 1 file changed, 158 insertions(+), 52 deletions(-)
drivers/iommu/intel-iommu.c +158 -52
@@ -49,7 +49,9 @@#include "irq_remapping.h" #include "pci.h" #include "intel-iommu-private.h" +#include <linux/crash_dump.h> +static int intel_iommu_in_crashdump;static void __init check_tylersburg_isoch (void );
@@ -1605,6 +1607,24 @@ static void domain_remove_dev_info(struct dmar_domain *domain)spin_unlock_irqrestore (&device_domain_lock, flags); } +#ifdef CONFIG_CRASH_DUMP +static int device_to_domain_id (struct intel_iommu *iommu,u8 bus,u8 devfn) +{ +int did =-1 ;/* domain-id returned */ +struct root_entry *root; +struct context_entry *context; +unsigned long flags; + +spin_lock_irqsave (&iommu->lock, flags); + root = &iommu->root_entry[bus]; + context =get_context_addr_from_root (root); +if (context &&context_present (context+devfn)) + did =context_get_did (context+devfn); +spin_unlock_irqrestore (&iommu->lock, flags); +return did; +} +#endif /* CONFIG_CRASH_DUMP */ +/* * find_domain * Note: we use struct pci_dev->dev.archdata.iommu stores the info
@@ -1634,6 +1654,9 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw)int segment;int ret;int did =-1 ;/* Default to "no domain_id supplied" */ +#ifdef CONFIG_CRASH_DUMP +struct domain_values_entry *dve =NULL ; +#endif /* CONFIG_CRASH_DUMP */ domain =find_domain (pdev);if (domain)
@@ -1680,6 +1703,24 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw) } iommu = drhd->iommu; +#ifdef CONFIG_CRASH_DUMP +if (intel_iommu_in_crashdump) { +/* +* if this device had a did in the old kernel +* use its values instead of generating new ones +*/ + did =device_to_domain_id (iommu, pdev->bus->number, + pdev->devfn); +if (did >0 || (did ==0 && !cap_caching_mode (iommu->cap))) + dve =intel_iommu_did_to_domain_values_entry (did, + iommu); +if (dve) + gaw = dve->gaw; +else + did =-1 ; + } +#endif /* CONFIG_CRASH_DUMP */ + ret =iommu_attach_domain (domain, iommu, did);if (ret) {free_domain_mem (domain);
@@ -1691,6 +1732,18 @@ static struct dmar_domain *get_domain_for_dev(struct pci_dev *pdev, int gaw) goto error; } +#ifdef CONFIG_CRASH_DUMP +if (intel_iommu_in_crashdump && dve) { + +if (domain->pgd) +free_pgtable_page (domain->pgd); + + domain->pgd = dve->pgd; + +copy_reserved_iova (&dve->iovad, &domain->iovad); + } +#endif /* CONFIG_CRASH_DUMP */ +/* register pcie-to-pci device */ if (dev_tmp) { info =alloc_devinfo_mem ();
@@ -2109,6 +2162,10 @@ static int __init init_dmars(void)struct pci_dev *pdev;struct intel_iommu *iommu;int i, ret; +#ifdef CONFIG_CRASH_DUMP +struct root_entry *root_old_phys; +struct root_entry *root_new_virt; +#endif /* CONFIG_CRASH_DUMP */ /* * for each drhd
@@ -2152,16 +2209,40 @@ static int __init init_dmars(void)if (ret) goto error; -/* -* TBD: -* we could share the same root & context tables -* among all IOMMU's. Need to Split it later. -*/ - ret =iommu_alloc_root_entry (iommu); -if (ret) { -printk (KERN_ERR "IOMMU: allocate root entry failed\n" ); - goto error; +#ifdef CONFIG_CRASH_DUMP +if (intel_iommu_in_crashdump) { +pr_info ("IOMMU Copying translate tables from panicked kernel\n" ); + ret =intel_iommu_copy_translation_tables (drhd, + &root_old_phys, &root_new_virt, + g_num_of_iommus); +if (ret) { +pr_err ("IOMMU: Copy translate tables failed\n" ); + +/* Best to stop trying */ + intel_iommu_in_crashdump = false; + goto error; + } + iommu->root_entry = root_new_virt; +pr_info ("IOMMU: root_new_virt:0x%12.12llx phys:0x%12.12llx\n" , + (u64 )root_new_virt, +virt_to_phys (root_new_virt)); +intel_iommu_get_dids_from_old_kernel (iommu); + }else { +#endif /* CONFIG_CRASH_DUMP */ +/* +* TBD: +* we could share the same root & context tables +* among all IOMMU's. Need to Split it later. +*/ + ret =iommu_alloc_root_entry (iommu); +if (ret) { +printk (KERN_ERR "IOMMU: allocate root entry failed\n" ); + goto error; + } +#ifdef CONFIG_CRASH_DUMP } +#endif /* CONFIG_CRASH_DUMP */ +if (!ecap_pass_through (iommu->ecap)) hw_pass_through =0 ; }
@@ -2220,50 +2301,63 @@ static int __init init_dmars(void)check_tylersburg_isoch (); +#ifdef CONFIG_CRASH_DUMP /* -* 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. +* In the crashdump kernel: Skip setting-up new domains for +* si, rmrr, and the isa bus on the expectation that these +* translations were copied from the old kernel. */ -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" ); - goto error; +if (!intel_iommu_in_crashdump) { +#endif /* CONFIG_CRASH_DUMP */ +/* +* 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. +*/ +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" ); + goto error; + } } - } -/* -* For each rmrr -* for each dev attached to rmrr -* do -* locate drhd for dev, alloc domain for dev -* allocate free domain -* allocate page table entries for rmrr -* if context not allocated for bus -* allocate and init context -* set present in root table for this bus -* init context with domain, translation etc -* endfor -* endfor -*/ -printk (KERN_INFO "IOMMU: Setting RMRR:\n" ); -for_each_rmrr_units (rmrr) { -for (i =0 ; i < rmrr->devices_cnt; i++) { - pdev = rmrr->devices[i]; -/* -* some BIOS lists non-exist devices in DMAR -* table. -*/ -if (!pdev) -continue ; - ret =iommu_prepare_rmrr_dev (rmrr, pdev); -if (ret) -printk (KERN_ERR -"IOMMU: mapping reserved region failed\n" ); +/* +* For each rmrr +* for each dev attached to rmrr +* do +* locate drhd for dev, alloc domain for dev +* allocate free domain +* allocate page table entries for rmrr +* if context not allocated for bus +* allocate and init context +* set present in root table for this bus +* init context with domain, translation etc +* endfor +* endfor +*/ +printk (KERN_INFO "IOMMU: Setting RMRR:\n" ); +for_each_rmrr_units (rmrr) { +for (i =0 ; i < rmrr->devices_cnt; i++) { + pdev = rmrr->devices[i]; +/* +* some BIOS lists non-exist devices in DMAR +* table. +*/ +if (!pdev) +continue ; + ret =iommu_prepare_rmrr_dev (rmrr, pdev); +if (ret) +printk (KERN_ERR +"IOMMU: mapping reserved region failed\n" ); + } } - } -iommu_prepare_isa (); +iommu_prepare_isa (); +#ifdef CONFIG_CRASH_DUMP + } +#endif /* CONFIG_CRASH_DUMP */ /* * for each drhd
@@ -3325,12 +3419,24 @@ int __init intel_iommu_init(void) goto out_free_dmar; } +#ifdef CONFIG_CRASH_DUMP /* -* Disable translation if already enabled prior to OS handover. +* If (This is the crash kernel) +* Set: copy iommu translate tables from old kernel +* Skip disabling the iommu hardware translations */ -for_each_active_iommu (iommu, drhd) -if (iommu->gcmd &DMA_GCMD_TE ) -iommu_disable_translation (iommu); +if (is_kdump_kernel ()) { + intel_iommu_in_crashdump = true; +pr_info ("IOMMU intel_iommu_in_crashdump = true\n" ); +pr_info ("IOMMU Skip disabling iommu hardware translations\n" ); + } else +#endif /* CONFIG_CRASH_DUMP */ +/* +* 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)
Signature · 1 line
Bill Sumner <[email protected]>