Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 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 | */ |
Thomas Gleixner | 33a65ba | 2020-08-26 13:16:42 +0200 | [diff] [blame] | 104 | irq_data->chip_data = info->ioapic.entry; |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 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 | |
Rikard Falkeborn | 9f510d1 | 2020-05-25 23:49:57 +0200 | [diff] [blame] | 134 | static const struct irq_domain_ops hyperv_ir_domain_ops = { |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 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 | |
Thomas Gleixner | e3beca48 | 2020-07-09 11:53:06 +0200 | [diff] [blame] | 158 | if (!ioapic_ir_domain) { |
| 159 | irq_domain_free_fwnode(fn); |
| 160 | return -ENOMEM; |
| 161 | } |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 162 | |
| 163 | /* |
| 164 | * Hyper-V doesn't provide irq remapping function for |
| 165 | * IO-APIC and so IO-APIC only accepts 8-bit APIC ID. |
| 166 | * Cpu's APIC ID is read from ACPI MADT table and APIC IDs |
| 167 | * in the MADT table on Hyper-v are sorted monotonic increasingly. |
| 168 | * APIC ID reflects cpu topology. There maybe some APIC ID |
| 169 | * gaps when cpu number in a socket is not power of two. Prepare |
| 170 | * max cpu affinity for IOAPIC irqs. Scan cpu 0-255 and set cpu |
| 171 | * into ioapic_max_cpumask if its APIC ID is less than 256. |
| 172 | */ |
| 173 | for (i = min_t(unsigned int, num_possible_cpus() - 1, 255); i >= 0; i--) |
| 174 | if (cpu_physical_id(i) < 256) |
| 175 | cpumask_set_cpu(i, &ioapic_max_cpumask); |
| 176 | |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | static int __init hyperv_enable_irq_remapping(void) |
| 181 | { |
| 182 | return IRQ_REMAP_X2APIC_MODE; |
| 183 | } |
| 184 | |
Thomas Gleixner | 6b6256e | 2020-08-26 13:16:39 +0200 | [diff] [blame] | 185 | static struct irq_domain *hyperv_get_irq_domain(struct irq_alloc_info *info) |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 186 | { |
Thomas Gleixner | b4c364d | 2020-08-26 13:16:36 +0200 | [diff] [blame] | 187 | if (info->type == X86_IRQ_ALLOC_TYPE_IOAPIC_GET_PARENT) |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 188 | return ioapic_ir_domain; |
| 189 | else |
| 190 | return NULL; |
| 191 | } |
| 192 | |
| 193 | struct irq_remap_ops hyperv_irq_remap_ops = { |
| 194 | .prepare = hyperv_prepare_irq_remapping, |
| 195 | .enable = hyperv_enable_irq_remapping, |
Thomas Gleixner | 6b6256e | 2020-08-26 13:16:39 +0200 | [diff] [blame] | 196 | .get_irq_domain = hyperv_get_irq_domain, |
Lan Tianyu | 29217a4 | 2019-02-27 22:54:04 +0800 | [diff] [blame] | 197 | }; |
| 198 | |
| 199 | #endif |