Skip to content

Commit 29217a4

Browse files
Lan Tianyujoergroedel
authored andcommitted
iommu/hyper-v: Add Hyper-V stub IOMMU driver
On the bare metal, enabling X2APIC mode requires interrupt remapping function which helps to deliver irq to cpu with 32-bit APIC ID. Hyper-V doesn't provide interrupt remapping function so far and Hyper-V MSI protocol already supports to deliver interrupt to the CPU whose virtual processor index is more than 255. IO-APIC interrupt still has 8-bit APIC ID limitation. This patch is to add Hyper-V stub IOMMU driver in order to enable X2APIC mode successfully in Hyper-V Linux guest. The driver returns X2APIC interrupt remapping capability when X2APIC mode is available. Otherwise, it creates a Hyper-V irq domain to limit IO-APIC interrupts' affinity and make sure cpus assigned with IO-APIC interrupt have 8-bit APIC ID. Define 24 IO-APIC remapping entries because Hyper-V only expose one single IO-APIC and one IO-APIC has 24 pins according IO-APIC spec( https://pdos.csail.mit.edu/6.828/2016/readings/ia32/ioapic.pdf). Reviewed-by: Michael Kelley <mikelley@microsoft.com> Signed-off-by: Lan Tianyu <Tianyu.Lan@microsoft.com> Signed-off-by: Joerg Roedel <jroedel@suse.de>
1 parent 84fdfaf commit 29217a4

File tree

5 files changed

+210
-0
lines changed

5 files changed

+210
-0
lines changed

drivers/iommu/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,4 +435,13 @@ config QCOM_IOMMU
435435
help
436436
Support for IOMMU on certain Qualcomm SoCs.
437437

438+
config HYPERV_IOMMU
439+
bool "Hyper-V x2APIC IRQ Handling"
440+
depends on HYPERV
441+
select IOMMU_API
442+
default HYPERV
443+
help
444+
Stub IOMMU driver to handle IRQs as to allow Hyper-V Linux
445+
guests to run with x2APIC mode enabled.
446+
438447
endif # IOMMU_SUPPORT

drivers/iommu/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ obj-$(CONFIG_EXYNOS_IOMMU) += exynos-iommu.o
3232
obj-$(CONFIG_FSL_PAMU) += fsl_pamu.o fsl_pamu_domain.o
3333
obj-$(CONFIG_S390_IOMMU) += s390-iommu.o
3434
obj-$(CONFIG_QCOM_IOMMU) += qcom_iommu.o
35+
obj-$(CONFIG_HYPERV_IOMMU) += hyperv-iommu.o

drivers/iommu/hyperv-iommu.c

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
/*
4+
* Hyper-V stub IOMMU driver.
5+
*
6+
* Copyright (C) 2019, Microsoft, Inc.
7+
*
8+
* Author : Lan Tianyu <Tianyu.Lan@microsoft.com>
9+
*/
10+
11+
#include <linux/types.h>
12+
#include <linux/interrupt.h>
13+
#include <linux/irq.h>
14+
#include <linux/iommu.h>
15+
#include <linux/module.h>
16+
17+
#include <asm/apic.h>
18+
#include <asm/cpu.h>
19+
#include <asm/hw_irq.h>
20+
#include <asm/io_apic.h>
21+
#include <asm/irq_remapping.h>
22+
#include <asm/hypervisor.h>
23+
24+
#include "irq_remapping.h"
25+
26+
#ifdef CONFIG_IRQ_REMAP
27+
28+
/*
29+
* According 82093AA IO-APIC spec , IO APIC has a 24-entry Interrupt
30+
* Redirection Table. Hyper-V exposes one single IO-APIC and so define
31+
* 24 IO APIC remmapping entries.
32+
*/
33+
#define IOAPIC_REMAPPING_ENTRY 24
34+
35+
static cpumask_t ioapic_max_cpumask = { CPU_BITS_NONE };
36+
static struct irq_domain *ioapic_ir_domain;
37+
38+
static int hyperv_ir_set_affinity(struct irq_data *data,
39+
const struct cpumask *mask, bool force)
40+
{
41+
struct irq_data *parent = data->parent_data;
42+
struct irq_cfg *cfg = irqd_cfg(data);
43+
struct IO_APIC_route_entry *entry;
44+
int ret;
45+
46+
/* Return error If new irq affinity is out of ioapic_max_cpumask. */
47+
if (!cpumask_subset(mask, &ioapic_max_cpumask))
48+
return -EINVAL;
49+
50+
ret = parent->chip->irq_set_affinity(parent, mask, force);
51+
if (ret < 0 || ret == IRQ_SET_MASK_OK_DONE)
52+
return ret;
53+
54+
entry = data->chip_data;
55+
entry->dest = cfg->dest_apicid;
56+
entry->vector = cfg->vector;
57+
send_cleanup_vector(cfg);
58+
59+
return 0;
60+
}
61+
62+
static struct irq_chip hyperv_ir_chip = {
63+
.name = "HYPERV-IR",
64+
.irq_ack = apic_ack_irq,
65+
.irq_set_affinity = hyperv_ir_set_affinity,
66+
};
67+
68+
static int hyperv_irq_remapping_alloc(struct irq_domain *domain,
69+
unsigned int virq, unsigned int nr_irqs,
70+
void *arg)
71+
{
72+
struct irq_alloc_info *info = arg;
73+
struct irq_data *irq_data;
74+
struct irq_desc *desc;
75+
int ret = 0;
76+
77+
if (!info || info->type != X86_IRQ_ALLOC_TYPE_IOAPIC || nr_irqs > 1)
78+
return -EINVAL;
79+
80+
ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
81+
if (ret < 0)
82+
return ret;
83+
84+
irq_data = irq_domain_get_irq_data(domain, virq);
85+
if (!irq_data) {
86+
irq_domain_free_irqs_common(domain, virq, nr_irqs);
87+
return -EINVAL;
88+
}
89+
90+
irq_data->chip = &hyperv_ir_chip;
91+
92+
/*
93+
* If there is interrupt remapping function of IOMMU, setting irq
94+
* affinity only needs to change IRTE of IOMMU. But Hyper-V doesn't
95+
* support interrupt remapping function, setting irq affinity of IO-APIC
96+
* interrupts still needs to change IO-APIC registers. But ioapic_
97+
* configure_entry() will ignore value of cfg->vector and cfg->
98+
* dest_apicid when IO-APIC's parent irq domain is not the vector
99+
* domain.(See ioapic_configure_entry()) In order to setting vector
100+
* and dest_apicid to IO-APIC register, IO-APIC entry pointer is saved
101+
* in the chip_data and hyperv_irq_remapping_activate()/hyperv_ir_set_
102+
* affinity() set vector and dest_apicid directly into IO-APIC entry.
103+
*/
104+
irq_data->chip_data = info->ioapic_entry;
105+
106+
/*
107+
* Hypver-V IO APIC irq affinity should be in the scope of
108+
* ioapic_max_cpumask because no irq remapping support.
109+
*/
110+
desc = irq_data_to_desc(irq_data);
111+
cpumask_copy(desc->irq_common_data.affinity, &ioapic_max_cpumask);
112+
113+
return 0;
114+
}
115+
116+
static void hyperv_irq_remapping_free(struct irq_domain *domain,
117+
unsigned int virq, unsigned int nr_irqs)
118+
{
119+
irq_domain_free_irqs_common(domain, virq, nr_irqs);
120+
}
121+
122+
static int hyperv_irq_remapping_activate(struct irq_domain *domain,
123+
struct irq_data *irq_data, bool reserve)
124+
{
125+
struct irq_cfg *cfg = irqd_cfg(irq_data);
126+
struct IO_APIC_route_entry *entry = irq_data->chip_data;
127+
128+
entry->dest = cfg->dest_apicid;
129+
entry->vector = cfg->vector;
130+
131+
return 0;
132+
}
133+
134+
static struct irq_domain_ops hyperv_ir_domain_ops = {
135+
.alloc = hyperv_irq_remapping_alloc,
136+
.free = hyperv_irq_remapping_free,
137+
.activate = hyperv_irq_remapping_activate,
138+
};
139+
140+
static int __init hyperv_prepare_irq_remapping(void)
141+
{
142+
struct fwnode_handle *fn;
143+
int i;
144+
145+
if (!hypervisor_is_type(X86_HYPER_MS_HYPERV) ||
146+
!x2apic_supported())
147+
return -ENODEV;
148+
149+
fn = irq_domain_alloc_named_id_fwnode("HYPERV-IR", 0);
150+
if (!fn)
151+
return -ENOMEM;
152+
153+
ioapic_ir_domain =
154+
irq_domain_create_hierarchy(arch_get_ir_parent_domain(),
155+
0, IOAPIC_REMAPPING_ENTRY, fn,
156+
&hyperv_ir_domain_ops, NULL);
157+
158+
irq_domain_free_fwnode(fn);
159+
160+
/*
161+
* Hyper-V doesn't provide irq remapping function for
162+
* IO-APIC and so IO-APIC only accepts 8-bit APIC ID.
163+
* Cpu's APIC ID is read from ACPI MADT table and APIC IDs
164+
* in the MADT table on Hyper-v are sorted monotonic increasingly.
165+
* APIC ID reflects cpu topology. There maybe some APIC ID
166+
* gaps when cpu number in a socket is not power of two. Prepare
167+
* max cpu affinity for IOAPIC irqs. Scan cpu 0-255 and set cpu
168+
* into ioapic_max_cpumask if its APIC ID is less than 256.
169+
*/
170+
for (i = min_t(unsigned int, num_possible_cpus() - 1, 255); i >= 0; i--)
171+
if (cpu_physical_id(i) < 256)
172+
cpumask_set_cpu(i, &ioapic_max_cpumask);
173+
174+
return 0;
175+
}
176+
177+
static int __init hyperv_enable_irq_remapping(void)
178+
{
179+
return IRQ_REMAP_X2APIC_MODE;
180+
}
181+
182+
static struct irq_domain *hyperv_get_ir_irq_domain(struct irq_alloc_info *info)
183+
{
184+
if (info->type == X86_IRQ_ALLOC_TYPE_IOAPIC)
185+
return ioapic_ir_domain;
186+
else
187+
return NULL;
188+
}
189+
190+
struct irq_remap_ops hyperv_irq_remap_ops = {
191+
.prepare = hyperv_prepare_irq_remapping,
192+
.enable = hyperv_enable_irq_remapping,
193+
.get_ir_irq_domain = hyperv_get_ir_irq_domain,
194+
};
195+
196+
#endif

drivers/iommu/irq_remapping.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ int __init irq_remapping_prepare(void)
103103
else if (IS_ENABLED(CONFIG_AMD_IOMMU) &&
104104
amd_iommu_irq_ops.prepare() == 0)
105105
remap_ops = &amd_iommu_irq_ops;
106+
else if (IS_ENABLED(CONFIG_HYPERV_IOMMU) &&
107+
hyperv_irq_remap_ops.prepare() == 0)
108+
remap_ops = &hyperv_irq_remap_ops;
106109
else
107110
return -ENOSYS;
108111

drivers/iommu/irq_remapping.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct irq_remap_ops {
6464

6565
extern struct irq_remap_ops intel_irq_remap_ops;
6666
extern struct irq_remap_ops amd_iommu_irq_ops;
67+
extern struct irq_remap_ops hyperv_irq_remap_ops;
6768

6869
#else /* CONFIG_IRQ_REMAP */
6970

0 commit comments

Comments
 (0)