Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a80dc3e0 authored by Joerg Roedel's avatar Joerg Roedel Committed by Ingo Molnar
Browse files

AMD IOMMU: add MSI interrupt support



The AMD IOMMU can generate interrupts for various reasons. This patch
adds the basic interrupt enabling infrastructure to the driver.

Signed-off-by: default avatarJoerg Roedel <joerg.roedel@amd.com>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent 3eaf28a1
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -553,6 +553,7 @@ config CALGARY_IOMMU_ENABLED_BY_DEFAULT
config AMD_IOMMU
	bool "AMD IOMMU support"
	select SWIOTLB
	select PCI_MSI
	depends on X86_64 && PCI && ACPI
	help
	  With this option you can enable support for AMD IOMMU hardware in
+11 −0
Original line number Diff line number Diff line
@@ -49,6 +49,17 @@ static int iommu_has_npcache(struct amd_iommu *iommu)
	return iommu->cap & IOMMU_CAP_NPCACHE;
}

/****************************************************************************
 *
 * Interrupt handling functions
 *
 ****************************************************************************/

irqreturn_t amd_iommu_int_handler(int irq, void *data)
{
	return IRQ_NONE;
}

/****************************************************************************
 *
 * IOMMU command queuing functions
+98 −1
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@
#include <linux/gfp.h>
#include <linux/list.h>
#include <linux/sysdev.h>
#include <linux/interrupt.h>
#include <linux/msi.h>
#include <asm/pci-direct.h>
#include <asm/amd_iommu_types.h>
#include <asm/amd_iommu.h>
@@ -515,17 +517,20 @@ static void __init set_device_exclusion_range(u16 devid, struct ivmd_header *m)
static void __init init_iommu_from_pci(struct amd_iommu *iommu)
{
	int cap_ptr = iommu->cap_ptr;
	u32 range;
	u32 range, misc;

	pci_read_config_dword(iommu->dev, cap_ptr + MMIO_CAP_HDR_OFFSET,
			      &iommu->cap);
	pci_read_config_dword(iommu->dev, cap_ptr + MMIO_RANGE_OFFSET,
			      &range);
	pci_read_config_dword(iommu->dev, cap_ptr + MMIO_MISC_OFFSET,
			      &misc);

	iommu->first_device = calc_devid(MMIO_GET_BUS(range),
					 MMIO_GET_FD(range));
	iommu->last_device = calc_devid(MMIO_GET_BUS(range),
					MMIO_GET_LD(range));
	iommu->evt_msi_num = MMIO_MSI_NUM(misc);
}

/*
@@ -696,6 +701,8 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h)
	if (!iommu->evt_buf)
		return -ENOMEM;

	iommu->int_enabled = false;

	init_iommu_from_pci(iommu);
	init_iommu_from_acpi(iommu, h);
	init_iommu_devices(iommu);
@@ -741,6 +748,95 @@ static int __init init_iommu_all(struct acpi_table_header *table)
	return 0;
}

/****************************************************************************
 *
 * The following functions initialize the MSI interrupts for all IOMMUs
 * in the system. Its a bit challenging because there could be multiple
 * IOMMUs per PCI BDF but we can call pci_enable_msi(x) only once per
 * pci_dev.
 *
 ****************************************************************************/

static int __init iommu_setup_msix(struct amd_iommu *iommu)
{
	struct amd_iommu *curr;
	struct msix_entry entries[32]; /* only 32 supported by AMD IOMMU */
	int nvec = 0, i;

	list_for_each_entry(curr, &amd_iommu_list, list) {
		if (curr->dev == iommu->dev) {
			entries[nvec].entry = curr->evt_msi_num;
			entries[nvec].vector = 0;
			curr->int_enabled = true;
			nvec++;
		}
	}

	if (pci_enable_msix(iommu->dev, entries, nvec)) {
		pci_disable_msix(iommu->dev);
		return 1;
	}

	for (i = 0; i < nvec; ++i) {
		int r = request_irq(entries->vector, amd_iommu_int_handler,
				    IRQF_SAMPLE_RANDOM,
				    "AMD IOMMU",
				    NULL);
		if (r)
			goto out_free;
	}

	return 0;

out_free:
	for (i -= 1; i >= 0; --i)
		free_irq(entries->vector, NULL);

	pci_disable_msix(iommu->dev);

	return 1;
}

static int __init iommu_setup_msi(struct amd_iommu *iommu)
{
	int r;
	struct amd_iommu *curr;

	list_for_each_entry(curr, &amd_iommu_list, list) {
		if (curr->dev == iommu->dev)
			curr->int_enabled = true;
	}


	if (pci_enable_msi(iommu->dev))
		return 1;

	r = request_irq(iommu->dev->irq, amd_iommu_int_handler,
			IRQF_SAMPLE_RANDOM,
			"AMD IOMMU",
			NULL);

	if (r) {
		pci_disable_msi(iommu->dev);
		return 1;
	}

	return 0;
}

static int __init iommu_init_msi(struct amd_iommu *iommu)
{
	if (iommu->int_enabled)
		return 0;

	if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSIX))
		return iommu_setup_msix(iommu);
	else if (pci_find_capability(iommu->dev, PCI_CAP_ID_MSI))
		return iommu_setup_msi(iommu);

	return 1;
}

/****************************************************************************
 *
 * The next functions belong to the third pass of parsing the ACPI
@@ -862,6 +958,7 @@ static void __init enable_iommus(void)

	list_for_each_entry(iommu, &amd_iommu_list, list) {
		iommu_set_exclusion_range(iommu);
		iommu_init_msi(iommu);
		iommu_enable(iommu);
	}
}
+3 −0
Original line number Diff line number Diff line
@@ -20,10 +20,13 @@
#ifndef _ASM_X86_AMD_IOMMU_H
#define _ASM_X86_AMD_IOMMU_H

#include <linux/irqreturn.h>

#ifdef CONFIG_AMD_IOMMU
extern int amd_iommu_init(void);
extern int amd_iommu_init_dma_ops(void);
extern void amd_iommu_detect(void);
extern irqreturn_t amd_iommu_int_handler(int irq, void *data);
#else
static inline int amd_iommu_init(void) { return -ENODEV; }
static inline void amd_iommu_detect(void) { }
+7 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@
/* Capability offsets used by the driver */
#define MMIO_CAP_HDR_OFFSET	0x00
#define MMIO_RANGE_OFFSET	0x0c
#define MMIO_MISC_OFFSET	0x10

/* Masks, shifts and macros to parse the device range capability */
#define MMIO_RANGE_LD_MASK	0xff000000
@@ -48,6 +49,7 @@
#define MMIO_GET_LD(x)  (((x) & MMIO_RANGE_LD_MASK) >> MMIO_RANGE_LD_SHIFT)
#define MMIO_GET_FD(x)  (((x) & MMIO_RANGE_FD_MASK) >> MMIO_RANGE_FD_SHIFT)
#define MMIO_GET_BUS(x) (((x) & MMIO_RANGE_BUS_MASK) >> MMIO_RANGE_BUS_SHIFT)
#define MMIO_MSI_NUM(x)	((x) & 0x1f)

/* Flag masks for the AMD IOMMU exclusion range */
#define MMIO_EXCL_ENABLE_MASK 0x01ULL
@@ -255,10 +257,15 @@ struct amd_iommu {
	u8 *evt_buf;
	/* size of event buffer */
	u32 evt_buf_size;
	/* MSI number for event interrupt */
	u16 evt_msi_num;

	/* if one, we need to send a completion wait command */
	int need_sync;

	/* true if interrupts for this IOMMU are already enabled */
	bool int_enabled;

	/* default dma_ops domain for that IOMMU */
	struct dma_ops_domain *default_dom;
};