diff options
Diffstat (limited to 'drivers/iommu/intel_irq_remapping.c')
| -rw-r--r-- | drivers/iommu/intel_irq_remapping.c | 238 | 
1 files changed, 130 insertions, 108 deletions
diff --git a/drivers/iommu/intel_irq_remapping.c b/drivers/iommu/intel_irq_remapping.c index f71673dbb23..9b174893f0f 100644 --- a/drivers/iommu/intel_irq_remapping.c +++ b/drivers/iommu/intel_irq_remapping.c @@ -6,11 +6,11 @@  #include <linux/hpet.h>  #include <linux/pci.h>  #include <linux/irq.h> +#include <linux/intel-iommu.h> +#include <linux/acpi.h>  #include <asm/io_apic.h>  #include <asm/smp.h>  #include <asm/cpu.h> -#include <linux/intel-iommu.h> -#include <acpi/acpi.h>  #include <asm/irq_remapping.h>  #include <asm/pci-direct.h>  #include <asm/msidef.h> @@ -38,15 +38,28 @@ static struct ioapic_scope ir_ioapic[MAX_IO_APICS];  static struct hpet_scope ir_hpet[MAX_HPET_TBS];  static int ir_ioapic_num, ir_hpet_num; +/* + * Lock ordering: + * ->dmar_global_lock + *	->irq_2_ir_lock + *		->qi->q_lock + *	->iommu->register_lock + * Note: + * intel_irq_remap_ops.{supported,prepare,enable,disable,reenable} are called + * in single-threaded environment with interrupt disabled, so no need to tabke + * the dmar_global_lock. + */  static DEFINE_RAW_SPINLOCK(irq_2_ir_lock); +static int __init parse_ioapics_under_ir(void); +  static struct irq_2_iommu *irq_2_iommu(unsigned int irq)  {  	struct irq_cfg *cfg = irq_get_chip_data(irq);  	return cfg ? &cfg->irq_2_iommu : NULL;  } -int get_irte(int irq, struct irte *entry) +static int get_irte(int irq, struct irte *entry)  {  	struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);  	unsigned long flags; @@ -69,19 +82,13 @@ static int alloc_irte(struct intel_iommu *iommu, int irq, u16 count)  	struct ir_table *table = iommu->ir_table;  	struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);  	struct irq_cfg *cfg = irq_get_chip_data(irq); -	u16 index, start_index;  	unsigned int mask = 0;  	unsigned long flags; -	int i; +	int index;  	if (!count || !irq_iommu)  		return -1; -	/* -	 * start the IRTE search from index 0. -	 */ -	index = start_index = 0; -  	if (count > 1) {  		count = __roundup_pow_of_two(count);  		mask = ilog2(count); @@ -96,32 +103,17 @@ static int alloc_irte(struct intel_iommu *iommu, int irq, u16 count)  	}  	raw_spin_lock_irqsave(&irq_2_ir_lock, flags); -	do { -		for (i = index; i < index + count; i++) -			if  (table->base[i].present) -				break; -		/* empty index found */ -		if (i == index + count) -			break; - -		index = (index + count) % INTR_REMAP_TABLE_ENTRIES; - -		if (index == start_index) { -			raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags); -			printk(KERN_ERR "can't allocate an IRTE\n"); -			return -1; -		} -	} while (1); - -	for (i = index; i < index + count; i++) -		table->base[i].present = 1; - -	cfg->remapped = 1; -	irq_iommu->iommu = iommu; -	irq_iommu->irte_index =  index; -	irq_iommu->sub_handle = 0; -	irq_iommu->irte_mask = mask; - +	index = bitmap_find_free_region(table->bitmap, +					INTR_REMAP_TABLE_ENTRIES, mask); +	if (index < 0) { +		pr_warn("IR%d: can't allocate an IRTE\n", iommu->seq_id); +	} else { +		cfg->remapped = 1; +		irq_iommu->iommu = iommu; +		irq_iommu->irte_index =  index; +		irq_iommu->sub_handle = 0; +		irq_iommu->irte_mask = mask; +	}  	raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags);  	return index; @@ -254,6 +246,8 @@ static int clear_entries(struct irq_2_iommu *irq_iommu)  		set_64bit(&entry->low, 0);  		set_64bit(&entry->high, 0);  	} +	bitmap_release_region(iommu->ir_table->bitmap, index, +			      irq_iommu->irte_mask);  	return qi_flush_iec(iommu, index, irq_iommu->irte_mask);  } @@ -324,19 +318,21 @@ static int set_ioapic_sid(struct irte *irte, int apic)  	if (!irte)  		return -1; +	down_read(&dmar_global_lock);  	for (i = 0; i < MAX_IO_APICS; i++) {  		if (ir_ioapic[i].id == apic) {  			sid = (ir_ioapic[i].bus << 8) | ir_ioapic[i].devfn;  			break;  		}  	} +	up_read(&dmar_global_lock);  	if (sid == 0) {  		pr_warning("Failed to set source-id of IOAPIC (%d)\n", apic);  		return -1;  	} -	set_irte_sid(irte, 1, 0, sid); +	set_irte_sid(irte, SVT_VERIFY_SID_SQ, SQ_ALL_16, sid);  	return 0;  } @@ -349,12 +345,14 @@ static int set_hpet_sid(struct irte *irte, u8 id)  	if (!irte)  		return -1; +	down_read(&dmar_global_lock);  	for (i = 0; i < MAX_HPET_TBS; i++) {  		if (ir_hpet[i].id == id) {  			sid = (ir_hpet[i].bus << 8) | ir_hpet[i].devfn;  			break;  		}  	} +	up_read(&dmar_global_lock);  	if (sid == 0) {  		pr_warning("Failed to set source-id of HPET block (%d)\n", id); @@ -453,6 +451,7 @@ static int intel_setup_irq_remapping(struct intel_iommu *iommu, int mode)  {  	struct ir_table *ir_table;  	struct page *pages; +	unsigned long *bitmap;  	ir_table = iommu->ir_table = kzalloc(sizeof(struct ir_table),  					     GFP_ATOMIC); @@ -464,13 +463,23 @@ static int intel_setup_irq_remapping(struct intel_iommu *iommu, int mode)  				 INTR_REMAP_PAGE_ORDER);  	if (!pages) { -		printk(KERN_ERR "failed to allocate pages of order %d\n", -		       INTR_REMAP_PAGE_ORDER); +		pr_err("IR%d: failed to allocate pages of order %d\n", +		       iommu->seq_id, INTR_REMAP_PAGE_ORDER);  		kfree(iommu->ir_table);  		return -ENOMEM;  	} +	bitmap = kcalloc(BITS_TO_LONGS(INTR_REMAP_TABLE_ENTRIES), +			 sizeof(long), GFP_ATOMIC); +	if (bitmap == NULL) { +		pr_err("IR%d: failed to allocate bitmap\n", iommu->seq_id); +		__free_pages(pages, INTR_REMAP_PAGE_ORDER); +		kfree(ir_table); +		return -ENOMEM; +	} +  	ir_table->base = page_address(pages); +	ir_table->bitmap = bitmap;  	iommu_set_irq_remapping(iommu, mode);  	return 0; @@ -521,16 +530,18 @@ static int __init dmar_x2apic_optout(void)  static int __init intel_irq_remapping_supported(void)  {  	struct dmar_drhd_unit *drhd; +	struct intel_iommu *iommu;  	if (disable_irq_remap)  		return 0;  	if (irq_remap_broken) { -		WARN_TAINT(1, TAINT_FIRMWARE_WORKAROUND, -			   "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" -			   "contact your BIOS vendor for an update\n"); +		printk(KERN_WARNING +			"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" +			"contact your BIOS vendor for an update\n"); +		add_taint(TAINT_FIRMWARE_WORKAROUND, LOCKDEP_STILL_OK);  		disable_irq_remap = 1;  		return 0;  	} @@ -538,12 +549,9 @@ static int __init intel_irq_remapping_supported(void)  	if (!dmar_ir_support())  		return 0; -	for_each_drhd_unit(drhd) { -		struct intel_iommu *iommu = drhd->iommu; - +	for_each_iommu(iommu, drhd)  		if (!ecap_ir_support(iommu->ecap))  			return 0; -	}  	return 1;  } @@ -551,6 +559,7 @@ static int __init intel_irq_remapping_supported(void)  static int __init intel_enable_irq_remapping(void)  {  	struct dmar_drhd_unit *drhd; +	struct intel_iommu *iommu;  	bool x2apic_present;  	int setup = 0;  	int eim = 0; @@ -563,6 +572,8 @@ static int __init intel_enable_irq_remapping(void)  	}  	if (x2apic_present) { +		pr_info("Queued invalidation will be enabled to support x2apic and Intr-remapping.\n"); +  		eim = !dmar_x2apic_optout();  		if (!eim)  			printk(KERN_WARNING @@ -571,9 +582,7 @@ static int __init intel_enable_irq_remapping(void)  				"Use 'intremap=no_x2apic_optout' to override BIOS request.\n");  	} -	for_each_drhd_unit(drhd) { -		struct intel_iommu *iommu = drhd->iommu; - +	for_each_iommu(iommu, drhd) {  		/*  		 * If the queued invalidation is already initialized,  		 * shouldn't disable it. @@ -598,9 +607,7 @@ static int __init intel_enable_irq_remapping(void)  	/*  	 * check for the Interrupt-remapping support  	 */ -	for_each_drhd_unit(drhd) { -		struct intel_iommu *iommu = drhd->iommu; - +	for_each_iommu(iommu, drhd) {  		if (!ecap_ir_support(iommu->ecap))  			continue; @@ -614,10 +621,8 @@ static int __init intel_enable_irq_remapping(void)  	/*  	 * Enable queued invalidation for all the DRHD's.  	 */ -	for_each_drhd_unit(drhd) { -		int ret; -		struct intel_iommu *iommu = drhd->iommu; -		ret = dmar_enable_qi(iommu); +	for_each_iommu(iommu, drhd) { +		int ret = dmar_enable_qi(iommu);  		if (ret) {  			printk(KERN_ERR "DRHD %Lx: failed to enable queued, " @@ -630,9 +635,7 @@ static int __init intel_enable_irq_remapping(void)  	/*  	 * Setup Interrupt-remapping for all the DRHD's now.  	 */ -	for_each_drhd_unit(drhd) { -		struct intel_iommu *iommu = drhd->iommu; - +	for_each_iommu(iommu, drhd) {  		if (!ecap_ir_support(iommu->ecap))  			continue; @@ -686,12 +689,12 @@ static void ir_parse_one_hpet_scope(struct acpi_dmar_device_scope *scope,  		 * Access PCI directly due to the PCI  		 * subsystem isn't initialized yet.  		 */ -		bus = read_pci_config_byte(bus, path->dev, path->fn, +		bus = read_pci_config_byte(bus, path->device, path->function,  					   PCI_SECONDARY_BUS);  		path++;  	}  	ir_hpet[ir_hpet_num].bus   = bus; -	ir_hpet[ir_hpet_num].devfn = PCI_DEVFN(path->dev, path->fn); +	ir_hpet[ir_hpet_num].devfn = PCI_DEVFN(path->device, path->function);  	ir_hpet[ir_hpet_num].iommu = iommu;  	ir_hpet[ir_hpet_num].id    = scope->enumeration_id;  	ir_hpet_num++; @@ -714,13 +717,13 @@ static void ir_parse_one_ioapic_scope(struct acpi_dmar_device_scope *scope,  		 * Access PCI directly due to the PCI  		 * subsystem isn't initialized yet.  		 */ -		bus = read_pci_config_byte(bus, path->dev, path->fn, +		bus = read_pci_config_byte(bus, path->device, path->function,  					   PCI_SECONDARY_BUS);  		path++;  	}  	ir_ioapic[ir_ioapic_num].bus   = bus; -	ir_ioapic[ir_ioapic_num].devfn = PCI_DEVFN(path->dev, path->fn); +	ir_ioapic[ir_ioapic_num].devfn = PCI_DEVFN(path->device, path->function);  	ir_ioapic[ir_ioapic_num].iommu = iommu;  	ir_ioapic[ir_ioapic_num].id    = scope->enumeration_id;  	ir_ioapic_num++; @@ -773,22 +776,20 @@ static int ir_parse_ioapic_hpet_scope(struct acpi_dmar_header *header,   * Finds the assocaition between IOAPIC's and its Interrupt-remapping   * hardware unit.   */ -int __init parse_ioapics_under_ir(void) +static int __init parse_ioapics_under_ir(void)  {  	struct dmar_drhd_unit *drhd; +	struct intel_iommu *iommu;  	int ir_supported = 0;  	int ioapic_idx; -	for_each_drhd_unit(drhd) { -		struct intel_iommu *iommu = drhd->iommu; - +	for_each_iommu(iommu, drhd)  		if (ecap_ir_support(iommu->ecap)) {  			if (ir_parse_ioapic_hpet_scope(drhd->hdr, iommu))  				return -1;  			ir_supported = 1;  		} -	}  	if (!ir_supported)  		return 0; @@ -806,12 +807,18 @@ int __init parse_ioapics_under_ir(void)  	return 1;  } -int __init ir_dev_scope_init(void) +static int __init ir_dev_scope_init(void)  { +	int ret; +  	if (!irq_remapping_enabled)  		return 0; -	return dmar_dev_scope_init(); +	down_write(&dmar_global_lock); +	ret = dmar_dev_scope_init(); +	up_write(&dmar_global_lock); + +	return ret;  }  rootfs_initcall(ir_dev_scope_init); @@ -892,23 +899,27 @@ static int intel_setup_ioapic_entry(int irq,  				    struct io_apic_irq_attr *attr)  {  	int ioapic_id = mpc_ioapic_id(attr->ioapic); -	struct intel_iommu *iommu = map_ioapic_to_ir(ioapic_id); +	struct intel_iommu *iommu;  	struct IR_IO_APIC_route_entry *entry;  	struct irte irte;  	int index; +	down_read(&dmar_global_lock); +	iommu = map_ioapic_to_ir(ioapic_id);  	if (!iommu) {  		pr_warn("No mapping iommu for ioapic %d\n", ioapic_id); -		return -ENODEV; -	} - -	entry = (struct IR_IO_APIC_route_entry *)route_entry; - -	index = alloc_irte(iommu, irq, 1); -	if (index < 0) { -		pr_warn("Failed to allocate IRTE for ioapic %d\n", ioapic_id); -		return -ENOMEM; +		index = -ENODEV; +	} else { +		index = alloc_irte(iommu, irq, 1); +		if (index < 0) { +			pr_warn("Failed to allocate IRTE for ioapic %d\n", +				ioapic_id); +			index = -ENOMEM; +		}  	} +	up_read(&dmar_global_lock); +	if (index < 0) +		return index;  	prepare_irte(&irte, vector, destination); @@ -927,6 +938,7 @@ static int intel_setup_ioapic_entry(int irq,  		irte.avail, irte.vector, irte.dest_id,  		irte.sid, irte.sq, irte.svt); +	entry = (struct IR_IO_APIC_route_entry *)route_entry;  	memset(entry, 0, sizeof(*entry));  	entry->index2	= (index >> 15) & 0x1; @@ -1057,20 +1069,23 @@ static int intel_msi_alloc_irq(struct pci_dev *dev, int irq, int nvec)  	struct intel_iommu *iommu;  	int index; +	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)); -		return -ENOENT; +		index = -ENOENT; +	} else { +		index = alloc_irte(iommu, irq, nvec); +		if (index < 0) { +			printk(KERN_ERR +			       "Unable to allocate %d IRTE for PCI %s\n", +			       nvec, pci_name(dev)); +			index = -ENOSPC; +		}  	} +	up_read(&dmar_global_lock); -	index = alloc_irte(iommu, irq, nvec); -	if (index < 0) { -		printk(KERN_ERR -		       "Unable to allocate %d IRTE for PCI %s\n", nvec, -		       pci_name(dev)); -		return -ENOSPC; -	}  	return index;  } @@ -1078,33 +1093,40 @@ static int intel_msi_setup_irq(struct pci_dev *pdev, unsigned int irq,  			       int index, int sub_handle)  {  	struct intel_iommu *iommu; +	int ret = -ENOENT; +	down_read(&dmar_global_lock);  	iommu = map_dev_to_ir(pdev); -	if (!iommu) -		return -ENOENT; -	/* -	 * setup the mapping between the irq and the IRTE -	 * base index, the sub_handle pointing to the -	 * appropriate interrupt remap table entry. -	 */ -	set_irte_irq(irq, iommu, index, sub_handle); +	if (iommu) { +		/* +		 * setup the mapping between the irq and the IRTE +		 * base index, the sub_handle pointing to the +		 * appropriate interrupt remap table entry. +		 */ +		set_irte_irq(irq, iommu, index, sub_handle); +		ret = 0; +	} +	up_read(&dmar_global_lock); -	return 0; +	return ret;  }  static int intel_setup_hpet_msi(unsigned int irq, unsigned int id)  { -	struct intel_iommu *iommu = map_hpet_to_ir(id); +	int ret = -1; +	struct intel_iommu *iommu;  	int index; -	if (!iommu) -		return -1; - -	index = alloc_irte(iommu, irq, 1); -	if (index < 0) -		return -1; +	down_read(&dmar_global_lock); +	iommu = map_hpet_to_ir(id); +	if (iommu) { +		index = alloc_irte(iommu, irq, 1); +		if (index >= 0) +			ret = 0; +	} +	up_read(&dmar_global_lock); -	return 0; +	return ret;  }  struct irq_remap_ops intel_irq_remap_ops = {  | 
