x86/ia64: intel-iommu: move to drivers/iommu/
This should ease finding similarities with different platforms, with the intention of solving problems once in a generic framework which everyone can use. Note: to move intel-iommu.c, the declaration of pci_find_upstream_pcie_bridge() has to move from drivers/pci/pci.h to include/linux/pci.h. This is handled in this patch, too. As suggested, also drop DMAR's EXPERIMENTAL tag while we're at it. Compile-tested on x86_64. Signed-off-by: Ohad Ben-Cohen <ohad@wizery.com> Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
This commit is contained in:
parent
29b68415e3
commit
166e9278a3
12 changed files with 62 additions and 80 deletions
|
|
@ -627,27 +627,6 @@ source "drivers/pci/hotplug/Kconfig"
|
|||
|
||||
source "drivers/pcmcia/Kconfig"
|
||||
|
||||
config DMAR
|
||||
bool "Support for DMA Remapping Devices (EXPERIMENTAL)"
|
||||
depends on IA64_GENERIC && ACPI && EXPERIMENTAL
|
||||
help
|
||||
DMA remapping (DMAR) devices support enables independent address
|
||||
translations for Direct Memory Access (DMA) from devices.
|
||||
These DMA remapping devices are reported via ACPI tables
|
||||
and include PCI device scope covered by these DMA
|
||||
remapping devices.
|
||||
|
||||
config DMAR_DEFAULT_ON
|
||||
def_bool y
|
||||
prompt "Enable DMA Remapping Devices by default"
|
||||
depends on DMAR
|
||||
help
|
||||
Selecting this option will enable a DMAR device at boot time if
|
||||
one is found. If this option is not selected, DMAR support can
|
||||
be enabled by passing intel_iommu=on to the kernel. It is
|
||||
recommended you say N here while the DMAR code remains
|
||||
experimental.
|
||||
|
||||
endmenu
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -1912,56 +1912,6 @@ config PCI_CNB20LE_QUIRK
|
|||
|
||||
You should say N unless you know you need this.
|
||||
|
||||
config DMAR
|
||||
bool "Support for DMA Remapping Devices (EXPERIMENTAL)"
|
||||
depends on PCI_MSI && ACPI && EXPERIMENTAL
|
||||
select IOMMU_API
|
||||
help
|
||||
DMA remapping (DMAR) devices support enables independent address
|
||||
translations for Direct Memory Access (DMA) from devices.
|
||||
These DMA remapping devices are reported via ACPI tables
|
||||
and include PCI device scope covered by these DMA
|
||||
remapping devices.
|
||||
|
||||
config DMAR_DEFAULT_ON
|
||||
def_bool y
|
||||
prompt "Enable DMA Remapping Devices by default"
|
||||
depends on DMAR
|
||||
help
|
||||
Selecting this option will enable a DMAR device at boot time if
|
||||
one is found. If this option is not selected, DMAR support can
|
||||
be enabled by passing intel_iommu=on to the kernel. It is
|
||||
recommended you say N here while the DMAR code remains
|
||||
experimental.
|
||||
|
||||
config DMAR_BROKEN_GFX_WA
|
||||
bool "Workaround broken graphics drivers (going away soon)"
|
||||
depends on DMAR && BROKEN
|
||||
---help---
|
||||
Current Graphics drivers tend to use physical address
|
||||
for DMA and avoid using DMA APIs. Setting this config
|
||||
option permits the IOMMU driver to set a unity map for
|
||||
all the OS-visible memory. Hence the driver can continue
|
||||
to use physical addresses for DMA, at least until this
|
||||
option is removed in the 2.6.32 kernel.
|
||||
|
||||
config DMAR_FLOPPY_WA
|
||||
def_bool y
|
||||
depends on DMAR
|
||||
---help---
|
||||
Floppy disk drivers are known to bypass DMA API calls
|
||||
thereby failing to work when IOMMU is enabled. This
|
||||
workaround will setup a 1:1 mapping for the first
|
||||
16MiB to make floppy (an ISA device) work.
|
||||
|
||||
config INTR_REMAP
|
||||
bool "Support for Interrupt Remapping (EXPERIMENTAL)"
|
||||
depends on X86_64 && X86_IO_APIC && PCI_MSI && ACPI && EXPERIMENTAL
|
||||
---help---
|
||||
Supports Interrupt remapping for IO-APIC and MSI devices.
|
||||
To use x2apic mode in the CPU's which support x2APIC enhancements or
|
||||
to support platforms with CPU's having > 8 bit APIC ID, say Y.
|
||||
|
||||
source "drivers/pci/pcie/Kconfig"
|
||||
|
||||
source "drivers/pci/Kconfig"
|
||||
|
|
|
|||
|
|
@ -46,3 +46,52 @@ config AMD_IOMMU_STATS
|
|||
statistics about whats happening in the driver and exports that
|
||||
information to userspace via debugfs.
|
||||
If unsure, say N.
|
||||
|
||||
# Intel IOMMU support
|
||||
config DMAR
|
||||
bool "Support for DMA Remapping Devices"
|
||||
depends on PCI_MSI && ACPI && (X86 || IA64_GENERIC)
|
||||
select IOMMU_API
|
||||
help
|
||||
DMA remapping (DMAR) devices support enables independent address
|
||||
translations for Direct Memory Access (DMA) from devices.
|
||||
These DMA remapping devices are reported via ACPI tables
|
||||
and include PCI device scope covered by these DMA
|
||||
remapping devices.
|
||||
|
||||
config DMAR_DEFAULT_ON
|
||||
def_bool y
|
||||
prompt "Enable DMA Remapping Devices by default"
|
||||
depends on DMAR
|
||||
help
|
||||
Selecting this option will enable a DMAR device at boot time if
|
||||
one is found. If this option is not selected, DMAR support can
|
||||
be enabled by passing intel_iommu=on to the kernel.
|
||||
|
||||
config DMAR_BROKEN_GFX_WA
|
||||
bool "Workaround broken graphics drivers (going away soon)"
|
||||
depends on DMAR && BROKEN && X86
|
||||
---help---
|
||||
Current Graphics drivers tend to use physical address
|
||||
for DMA and avoid using DMA APIs. Setting this config
|
||||
option permits the IOMMU driver to set a unity map for
|
||||
all the OS-visible memory. Hence the driver can continue
|
||||
to use physical addresses for DMA, at least until this
|
||||
option is removed in the 2.6.32 kernel.
|
||||
|
||||
config DMAR_FLOPPY_WA
|
||||
def_bool y
|
||||
depends on DMAR && X86
|
||||
---help---
|
||||
Floppy disk drivers are known to bypass DMA API calls
|
||||
thereby failing to work when IOMMU is enabled. This
|
||||
workaround will setup a 1:1 mapping for the first
|
||||
16MiB to make floppy (an ISA device) work.
|
||||
|
||||
config INTR_REMAP
|
||||
bool "Support for Interrupt Remapping (EXPERIMENTAL)"
|
||||
depends on X86_64 && X86_IO_APIC && PCI_MSI && ACPI && EXPERIMENTAL
|
||||
---help---
|
||||
Supports Interrupt remapping for IO-APIC and MSI devices.
|
||||
To use x2apic mode in the CPU's which support x2APIC enhancements or
|
||||
to support platforms with CPU's having > 8 bit APIC ID, say Y.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
obj-$(CONFIG_IOMMU_API) += iommu.o
|
||||
obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
|
||||
obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o
|
||||
obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
|
||||
obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
|
||||
|
|
|
|||
1461
drivers/iommu/dmar.c
Normal file
1461
drivers/iommu/dmar.c
Normal file
File diff suppressed because it is too large
Load diff
4016
drivers/iommu/intel-iommu.c
Normal file
4016
drivers/iommu/intel-iommu.c
Normal file
File diff suppressed because it is too large
Load diff
797
drivers/iommu/intr_remapping.c
Normal file
797
drivers/iommu/intr_remapping.c
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
#include <linux/interrupt.h>
|
||||
#include <linux/dmar.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/hpet.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/irq.h>
|
||||
#include <asm/io_apic.h>
|
||||
#include <asm/smp.h>
|
||||
#include <asm/cpu.h>
|
||||
#include <linux/intel-iommu.h>
|
||||
#include "intr_remapping.h"
|
||||
#include <acpi/acpi.h>
|
||||
#include <asm/pci-direct.h>
|
||||
|
||||
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;
|
||||
int intr_remapping_enabled;
|
||||
|
||||
static int disable_intremap;
|
||||
static int disable_sourceid_checking;
|
||||
|
||||
static __init int setup_nointremap(char *str)
|
||||
{
|
||||
disable_intremap = 1;
|
||||
return 0;
|
||||
}
|
||||
early_param("nointremap", setup_nointremap);
|
||||
|
||||
static __init int setup_intremap(char *str)
|
||||
{
|
||||
if (!str)
|
||||
return -EINVAL;
|
||||
|
||||
if (!strncmp(str, "on", 2))
|
||||
disable_intremap = 0;
|
||||
else if (!strncmp(str, "off", 3))
|
||||
disable_intremap = 1;
|
||||
else if (!strncmp(str, "nosid", 5))
|
||||
disable_sourceid_checking = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
early_param("intremap", setup_intremap);
|
||||
|
||||
static DEFINE_SPINLOCK(irq_2_ir_lock);
|
||||
|
||||
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)
|
||||
{
|
||||
struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);
|
||||
unsigned long flags;
|
||||
int index;
|
||||
|
||||
if (!entry || !irq_iommu)
|
||||
return -1;
|
||||
|
||||
spin_lock_irqsave(&irq_2_ir_lock, flags);
|
||||
|
||||
index = irq_iommu->irte_index + irq_iommu->sub_handle;
|
||||
*entry = *(irq_iommu->iommu->ir_table->base + index);
|
||||
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
u16 index, start_index;
|
||||
unsigned int mask = 0;
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (mask > ecap_max_handle_mask(iommu->ecap)) {
|
||||
printk(KERN_ERR
|
||||
"Requested mask %x exceeds the max invalidation handle"
|
||||
" mask value %Lx\n", mask,
|
||||
ecap_max_handle_mask(iommu->ecap));
|
||||
return -1;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
irq_iommu->iommu = iommu;
|
||||
irq_iommu->irte_index = index;
|
||||
irq_iommu->sub_handle = 0;
|
||||
irq_iommu->irte_mask = mask;
|
||||
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static int qi_flush_iec(struct intel_iommu *iommu, int index, int mask)
|
||||
{
|
||||
struct qi_desc desc;
|
||||
|
||||
desc.low = QI_IEC_IIDEX(index) | QI_IEC_TYPE | QI_IEC_IM(mask)
|
||||
| QI_IEC_SELECTIVE;
|
||||
desc.high = 0;
|
||||
|
||||
return qi_submit_sync(&desc, iommu);
|
||||
}
|
||||
|
||||
int map_irq_to_irte_handle(int irq, u16 *sub_handle)
|
||||
{
|
||||
struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);
|
||||
unsigned long flags;
|
||||
int index;
|
||||
|
||||
if (!irq_iommu)
|
||||
return -1;
|
||||
|
||||
spin_lock_irqsave(&irq_2_ir_lock, flags);
|
||||
*sub_handle = irq_iommu->sub_handle;
|
||||
index = irq_iommu->irte_index;
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
return index;
|
||||
}
|
||||
|
||||
int set_irte_irq(int irq, struct intel_iommu *iommu, u16 index, u16 subhandle)
|
||||
{
|
||||
struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);
|
||||
unsigned long flags;
|
||||
|
||||
if (!irq_iommu)
|
||||
return -1;
|
||||
|
||||
spin_lock_irqsave(&irq_2_ir_lock, flags);
|
||||
|
||||
irq_iommu->iommu = iommu;
|
||||
irq_iommu->irte_index = index;
|
||||
irq_iommu->sub_handle = subhandle;
|
||||
irq_iommu->irte_mask = 0;
|
||||
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int modify_irte(int irq, struct irte *irte_modified)
|
||||
{
|
||||
struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);
|
||||
struct intel_iommu *iommu;
|
||||
unsigned long flags;
|
||||
struct irte *irte;
|
||||
int rc, index;
|
||||
|
||||
if (!irq_iommu)
|
||||
return -1;
|
||||
|
||||
spin_lock_irqsave(&irq_2_ir_lock, flags);
|
||||
|
||||
iommu = irq_iommu->iommu;
|
||||
|
||||
index = irq_iommu->irte_index + irq_iommu->sub_handle;
|
||||
irte = &iommu->ir_table->base[index];
|
||||
|
||||
set_64bit(&irte->low, irte_modified->low);
|
||||
set_64bit(&irte->high, irte_modified->high);
|
||||
__iommu_flush_cache(iommu, irte, sizeof(*irte));
|
||||
|
||||
rc = qi_flush_iec(iommu, index, 0);
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
struct intel_iommu *map_hpet_to_ir(u8 hpet_id)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_HPET_TBS; i++)
|
||||
if (ir_hpet[i].id == hpet_id)
|
||||
return ir_hpet[i].iommu;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct intel_iommu *map_ioapic_to_ir(int apic)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_IO_APICS; i++)
|
||||
if (ir_ioapic[i].id == apic)
|
||||
return ir_ioapic[i].iommu;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct intel_iommu *map_dev_to_ir(struct pci_dev *dev)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
|
||||
drhd = dmar_find_matched_drhd_unit(dev);
|
||||
if (!drhd)
|
||||
return NULL;
|
||||
|
||||
return drhd->iommu;
|
||||
}
|
||||
|
||||
static int clear_entries(struct irq_2_iommu *irq_iommu)
|
||||
{
|
||||
struct irte *start, *entry, *end;
|
||||
struct intel_iommu *iommu;
|
||||
int index;
|
||||
|
||||
if (irq_iommu->sub_handle)
|
||||
return 0;
|
||||
|
||||
iommu = irq_iommu->iommu;
|
||||
index = irq_iommu->irte_index + irq_iommu->sub_handle;
|
||||
|
||||
start = iommu->ir_table->base + index;
|
||||
end = start + (1 << irq_iommu->irte_mask);
|
||||
|
||||
for (entry = start; entry < end; entry++) {
|
||||
set_64bit(&entry->low, 0);
|
||||
set_64bit(&entry->high, 0);
|
||||
}
|
||||
|
||||
return qi_flush_iec(iommu, index, irq_iommu->irte_mask);
|
||||
}
|
||||
|
||||
int free_irte(int irq)
|
||||
{
|
||||
struct irq_2_iommu *irq_iommu = irq_2_iommu(irq);
|
||||
unsigned long flags;
|
||||
int rc;
|
||||
|
||||
if (!irq_iommu)
|
||||
return -1;
|
||||
|
||||
spin_lock_irqsave(&irq_2_ir_lock, flags);
|
||||
|
||||
rc = clear_entries(irq_iommu);
|
||||
|
||||
irq_iommu->iommu = NULL;
|
||||
irq_iommu->irte_index = 0;
|
||||
irq_iommu->sub_handle = 0;
|
||||
irq_iommu->irte_mask = 0;
|
||||
|
||||
spin_unlock_irqrestore(&irq_2_ir_lock, flags);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* source validation type
|
||||
*/
|
||||
#define SVT_NO_VERIFY 0x0 /* no verification is required */
|
||||
#define SVT_VERIFY_SID_SQ 0x1 /* verify using SID and SQ fields */
|
||||
#define SVT_VERIFY_BUS 0x2 /* verify bus of request-id */
|
||||
|
||||
/*
|
||||
* source-id qualifier
|
||||
*/
|
||||
#define SQ_ALL_16 0x0 /* verify all 16 bits of request-id */
|
||||
#define SQ_13_IGNORE_1 0x1 /* verify most significant 13 bits, ignore
|
||||
* the third least significant bit
|
||||
*/
|
||||
#define SQ_13_IGNORE_2 0x2 /* verify most significant 13 bits, ignore
|
||||
* the second and third least significant bits
|
||||
*/
|
||||
#define SQ_13_IGNORE_3 0x3 /* verify most significant 13 bits, ignore
|
||||
* the least three significant bits
|
||||
*/
|
||||
|
||||
/*
|
||||
* set SVT, SQ and SID fields of irte to verify
|
||||
* source ids of interrupt requests
|
||||
*/
|
||||
static void set_irte_sid(struct irte *irte, unsigned int svt,
|
||||
unsigned int sq, unsigned int sid)
|
||||
{
|
||||
if (disable_sourceid_checking)
|
||||
svt = SVT_NO_VERIFY;
|
||||
irte->svt = svt;
|
||||
irte->sq = sq;
|
||||
irte->sid = sid;
|
||||
}
|
||||
|
||||
int set_ioapic_sid(struct irte *irte, int apic)
|
||||
{
|
||||
int i;
|
||||
u16 sid = 0;
|
||||
|
||||
if (!irte)
|
||||
return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (sid == 0) {
|
||||
pr_warning("Failed to set source-id of IOAPIC (%d)\n", apic);
|
||||
return -1;
|
||||
}
|
||||
|
||||
set_irte_sid(irte, 1, 0, sid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_hpet_sid(struct irte *irte, u8 id)
|
||||
{
|
||||
int i;
|
||||
u16 sid = 0;
|
||||
|
||||
if (!irte)
|
||||
return -1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (sid == 0) {
|
||||
pr_warning("Failed to set source-id of HPET block (%d)\n", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Should really use SQ_ALL_16. Some platforms are broken.
|
||||
* While we figure out the right quirks for these broken platforms, use
|
||||
* SQ_13_IGNORE_3 for now.
|
||||
*/
|
||||
set_irte_sid(irte, SVT_VERIFY_SID_SQ, SQ_13_IGNORE_3, sid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int set_msi_sid(struct irte *irte, struct pci_dev *dev)
|
||||
{
|
||||
struct pci_dev *bridge;
|
||||
|
||||
if (!irte || !dev)
|
||||
return -1;
|
||||
|
||||
/* PCIe device or Root Complex integrated PCI device */
|
||||
if (pci_is_pcie(dev) || !dev->bus->parent) {
|
||||
set_irte_sid(irte, SVT_VERIFY_SID_SQ, SQ_ALL_16,
|
||||
(dev->bus->number << 8) | dev->devfn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bridge = pci_find_upstream_pcie_bridge(dev);
|
||||
if (bridge) {
|
||||
if (pci_is_pcie(bridge))/* this is a PCIe-to-PCI/PCIX bridge */
|
||||
set_irte_sid(irte, SVT_VERIFY_BUS, SQ_ALL_16,
|
||||
(bridge->bus->number << 8) | dev->bus->number);
|
||||
else /* this is a legacy PCI bridge */
|
||||
set_irte_sid(irte, SVT_VERIFY_SID_SQ, SQ_ALL_16,
|
||||
(bridge->bus->number << 8) | bridge->devfn);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void iommu_set_intr_remapping(struct intel_iommu *iommu, int mode)
|
||||
{
|
||||
u64 addr;
|
||||
u32 sts;
|
||||
unsigned long flags;
|
||||
|
||||
addr = virt_to_phys((void *)iommu->ir_table->base);
|
||||
|
||||
spin_lock_irqsave(&iommu->register_lock, flags);
|
||||
|
||||
dmar_writeq(iommu->reg + DMAR_IRTA_REG,
|
||||
(addr) | IR_X2APIC_MODE(mode) | INTR_REMAP_TABLE_REG_SIZE);
|
||||
|
||||
/* Set interrupt-remapping table pointer */
|
||||
iommu->gcmd |= DMA_GCMD_SIRTP;
|
||||
writel(iommu->gcmd, iommu->reg + DMAR_GCMD_REG);
|
||||
|
||||
IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
|
||||
readl, (sts & DMA_GSTS_IRTPS), sts);
|
||||
spin_unlock_irqrestore(&iommu->register_lock, flags);
|
||||
|
||||
/*
|
||||
* global invalidation of interrupt entry cache before enabling
|
||||
* interrupt-remapping.
|
||||
*/
|
||||
qi_global_iec(iommu);
|
||||
|
||||
spin_lock_irqsave(&iommu->register_lock, flags);
|
||||
|
||||
/* Enable interrupt-remapping */
|
||||
iommu->gcmd |= DMA_GCMD_IRE;
|
||||
writel(iommu->gcmd, iommu->reg + DMAR_GCMD_REG);
|
||||
|
||||
IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
|
||||
readl, (sts & DMA_GSTS_IRES), sts);
|
||||
|
||||
spin_unlock_irqrestore(&iommu->register_lock, flags);
|
||||
}
|
||||
|
||||
|
||||
static int setup_intr_remapping(struct intel_iommu *iommu, int mode)
|
||||
{
|
||||
struct ir_table *ir_table;
|
||||
struct page *pages;
|
||||
|
||||
ir_table = iommu->ir_table = kzalloc(sizeof(struct ir_table),
|
||||
GFP_ATOMIC);
|
||||
|
||||
if (!iommu->ir_table)
|
||||
return -ENOMEM;
|
||||
|
||||
pages = alloc_pages_node(iommu->node, GFP_ATOMIC | __GFP_ZERO,
|
||||
INTR_REMAP_PAGE_ORDER);
|
||||
|
||||
if (!pages) {
|
||||
printk(KERN_ERR "failed to allocate pages of order %d\n",
|
||||
INTR_REMAP_PAGE_ORDER);
|
||||
kfree(iommu->ir_table);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ir_table->base = page_address(pages);
|
||||
|
||||
iommu_set_intr_remapping(iommu, mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disable Interrupt Remapping.
|
||||
*/
|
||||
static void iommu_disable_intr_remapping(struct intel_iommu *iommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
u32 sts;
|
||||
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
return;
|
||||
|
||||
/*
|
||||
* global invalidation of interrupt entry cache before disabling
|
||||
* interrupt-remapping.
|
||||
*/
|
||||
qi_global_iec(iommu);
|
||||
|
||||
spin_lock_irqsave(&iommu->register_lock, flags);
|
||||
|
||||
sts = dmar_readq(iommu->reg + DMAR_GSTS_REG);
|
||||
if (!(sts & DMA_GSTS_IRES))
|
||||
goto end;
|
||||
|
||||
iommu->gcmd &= ~DMA_GCMD_IRE;
|
||||
writel(iommu->gcmd, iommu->reg + DMAR_GCMD_REG);
|
||||
|
||||
IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
|
||||
readl, !(sts & DMA_GSTS_IRES), sts);
|
||||
|
||||
end:
|
||||
spin_unlock_irqrestore(&iommu->register_lock, flags);
|
||||
}
|
||||
|
||||
int __init intr_remapping_supported(void)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
|
||||
if (disable_intremap)
|
||||
return 0;
|
||||
|
||||
if (!dmar_ir_support())
|
||||
return 0;
|
||||
|
||||
for_each_drhd_unit(drhd) {
|
||||
struct intel_iommu *iommu = drhd->iommu;
|
||||
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __init enable_intr_remapping(int eim)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
int setup = 0;
|
||||
|
||||
if (parse_ioapics_under_ir() != 1) {
|
||||
printk(KERN_INFO "Not enable interrupt remapping\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for_each_drhd_unit(drhd) {
|
||||
struct intel_iommu *iommu = drhd->iommu;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
iommu_disable_intr_remapping(iommu);
|
||||
|
||||
dmar_disable_qi(iommu);
|
||||
}
|
||||
|
||||
/*
|
||||
* check for the Interrupt-remapping support
|
||||
*/
|
||||
for_each_drhd_unit(drhd) {
|
||||
struct intel_iommu *iommu = drhd->iommu;
|
||||
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
continue;
|
||||
|
||||
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);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "DRHD %Lx: failed to enable queued, "
|
||||
" invalidation, ecap %Lx, ret %d\n",
|
||||
drhd->reg_base_addr, iommu->ecap, ret);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Setup Interrupt-remapping for all the DRHD's now.
|
||||
*/
|
||||
for_each_drhd_unit(drhd) {
|
||||
struct intel_iommu *iommu = drhd->iommu;
|
||||
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
continue;
|
||||
|
||||
if (setup_intr_remapping(iommu, eim))
|
||||
goto error;
|
||||
|
||||
setup = 1;
|
||||
}
|
||||
|
||||
if (!setup)
|
||||
goto error;
|
||||
|
||||
intr_remapping_enabled = 1;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
/*
|
||||
* handle error condition gracefully here!
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ir_parse_one_hpet_scope(struct acpi_dmar_device_scope *scope,
|
||||
struct intel_iommu *iommu)
|
||||
{
|
||||
struct acpi_dmar_pci_path *path;
|
||||
u8 bus;
|
||||
int count;
|
||||
|
||||
bus = scope->bus;
|
||||
path = (struct acpi_dmar_pci_path *)(scope + 1);
|
||||
count = (scope->length - sizeof(struct acpi_dmar_device_scope))
|
||||
/ sizeof(struct acpi_dmar_pci_path);
|
||||
|
||||
while (--count > 0) {
|
||||
/*
|
||||
* Access PCI directly due to the PCI
|
||||
* subsystem isn't initialized yet.
|
||||
*/
|
||||
bus = read_pci_config_byte(bus, path->dev, path->fn,
|
||||
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].iommu = iommu;
|
||||
ir_hpet[ir_hpet_num].id = scope->enumeration_id;
|
||||
ir_hpet_num++;
|
||||
}
|
||||
|
||||
static void ir_parse_one_ioapic_scope(struct acpi_dmar_device_scope *scope,
|
||||
struct intel_iommu *iommu)
|
||||
{
|
||||
struct acpi_dmar_pci_path *path;
|
||||
u8 bus;
|
||||
int count;
|
||||
|
||||
bus = scope->bus;
|
||||
path = (struct acpi_dmar_pci_path *)(scope + 1);
|
||||
count = (scope->length - sizeof(struct acpi_dmar_device_scope))
|
||||
/ sizeof(struct acpi_dmar_pci_path);
|
||||
|
||||
while (--count > 0) {
|
||||
/*
|
||||
* Access PCI directly due to the PCI
|
||||
* subsystem isn't initialized yet.
|
||||
*/
|
||||
bus = read_pci_config_byte(bus, path->dev, path->fn,
|
||||
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].iommu = iommu;
|
||||
ir_ioapic[ir_ioapic_num].id = scope->enumeration_id;
|
||||
ir_ioapic_num++;
|
||||
}
|
||||
|
||||
static int ir_parse_ioapic_hpet_scope(struct acpi_dmar_header *header,
|
||||
struct intel_iommu *iommu)
|
||||
{
|
||||
struct acpi_dmar_hardware_unit *drhd;
|
||||
struct acpi_dmar_device_scope *scope;
|
||||
void *start, *end;
|
||||
|
||||
drhd = (struct acpi_dmar_hardware_unit *)header;
|
||||
|
||||
start = (void *)(drhd + 1);
|
||||
end = ((void *)drhd) + header->length;
|
||||
|
||||
while (start < end) {
|
||||
scope = start;
|
||||
if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_IOAPIC) {
|
||||
if (ir_ioapic_num == MAX_IO_APICS) {
|
||||
printk(KERN_WARNING "Exceeded Max IO APICS\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "IOAPIC id %d under DRHD base "
|
||||
" 0x%Lx IOMMU %d\n", scope->enumeration_id,
|
||||
drhd->address, iommu->seq_id);
|
||||
|
||||
ir_parse_one_ioapic_scope(scope, iommu);
|
||||
} else if (scope->entry_type == ACPI_DMAR_SCOPE_TYPE_HPET) {
|
||||
if (ir_hpet_num == MAX_HPET_TBS) {
|
||||
printk(KERN_WARNING "Exceeded Max HPET blocks\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "HPET id %d under DRHD base"
|
||||
" 0x%Lx\n", scope->enumeration_id,
|
||||
drhd->address);
|
||||
|
||||
ir_parse_one_hpet_scope(scope, iommu);
|
||||
}
|
||||
start += scope->length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the assocaition between IOAPIC's and its Interrupt-remapping
|
||||
* hardware unit.
|
||||
*/
|
||||
int __init parse_ioapics_under_ir(void)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
int ir_supported = 0;
|
||||
|
||||
for_each_drhd_unit(drhd) {
|
||||
struct intel_iommu *iommu = drhd->iommu;
|
||||
|
||||
if (ecap_ir_support(iommu->ecap)) {
|
||||
if (ir_parse_ioapic_hpet_scope(drhd->hdr, iommu))
|
||||
return -1;
|
||||
|
||||
ir_supported = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (ir_supported && ir_ioapic_num != nr_ioapics) {
|
||||
printk(KERN_WARNING
|
||||
"Not all IO-APIC's listed under remapping hardware\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ir_supported;
|
||||
}
|
||||
|
||||
void disable_intr_remapping(void)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
struct intel_iommu *iommu = NULL;
|
||||
|
||||
/*
|
||||
* Disable Interrupt-remapping for all the DRHD's now.
|
||||
*/
|
||||
for_each_iommu(iommu, drhd) {
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
continue;
|
||||
|
||||
iommu_disable_intr_remapping(iommu);
|
||||
}
|
||||
}
|
||||
|
||||
int reenable_intr_remapping(int eim)
|
||||
{
|
||||
struct dmar_drhd_unit *drhd;
|
||||
int setup = 0;
|
||||
struct intel_iommu *iommu = NULL;
|
||||
|
||||
for_each_iommu(iommu, drhd)
|
||||
if (iommu->qi)
|
||||
dmar_reenable_qi(iommu);
|
||||
|
||||
/*
|
||||
* Setup Interrupt-remapping for all the DRHD's now.
|
||||
*/
|
||||
for_each_iommu(iommu, drhd) {
|
||||
if (!ecap_ir_support(iommu->ecap))
|
||||
continue;
|
||||
|
||||
/* Set up interrupt remapping for iommu.*/
|
||||
iommu_set_intr_remapping(iommu, eim);
|
||||
setup = 1;
|
||||
}
|
||||
|
||||
if (!setup)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
/*
|
||||
* handle error condition gracefully here!
|
||||
*/
|
||||
return -1;
|
||||
}
|
||||
|
||||
17
drivers/iommu/intr_remapping.h
Normal file
17
drivers/iommu/intr_remapping.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include <linux/intel-iommu.h>
|
||||
|
||||
struct ioapic_scope {
|
||||
struct intel_iommu *iommu;
|
||||
unsigned int id;
|
||||
unsigned int bus; /* PCI bus number */
|
||||
unsigned int devfn; /* PCI devfn number */
|
||||
};
|
||||
|
||||
struct hpet_scope {
|
||||
struct intel_iommu *iommu;
|
||||
u8 id;
|
||||
unsigned int bus;
|
||||
unsigned int devfn;
|
||||
};
|
||||
|
||||
#define IR_X2APIC_MODE(mode) (mode ? (1 << 11) : 0)
|
||||
435
drivers/iommu/iova.c
Normal file
435
drivers/iommu/iova.c
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
/*
|
||||
* Copyright © 2006-2009, 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
* Place - Suite 330, Boston, MA 02111-1307 USA.
|
||||
*
|
||||
* Author: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/iova.h>
|
||||
|
||||
void
|
||||
init_iova_domain(struct iova_domain *iovad, unsigned long pfn_32bit)
|
||||
{
|
||||
spin_lock_init(&iovad->iova_rbtree_lock);
|
||||
iovad->rbroot = RB_ROOT;
|
||||
iovad->cached32_node = NULL;
|
||||
iovad->dma_32bit_pfn = pfn_32bit;
|
||||
}
|
||||
|
||||
static struct rb_node *
|
||||
__get_cached_rbnode(struct iova_domain *iovad, unsigned long *limit_pfn)
|
||||
{
|
||||
if ((*limit_pfn != iovad->dma_32bit_pfn) ||
|
||||
(iovad->cached32_node == NULL))
|
||||
return rb_last(&iovad->rbroot);
|
||||
else {
|
||||
struct rb_node *prev_node = rb_prev(iovad->cached32_node);
|
||||
struct iova *curr_iova =
|
||||
container_of(iovad->cached32_node, struct iova, node);
|
||||
*limit_pfn = curr_iova->pfn_lo - 1;
|
||||
return prev_node;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
__cached_rbnode_insert_update(struct iova_domain *iovad,
|
||||
unsigned long limit_pfn, struct iova *new)
|
||||
{
|
||||
if (limit_pfn != iovad->dma_32bit_pfn)
|
||||
return;
|
||||
iovad->cached32_node = &new->node;
|
||||
}
|
||||
|
||||
static void
|
||||
__cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free)
|
||||
{
|
||||
struct iova *cached_iova;
|
||||
struct rb_node *curr;
|
||||
|
||||
if (!iovad->cached32_node)
|
||||
return;
|
||||
curr = iovad->cached32_node;
|
||||
cached_iova = container_of(curr, struct iova, node);
|
||||
|
||||
if (free->pfn_lo >= cached_iova->pfn_lo) {
|
||||
struct rb_node *node = rb_next(&free->node);
|
||||
struct iova *iova = container_of(node, struct iova, node);
|
||||
|
||||
/* only cache if it's below 32bit pfn */
|
||||
if (node && iova->pfn_lo < iovad->dma_32bit_pfn)
|
||||
iovad->cached32_node = node;
|
||||
else
|
||||
iovad->cached32_node = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Computes the padding size required, to make the
|
||||
* the start address naturally aligned on its size
|
||||
*/
|
||||
static int
|
||||
iova_get_pad_size(int size, unsigned int limit_pfn)
|
||||
{
|
||||
unsigned int pad_size = 0;
|
||||
unsigned int order = ilog2(size);
|
||||
|
||||
if (order)
|
||||
pad_size = (limit_pfn + 1) % (1 << order);
|
||||
|
||||
return pad_size;
|
||||
}
|
||||
|
||||
static int __alloc_and_insert_iova_range(struct iova_domain *iovad,
|
||||
unsigned long size, unsigned long limit_pfn,
|
||||
struct iova *new, bool size_aligned)
|
||||
{
|
||||
struct rb_node *prev, *curr = NULL;
|
||||
unsigned long flags;
|
||||
unsigned long saved_pfn;
|
||||
unsigned int pad_size = 0;
|
||||
|
||||
/* Walk the tree backwards */
|
||||
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
|
||||
saved_pfn = limit_pfn;
|
||||
curr = __get_cached_rbnode(iovad, &limit_pfn);
|
||||
prev = curr;
|
||||
while (curr) {
|
||||
struct iova *curr_iova = container_of(curr, struct iova, node);
|
||||
|
||||
if (limit_pfn < curr_iova->pfn_lo)
|
||||
goto move_left;
|
||||
else if (limit_pfn < curr_iova->pfn_hi)
|
||||
goto adjust_limit_pfn;
|
||||
else {
|
||||
if (size_aligned)
|
||||
pad_size = iova_get_pad_size(size, limit_pfn);
|
||||
if ((curr_iova->pfn_hi + size + pad_size) <= limit_pfn)
|
||||
break; /* found a free slot */
|
||||
}
|
||||
adjust_limit_pfn:
|
||||
limit_pfn = curr_iova->pfn_lo - 1;
|
||||
move_left:
|
||||
prev = curr;
|
||||
curr = rb_prev(curr);
|
||||
}
|
||||
|
||||
if (!curr) {
|
||||
if (size_aligned)
|
||||
pad_size = iova_get_pad_size(size, limit_pfn);
|
||||
if ((IOVA_START_PFN + size + pad_size) > limit_pfn) {
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
/* pfn_lo will point to size aligned address if size_aligned is set */
|
||||
new->pfn_lo = limit_pfn - (size + pad_size) + 1;
|
||||
new->pfn_hi = new->pfn_lo + size - 1;
|
||||
|
||||
/* Insert the new_iova into domain rbtree by holding writer lock */
|
||||
/* Add new node and rebalance tree. */
|
||||
{
|
||||
struct rb_node **entry, *parent = NULL;
|
||||
|
||||
/* If we have 'prev', it's a valid place to start the
|
||||
insertion. Otherwise, start from the root. */
|
||||
if (prev)
|
||||
entry = &prev;
|
||||
else
|
||||
entry = &iovad->rbroot.rb_node;
|
||||
|
||||
/* Figure out where to put new node */
|
||||
while (*entry) {
|
||||
struct iova *this = container_of(*entry,
|
||||
struct iova, node);
|
||||
parent = *entry;
|
||||
|
||||
if (new->pfn_lo < this->pfn_lo)
|
||||
entry = &((*entry)->rb_left);
|
||||
else if (new->pfn_lo > this->pfn_lo)
|
||||
entry = &((*entry)->rb_right);
|
||||
else
|
||||
BUG(); /* this should not happen */
|
||||
}
|
||||
|
||||
/* Add new node and rebalance tree. */
|
||||
rb_link_node(&new->node, parent, entry);
|
||||
rb_insert_color(&new->node, &iovad->rbroot);
|
||||
}
|
||||
__cached_rbnode_insert_update(iovad, saved_pfn, new);
|
||||
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
iova_insert_rbtree(struct rb_root *root, struct iova *iova)
|
||||
{
|
||||
struct rb_node **new = &(root->rb_node), *parent = NULL;
|
||||
/* Figure out where to put new node */
|
||||
while (*new) {
|
||||
struct iova *this = container_of(*new, struct iova, node);
|
||||
parent = *new;
|
||||
|
||||
if (iova->pfn_lo < this->pfn_lo)
|
||||
new = &((*new)->rb_left);
|
||||
else if (iova->pfn_lo > this->pfn_lo)
|
||||
new = &((*new)->rb_right);
|
||||
else
|
||||
BUG(); /* this should not happen */
|
||||
}
|
||||
/* Add new node and rebalance tree. */
|
||||
rb_link_node(&iova->node, parent, new);
|
||||
rb_insert_color(&iova->node, root);
|
||||
}
|
||||
|
||||
/**
|
||||
* alloc_iova - allocates an iova
|
||||
* @iovad - iova domain in question
|
||||
* @size - size of page frames to allocate
|
||||
* @limit_pfn - max limit address
|
||||
* @size_aligned - set if size_aligned address range is required
|
||||
* This function allocates an iova in the range limit_pfn to IOVA_START_PFN
|
||||
* looking from limit_pfn instead from IOVA_START_PFN. If the size_aligned
|
||||
* flag is set then the allocated address iova->pfn_lo will be naturally
|
||||
* aligned on roundup_power_of_two(size).
|
||||
*/
|
||||
struct iova *
|
||||
alloc_iova(struct iova_domain *iovad, unsigned long size,
|
||||
unsigned long limit_pfn,
|
||||
bool size_aligned)
|
||||
{
|
||||
struct iova *new_iova;
|
||||
int ret;
|
||||
|
||||
new_iova = alloc_iova_mem();
|
||||
if (!new_iova)
|
||||
return NULL;
|
||||
|
||||
/* If size aligned is set then round the size to
|
||||
* to next power of two.
|
||||
*/
|
||||
if (size_aligned)
|
||||
size = __roundup_pow_of_two(size);
|
||||
|
||||
ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn,
|
||||
new_iova, size_aligned);
|
||||
|
||||
if (ret) {
|
||||
free_iova_mem(new_iova);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new_iova;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_iova - find's an iova for a given pfn
|
||||
* @iovad - iova domain in question.
|
||||
* pfn - page frame number
|
||||
* This function finds and returns an iova belonging to the
|
||||
* given doamin which matches the given pfn.
|
||||
*/
|
||||
struct iova *find_iova(struct iova_domain *iovad, unsigned long pfn)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rb_node *node;
|
||||
|
||||
/* Take the lock so that no other thread is manipulating the rbtree */
|
||||
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
|
||||
node = iovad->rbroot.rb_node;
|
||||
while (node) {
|
||||
struct iova *iova = container_of(node, struct iova, node);
|
||||
|
||||
/* If pfn falls within iova's range, return iova */
|
||||
if ((pfn >= iova->pfn_lo) && (pfn <= iova->pfn_hi)) {
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
/* We are not holding the lock while this iova
|
||||
* is referenced by the caller as the same thread
|
||||
* which called this function also calls __free_iova()
|
||||
* and it is by desing that only one thread can possibly
|
||||
* reference a particular iova and hence no conflict.
|
||||
*/
|
||||
return iova;
|
||||
}
|
||||
|
||||
if (pfn < iova->pfn_lo)
|
||||
node = node->rb_left;
|
||||
else if (pfn > iova->pfn_lo)
|
||||
node = node->rb_right;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* __free_iova - frees the given iova
|
||||
* @iovad: iova domain in question.
|
||||
* @iova: iova in question.
|
||||
* Frees the given iova belonging to the giving domain
|
||||
*/
|
||||
void
|
||||
__free_iova(struct iova_domain *iovad, struct iova *iova)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
|
||||
__cached_rbnode_delete_update(iovad, iova);
|
||||
rb_erase(&iova->node, &iovad->rbroot);
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
free_iova_mem(iova);
|
||||
}
|
||||
|
||||
/**
|
||||
* free_iova - finds and frees the iova for a given pfn
|
||||
* @iovad: - iova domain in question.
|
||||
* @pfn: - pfn that is allocated previously
|
||||
* This functions finds an iova for a given pfn and then
|
||||
* frees the iova from that domain.
|
||||
*/
|
||||
void
|
||||
free_iova(struct iova_domain *iovad, unsigned long pfn)
|
||||
{
|
||||
struct iova *iova = find_iova(iovad, pfn);
|
||||
if (iova)
|
||||
__free_iova(iovad, iova);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* put_iova_domain - destroys the iova doamin
|
||||
* @iovad: - iova domain in question.
|
||||
* All the iova's in that domain are destroyed.
|
||||
*/
|
||||
void put_iova_domain(struct iova_domain *iovad)
|
||||
{
|
||||
struct rb_node *node;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
|
||||
node = rb_first(&iovad->rbroot);
|
||||
while (node) {
|
||||
struct iova *iova = container_of(node, struct iova, node);
|
||||
rb_erase(node, &iovad->rbroot);
|
||||
free_iova_mem(iova);
|
||||
node = rb_first(&iovad->rbroot);
|
||||
}
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
}
|
||||
|
||||
static int
|
||||
__is_range_overlap(struct rb_node *node,
|
||||
unsigned long pfn_lo, unsigned long pfn_hi)
|
||||
{
|
||||
struct iova *iova = container_of(node, struct iova, node);
|
||||
|
||||
if ((pfn_lo <= iova->pfn_hi) && (pfn_hi >= iova->pfn_lo))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct iova *
|
||||
__insert_new_range(struct iova_domain *iovad,
|
||||
unsigned long pfn_lo, unsigned long pfn_hi)
|
||||
{
|
||||
struct iova *iova;
|
||||
|
||||
iova = alloc_iova_mem();
|
||||
if (!iova)
|
||||
return iova;
|
||||
|
||||
iova->pfn_hi = pfn_hi;
|
||||
iova->pfn_lo = pfn_lo;
|
||||
iova_insert_rbtree(&iovad->rbroot, iova);
|
||||
return iova;
|
||||
}
|
||||
|
||||
static void
|
||||
__adjust_overlap_range(struct iova *iova,
|
||||
unsigned long *pfn_lo, unsigned long *pfn_hi)
|
||||
{
|
||||
if (*pfn_lo < iova->pfn_lo)
|
||||
iova->pfn_lo = *pfn_lo;
|
||||
if (*pfn_hi > iova->pfn_hi)
|
||||
*pfn_lo = iova->pfn_hi + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* reserve_iova - reserves an iova in the given range
|
||||
* @iovad: - iova domain pointer
|
||||
* @pfn_lo: - lower page frame address
|
||||
* @pfn_hi:- higher pfn adderss
|
||||
* This function allocates reserves the address range from pfn_lo to pfn_hi so
|
||||
* that this address is not dished out as part of alloc_iova.
|
||||
*/
|
||||
struct iova *
|
||||
reserve_iova(struct iova_domain *iovad,
|
||||
unsigned long pfn_lo, unsigned long pfn_hi)
|
||||
{
|
||||
struct rb_node *node;
|
||||
unsigned long flags;
|
||||
struct iova *iova;
|
||||
unsigned int overlap = 0;
|
||||
|
||||
spin_lock_irqsave(&iovad->iova_rbtree_lock, flags);
|
||||
for (node = rb_first(&iovad->rbroot); node; node = rb_next(node)) {
|
||||
if (__is_range_overlap(node, pfn_lo, pfn_hi)) {
|
||||
iova = container_of(node, struct iova, node);
|
||||
__adjust_overlap_range(iova, &pfn_lo, &pfn_hi);
|
||||
if ((pfn_lo >= iova->pfn_lo) &&
|
||||
(pfn_hi <= iova->pfn_hi))
|
||||
goto finish;
|
||||
overlap = 1;
|
||||
|
||||
} else if (overlap)
|
||||
break;
|
||||
}
|
||||
|
||||
/* We are here either because this is the first reserver node
|
||||
* or need to insert remaining non overlap addr range
|
||||
*/
|
||||
iova = __insert_new_range(iovad, pfn_lo, pfn_hi);
|
||||
finish:
|
||||
|
||||
spin_unlock_irqrestore(&iovad->iova_rbtree_lock, flags);
|
||||
return iova;
|
||||
}
|
||||
|
||||
/**
|
||||
* copy_reserved_iova - copies the reserved between domains
|
||||
* @from: - source doamin from where to copy
|
||||
* @to: - destination domin where to copy
|
||||
* This function copies reserved iova's from one doamin to
|
||||
* other.
|
||||
*/
|
||||
void
|
||||
copy_reserved_iova(struct iova_domain *from, struct iova_domain *to)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct rb_node *node;
|
||||
|
||||
spin_lock_irqsave(&from->iova_rbtree_lock, flags);
|
||||
for (node = rb_first(&from->rbroot); node; node = rb_next(node)) {
|
||||
struct iova *iova = container_of(node, struct iova, node);
|
||||
struct iova *new_iova;
|
||||
new_iova = reserve_iova(to, iova->pfn_lo, iova->pfn_hi);
|
||||
if (!new_iova)
|
||||
printk(KERN_ERR "Reserve iova range %lx@%lx failed\n",
|
||||
iova->pfn_lo, iova->pfn_lo);
|
||||
}
|
||||
spin_unlock_irqrestore(&from->iova_rbtree_lock, flags);
|
||||
}
|
||||
|
|
@ -29,11 +29,6 @@ obj-$(CONFIG_PCI_MSI) += msi.o
|
|||
# Build the Hypertransport interrupt support
|
||||
obj-$(CONFIG_HT_IRQ) += htirq.o
|
||||
|
||||
# Build Intel IOMMU support
|
||||
obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
|
||||
|
||||
obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
|
||||
|
||||
obj-$(CONFIG_PCI_IOV) += iov.o
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -184,8 +184,6 @@ pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
struct pci_dev *pci_find_upstream_pcie_bridge(struct pci_dev *pdev);
|
||||
|
||||
/* PCI slot sysfs helper code */
|
||||
#define to_pci_slot(s) container_of(s, struct pci_slot, kobj)
|
||||
|
||||
|
|
|
|||
|
|
@ -1589,5 +1589,16 @@ int pci_vpd_find_tag(const u8 *buf, unsigned int off, unsigned int len, u8 rdt);
|
|||
int pci_vpd_find_info_keyword(const u8 *buf, unsigned int off,
|
||||
unsigned int len, const char *kw);
|
||||
|
||||
/**
|
||||
* pci_find_upstream_pcie_bridge - find upstream PCIe-to-PCI bridge of a device
|
||||
* @pdev: the PCI device
|
||||
*
|
||||
* if the device is PCIE, return NULL
|
||||
* if the device isn't connected to a PCIe bridge (that is its parent is a
|
||||
* legacy PCI bridge and the bridge is directly connected to bus 0), return its
|
||||
* parent
|
||||
*/
|
||||
struct pci_dev *pci_find_upstream_pcie_bridge(struct pci_dev *pdev);
|
||||
|
||||
#endif /* __KERNEL__ */
|
||||
#endif /* LINUX_PCI_H */
|
||||
|
|
|
|||
Loading…
Reference in a new issue