From 2c1296d92ac0367364bcb73a43c12a0bdfbfee75 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 28 May 2015 18:41:32 +0200 Subject: iommu: Add iommu_get_domain_for_dev function This function can be used to request the current domain a device is attached to. Signed-off-by: Joerg Roedel --- include/linux/iommu.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include') diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 0546b8710ce3..683a1c4b15e7 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -193,6 +193,7 @@ extern int iommu_attach_device(struct iommu_domain *domain, struct device *dev); extern void iommu_detach_device(struct iommu_domain *domain, struct device *dev); +extern struct iommu_domain *iommu_get_domain_for_dev(struct device *dev); extern int iommu_map(struct iommu_domain *domain, unsigned long iova, phys_addr_t paddr, size_t size, int prot); extern size_t iommu_unmap(struct iommu_domain *domain, unsigned long iova, @@ -332,6 +333,11 @@ static inline void iommu_detach_device(struct iommu_domain *domain, { } +static inline struct iommu_domain *iommu_get_domain_for_dev(struct device *dev) +{ + return NULL; +} + static inline int iommu_map(struct iommu_domain *domain, unsigned long iova, phys_addr_t paddr, int gfp_order, int prot) { -- cgit v1.2.3 From a1015c2b99b94cf521603b41debf167114031456 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 28 May 2015 18:41:33 +0200 Subject: iommu: Introduce direct mapped region handling Add two new functions to the IOMMU-API to allow the IOMMU drivers to export the requirements for direct mapped regions per device. This is useful for exporting the information in Intel VT-d's RMRR entries or AMD-Vi's unity mappings. Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 16 ++++++++++++++++ include/linux/iommu.h | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) (limited to 'include') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index a0a38bd2668b..6b8d6e7771e1 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1469,3 +1469,19 @@ int iommu_domain_set_attr(struct iommu_domain *domain, return ret; } EXPORT_SYMBOL_GPL(iommu_domain_set_attr); + +void iommu_get_dm_regions(struct device *dev, struct list_head *list) +{ + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (ops && ops->get_dm_regions) + ops->get_dm_regions(dev, list); +} + +void iommu_put_dm_regions(struct device *dev, struct list_head *list) +{ + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (ops && ops->put_dm_regions) + ops->put_dm_regions(dev, list); +} diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 683a1c4b15e7..689499904166 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -114,6 +114,20 @@ enum iommu_attr { DOMAIN_ATTR_MAX, }; +/** + * struct iommu_dm_region - descriptor for a direct mapped memory region + * @list: Linked list pointers + * @start: System physical start address of the region + * @length: Length of the region in bytes + * @prot: IOMMU Protection flags (READ/WRITE/...) + */ +struct iommu_dm_region { + struct list_head list; + phys_addr_t start; + size_t length; + int prot; +}; + #ifdef CONFIG_IOMMU_API /** @@ -159,6 +173,10 @@ struct iommu_ops { int (*domain_set_attr)(struct iommu_domain *domain, enum iommu_attr attr, void *data); + /* Request/Free a list of direct mapping requirements for a device */ + void (*get_dm_regions)(struct device *dev, struct list_head *list); + void (*put_dm_regions)(struct device *dev, struct list_head *list); + /* Window handling functions */ int (*domain_window_enable)(struct iommu_domain *domain, u32 wnd_nr, phys_addr_t paddr, u64 size, int prot); @@ -205,6 +223,9 @@ extern phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t io extern void iommu_set_fault_handler(struct iommu_domain *domain, iommu_fault_handler_t handler, void *token); +extern void iommu_get_dm_regions(struct device *dev, struct list_head *list); +extern void iommu_put_dm_regions(struct device *dev, struct list_head *list); + extern int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group); extern void iommu_detach_group(struct iommu_domain *domain, @@ -379,6 +400,16 @@ static inline void iommu_set_fault_handler(struct iommu_domain *domain, { } +static inline void iommu_get_dm_regions(struct device *dev, + struct list_head *list) +{ +} + +static inline void iommu_put_dm_regions(struct device *dev, + struct list_head *list) +{ +} + static inline int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group) { -- cgit v1.2.3 From 6827ca83695d5e41ad31b0719788ee65f00ca4b3 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 28 May 2015 18:41:35 +0200 Subject: iommu: Add function to query the default domain of a group This will be used to handle unity mappings in the iommu drivers. Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 5 +++++ include/linux/iommu.h | 1 + 2 files changed, 6 insertions(+) (limited to 'include') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index ffad1eaf450d..224c6dd2d249 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -837,6 +837,11 @@ struct iommu_group *iommu_group_get_for_dev(struct device *dev) return group; } +struct iommu_domain *iommu_group_default_domain(struct iommu_group *group) +{ + return group->default_domain; +} + static int add_iommu_group(struct device *dev, void *data) { struct iommu_callback_data *cb = data; diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 689499904166..b944b2be4fa2 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -249,6 +249,7 @@ extern int iommu_group_unregister_notifier(struct iommu_group *group, struct notifier_block *nb); extern int iommu_group_id(struct iommu_group *group); extern struct iommu_group *iommu_group_get_for_dev(struct device *dev); +extern struct iommu_domain *iommu_group_default_domain(struct iommu_group *); extern int iommu_domain_get_attr(struct iommu_domain *domain, enum iommu_attr, void *data); -- cgit v1.2.3 From d290f1e70d85a9a4d124594c6a3d769329960bdc Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Thu, 28 May 2015 18:41:36 +0200 Subject: iommu: Introduce iommu_request_dm_for_dev() This function can be called by an IOMMU driver to request that a device's default domain is direct mapped. Signed-off-by: Joerg Roedel --- drivers/iommu/iommu.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/iommu.h | 6 ++++++ 2 files changed, 59 insertions(+) (limited to 'include') diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 224c6dd2d249..3b1a2551a747 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -1538,3 +1538,56 @@ void iommu_put_dm_regions(struct device *dev, struct list_head *list) if (ops && ops->put_dm_regions) ops->put_dm_regions(dev, list); } + +/* Request that a device is direct mapped by the IOMMU */ +int iommu_request_dm_for_dev(struct device *dev) +{ + struct iommu_domain *dm_domain; + struct iommu_group *group; + int ret; + + /* Device must already be in a group before calling this function */ + group = iommu_group_get_for_dev(dev); + if (!group) + return -EINVAL; + + mutex_lock(&group->mutex); + + /* Check if the default domain is already direct mapped */ + ret = 0; + if (group->default_domain && + group->default_domain->type == IOMMU_DOMAIN_IDENTITY) + goto out; + + /* Don't change mappings of existing devices */ + ret = -EBUSY; + if (iommu_group_device_count(group) != 1) + goto out; + + /* Allocate a direct mapped domain */ + ret = -ENOMEM; + dm_domain = __iommu_domain_alloc(dev->bus, IOMMU_DOMAIN_IDENTITY); + if (!dm_domain) + goto out; + + /* Attach the device to the domain */ + ret = __iommu_attach_group(dm_domain, group); + if (ret) { + iommu_domain_free(dm_domain); + goto out; + } + + /* Make the direct mapped domain the default for this group */ + if (group->default_domain) + iommu_domain_free(group->default_domain); + group->default_domain = dm_domain; + + pr_info("Using direct mapping for device %s\n", dev_name(dev)); + + ret = 0; +out: + mutex_unlock(&group->mutex); + iommu_group_put(group); + + return ret; +} diff --git a/include/linux/iommu.h b/include/linux/iommu.h index b944b2be4fa2..dc767f7c3704 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -225,6 +225,7 @@ extern void iommu_set_fault_handler(struct iommu_domain *domain, extern void iommu_get_dm_regions(struct device *dev, struct list_head *list); extern void iommu_put_dm_regions(struct device *dev, struct list_head *list); +extern int iommu_request_dm_for_dev(struct device *dev); extern int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group); @@ -411,6 +412,11 @@ static inline void iommu_put_dm_regions(struct device *dev, { } +static inline int iommu_request_dm_for_dev(struct device *dev) +{ + return -ENODEV; +} + static inline int iommu_attach_group(struct iommu_domain *domain, struct iommu_group *group) { -- cgit v1.2.3 From 4158c2eca3c77ed3cccdcaeab153aad4e433369c Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Fri, 12 Jun 2015 10:14:02 +0200 Subject: iommu/vt-d: Detect pre enabled translation Add code to detect whether translation is already enabled in the IOMMU. Save this state in a flags field added to struct intel_iommu. Tested-by: ZhenHua Li Tested-by: Baoquan He Signed-off-by: Joerg Roedel --- drivers/iommu/intel-iommu.c | 19 +++++++++++++++++++ include/linux/intel-iommu.h | 4 ++++ 2 files changed, 23 insertions(+) (limited to 'include') diff --git a/drivers/iommu/intel-iommu.c b/drivers/iommu/intel-iommu.c index bf3e450b5b97..39b90621a1a1 100644 --- a/drivers/iommu/intel-iommu.c +++ b/drivers/iommu/intel-iommu.c @@ -443,6 +443,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) { @@ -2809,6 +2823,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 a240e61a7700..b85b81ad5eba 100644 --- a/include/linux/intel-iommu.h +++ b/include/linux/intel-iommu.h @@ -320,6 +320,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 */ @@ -351,6 +354,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( -- cgit v1.2.3 From af3b358e48115588d905cc07a47b3f356e0d01d1 Mon Sep 17 00:00:00 2001 From: Joerg Roedel Date: Fri, 12 Jun 2015 15:00:21 +0200 Subject: iommu/vt-d: Copy IR table from old kernel when in kdump mode 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. Tested-by: ZhenHua Li Tested-by: Baoquan He Signed-off-by: Joerg Roedel --- drivers/iommu/intel_irq_remapping.c | 70 +++++++++++++++++++++++++++++++++++++ include/linux/intel-iommu.h | 1 + 2 files changed, 71 insertions(+) (limited to 'include') diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c index 84970281b754..2a901219f953 100644 --- a/drivers/iommu/intel_irq_remapping.c +++ b/drivers/iommu/intel_irq_remapping.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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 b85b81ad5eba..9e14edcf7f6e 100644 --- a/include/linux/intel-iommu.h +++ b/include/linux/intel-iommu.h @@ -296,6 +296,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 -- cgit v1.2.3