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