summaryrefslogtreecommitdiff
path: root/drivers/iommu/irq_remapping.c
blob: 83f36f61416e5d3b11e099ba51eded8bf4c964bd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cpumask.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/msi.h>
#include <linux/irq.h>
#include <linux/pci.h>
#include <linux/irqdomain.h>

#include <asm/hw_irq.h>
#include <asm/irq_remapping.h>
#include <asm/processor.h>
#include <asm/x86_init.h>
#include <asm/apic.h>
#include <asm/hpet.h>

#include "irq_remapping.h"

int irq_remapping_enabled;
int irq_remap_broken;
int disable_sourceid_checking;
int no_x2apic_optout;

int disable_irq_post = 0;

static int disable_irq_remap;
static struct irq_remap_ops *remap_ops;

static void irq_remapping_restore_boot_irq_mode(void)
{
	/*
	 * With interrupt-remapping, for now we will use virtual wire A
	 * mode, as virtual wire B is little complex (need to configure
	 * both IOAPIC RTE as well as interrupt-remapping table entry).
	 * As this gets called during crash dump, keep this simple for
	 * now.
	 */
	if (boot_cpu_has(X86_FEATURE_APIC) || apic_from_smp_config())
		disconnect_bsp_APIC(0);
}

static void __init irq_remapping_modify_x86_ops(void)
{
	x86_apic_ops.restore = irq_remapping_restore_boot_irq_mode;
}

static __init int setup_nointremap(char *str)
{
	disable_irq_remap = 1;
	return 0;
}
early_param("nointremap", setup_nointremap);

static __init int setup_irqremap(char *str)
{
	if (!str)
		return -EINVAL;

	while (*str) {
		if (!strncmp(str, "on", 2)) {
			disable_irq_remap = 0;
			disable_irq_post = 0;
		} else if (!strncmp(str, "off", 3)) {
			disable_irq_remap = 1;
			disable_irq_post = 1;
		} else if (!strncmp(str, "nosid", 5))
			disable_sourceid_checking = 1;
		else if (!strncmp(str, "no_x2apic_optout", 16))
			no_x2apic_optout = 1;
		else if (!strncmp(str, "nopost", 6))
			disable_irq_post = 1;

		str += strcspn(str, ",");
		while (*str == ',')
			str++;
	}

	return 0;
}
early_param("intremap", setup_irqremap);

void set_irq_remapping_broken(void)
{
	irq_remap_broken = 1;
}

bool irq_remapping_cap(enum irq_remap_cap cap)
{
	if (!remap_ops || disable_irq_post)
		return false;

	return (remap_ops->capability & (1 << cap));
}
EXPORT_SYMBOL_GPL(irq_remapping_cap);

int __init irq_remapping_prepare(void)
{
	if (disable_irq_remap)
		return -ENOSYS;

	if (intel_irq_remap_ops.prepare() == 0)
		remap_ops = &intel_irq_remap_ops;
	else if (IS_ENABLED(CONFIG_AMD_IOMMU) &&
		 amd_iommu_irq_ops.prepare() == 0)
		remap_ops = &amd_iommu_irq_ops;
	else if (IS_ENABLED(CONFIG_HYPERV_IOMMU) &&
		 hyperv_irq_remap_ops.prepare() == 0)
		remap_ops = &hyperv_irq_remap_ops;
	else
		return -ENOSYS;

	return 0;
}

int __init irq_remapping_enable(void)
{
	int ret;

	if (!remap_ops->enable)
		return -ENODEV;

	ret = remap_ops->enable();

	if (irq_remapping_enabled)
		irq_remapping_modify_x86_ops();

	return ret;
}

void irq_remapping_disable(void)
{
	if (irq_remapping_enabled && remap_ops->disable)
		remap_ops->disable();
}

int irq_remapping_reenable(int mode)
{
	if (irq_remapping_enabled && remap_ops->reenable)
		return remap_ops->reenable(mode);

	return 0;
}

int __init irq_remap_enable_fault_handling(void)
{
	if (!irq_remapping_enabled)
		return 0;

	if (!remap_ops->enable_faulting)
		return -ENODEV;

	return remap_ops->enable_faulting();
}

void panic_if_irq_remap(const char *msg)
{
	if (irq_remapping_enabled)
		panic(msg);
}

/**
 * irq_remapping_get_ir_irq_domain - Get the irqdomain associated with the IOMMU
 *				     device serving request @info
 * @info: interrupt allocation information, used to identify the IOMMU device
 *
 * It's used to get parent irqdomain for HPET and IOAPIC irqdomains.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *
irq_remapping_get_ir_irq_domain(struct irq_alloc_info *info)
{
	if (!remap_ops || !remap_ops->get_ir_irq_domain)
		return NULL;

	return remap_ops->get_ir_irq_domain(info);
}

/**
 * irq_remapping_get_irq_domain - Get the irqdomain serving the request @info
 * @info: interrupt allocation information, used to identify the IOMMU device
 *
 * There will be one PCI MSI/MSIX irqdomain associated with each interrupt
 * remapping device, so this interface is used to retrieve the PCI MSI/MSIX
 * irqdomain serving request @info.
 * Returns pointer to IRQ domain, or NULL on failure.
 */
struct irq_domain *
irq_remapping_get_irq_domain(struct irq_alloc_info *info)
{
	if (!remap_ops || !remap_ops->get_irq_domain)
		return NULL;

	return remap_ops->get_irq_domain(info);
}