Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iommu/hyperv: setup an IO-APIC IRQ remapping domain for root partition

Just like MSI/MSI-X, IO-APIC interrupts are remapped by Microsoft
Hypervisor when Linux runs as the root partition. Implement an IRQ
domain to handle mapping and unmapping of IO-APIC interrupts.

Signed-off-by: Wei Liu <wei.liu@kernel.org>
Acked-by: Joerg Roedel <joro@8bytes.org>
Reviewed-by: Michael Kelley <mikelley@microsoft.com>
Link: https://lore.kernel.org/r/20210203150435.27941-17-wei.liu@kernel.org

Wei Liu fb5ef351 e39397d1

+203 -3
+25
arch/x86/hyperv/irqdomain.c
··· 358 358 } 359 359 360 360 #endif /* CONFIG_PCI_MSI */ 361 + 362 + int hv_unmap_ioapic_interrupt(int ioapic_id, struct hv_interrupt_entry *entry) 363 + { 364 + union hv_device_id device_id; 365 + 366 + device_id.as_uint64 = 0; 367 + device_id.device_type = HV_DEVICE_TYPE_IOAPIC; 368 + device_id.ioapic.ioapic_id = (u8)ioapic_id; 369 + 370 + return hv_unmap_interrupt(device_id.as_uint64, entry); 371 + } 372 + EXPORT_SYMBOL_GPL(hv_unmap_ioapic_interrupt); 373 + 374 + int hv_map_ioapic_interrupt(int ioapic_id, bool level, int cpu, int vector, 375 + struct hv_interrupt_entry *entry) 376 + { 377 + union hv_device_id device_id; 378 + 379 + device_id.as_uint64 = 0; 380 + device_id.device_type = HV_DEVICE_TYPE_IOAPIC; 381 + device_id.ioapic.ioapic_id = (u8)ioapic_id; 382 + 383 + return hv_map_interrupt(device_id, level, cpu, vector, entry); 384 + } 385 + EXPORT_SYMBOL_GPL(hv_map_ioapic_interrupt);
+4
arch/x86/include/asm/mshyperv.h
··· 265 265 266 266 struct irq_domain *hv_create_pci_msi_domain(void); 267 267 268 + int hv_map_ioapic_interrupt(int ioapic_id, bool level, int vcpu, int vector, 269 + struct hv_interrupt_entry *entry); 270 + int hv_unmap_ioapic_interrupt(int ioapic_id, struct hv_interrupt_entry *entry); 271 + 268 272 #else /* CONFIG_HYPERV */ 269 273 static inline void hyperv_init(void) {} 270 274 static inline void hyperv_setup_mmu_ops(void) {}
+174 -3
drivers/iommu/hyperv-iommu.c
··· 20 20 #include <asm/io_apic.h> 21 21 #include <asm/irq_remapping.h> 22 22 #include <asm/hypervisor.h> 23 + #include <asm/mshyperv.h> 23 24 24 25 #include "irq_remapping.h" 25 26 ··· 116 115 .free = hyperv_irq_remapping_free, 117 116 }; 118 117 118 + static const struct irq_domain_ops hyperv_root_ir_domain_ops; 119 119 static int __init hyperv_prepare_irq_remapping(void) 120 120 { 121 121 struct fwnode_handle *fn; 122 122 int i; 123 + const char *name; 124 + const struct irq_domain_ops *ops; 123 125 124 126 if (!hypervisor_is_type(X86_HYPER_MS_HYPERV) || 125 127 x86_init.hyper.msi_ext_dest_id() || 126 128 !x2apic_supported()) 127 129 return -ENODEV; 128 130 129 - fn = irq_domain_alloc_named_id_fwnode("HYPERV-IR", 0); 131 + if (hv_root_partition) { 132 + name = "HYPERV-ROOT-IR"; 133 + ops = &hyperv_root_ir_domain_ops; 134 + } else { 135 + name = "HYPERV-IR"; 136 + ops = &hyperv_ir_domain_ops; 137 + } 138 + 139 + fn = irq_domain_alloc_named_id_fwnode(name, 0); 130 140 if (!fn) 131 141 return -ENOMEM; 132 142 133 143 ioapic_ir_domain = 134 144 irq_domain_create_hierarchy(arch_get_ir_parent_domain(), 135 - 0, IOAPIC_REMAPPING_ENTRY, fn, 136 - &hyperv_ir_domain_ops, NULL); 145 + 0, IOAPIC_REMAPPING_ENTRY, fn, ops, NULL); 137 146 138 147 if (!ioapic_ir_domain) { 139 148 irq_domain_free_fwnode(fn); 140 149 return -ENOMEM; 141 150 } 151 + 152 + if (hv_root_partition) 153 + return 0; /* The rest is only relevant to guests */ 142 154 143 155 /* 144 156 * Hyper-V doesn't provide irq remapping function for ··· 178 164 struct irq_remap_ops hyperv_irq_remap_ops = { 179 165 .prepare = hyperv_prepare_irq_remapping, 180 166 .enable = hyperv_enable_irq_remapping, 167 + }; 168 + 169 + /* IRQ remapping domain when Linux runs as the root partition */ 170 + struct hyperv_root_ir_data { 171 + u8 ioapic_id; 172 + bool is_level; 173 + struct hv_interrupt_entry entry; 174 + }; 175 + 176 + static void 177 + hyperv_root_ir_compose_msi_msg(struct irq_data *irq_data, struct msi_msg *msg) 178 + { 179 + u64 status; 180 + u32 vector; 181 + struct irq_cfg *cfg; 182 + int ioapic_id; 183 + struct cpumask *affinity; 184 + int cpu; 185 + struct hv_interrupt_entry entry; 186 + struct hyperv_root_ir_data *data = irq_data->chip_data; 187 + struct IO_APIC_route_entry e; 188 + 189 + cfg = irqd_cfg(irq_data); 190 + affinity = irq_data_get_effective_affinity_mask(irq_data); 191 + cpu = cpumask_first_and(affinity, cpu_online_mask); 192 + 193 + vector = cfg->vector; 194 + ioapic_id = data->ioapic_id; 195 + 196 + if (data->entry.source == HV_DEVICE_TYPE_IOAPIC 197 + && data->entry.ioapic_rte.as_uint64) { 198 + entry = data->entry; 199 + 200 + status = hv_unmap_ioapic_interrupt(ioapic_id, &entry); 201 + 202 + if (status != HV_STATUS_SUCCESS) 203 + pr_debug("%s: unexpected unmap status %lld\n", __func__, status); 204 + 205 + data->entry.ioapic_rte.as_uint64 = 0; 206 + data->entry.source = 0; /* Invalid source */ 207 + } 208 + 209 + 210 + status = hv_map_ioapic_interrupt(ioapic_id, data->is_level, cpu, 211 + vector, &entry); 212 + 213 + if (status != HV_STATUS_SUCCESS) { 214 + pr_err("%s: map hypercall failed, status %lld\n", __func__, status); 215 + return; 216 + } 217 + 218 + data->entry = entry; 219 + 220 + /* Turn it into an IO_APIC_route_entry, and generate MSI MSG. */ 221 + e.w1 = entry.ioapic_rte.low_uint32; 222 + e.w2 = entry.ioapic_rte.high_uint32; 223 + 224 + memset(msg, 0, sizeof(*msg)); 225 + msg->arch_data.vector = e.vector; 226 + msg->arch_data.delivery_mode = e.delivery_mode; 227 + msg->arch_addr_lo.dest_mode_logical = e.dest_mode_logical; 228 + msg->arch_addr_lo.dmar_format = e.ir_format; 229 + msg->arch_addr_lo.dmar_index_0_14 = e.ir_index_0_14; 230 + } 231 + 232 + static int hyperv_root_ir_set_affinity(struct irq_data *data, 233 + const struct cpumask *mask, bool force) 234 + { 235 + struct irq_data *parent = data->parent_data; 236 + struct irq_cfg *cfg = irqd_cfg(data); 237 + int ret; 238 + 239 + ret = parent->chip->irq_set_affinity(parent, mask, force); 240 + if (ret < 0 || ret == IRQ_SET_MASK_OK_DONE) 241 + return ret; 242 + 243 + send_cleanup_vector(cfg); 244 + 245 + return 0; 246 + } 247 + 248 + static struct irq_chip hyperv_root_ir_chip = { 249 + .name = "HYPERV-ROOT-IR", 250 + .irq_ack = apic_ack_irq, 251 + .irq_set_affinity = hyperv_root_ir_set_affinity, 252 + .irq_compose_msi_msg = hyperv_root_ir_compose_msi_msg, 253 + }; 254 + 255 + static int hyperv_root_irq_remapping_alloc(struct irq_domain *domain, 256 + unsigned int virq, unsigned int nr_irqs, 257 + void *arg) 258 + { 259 + struct irq_alloc_info *info = arg; 260 + struct irq_data *irq_data; 261 + struct hyperv_root_ir_data *data; 262 + int ret = 0; 263 + 264 + if (!info || info->type != X86_IRQ_ALLOC_TYPE_IOAPIC || nr_irqs > 1) 265 + return -EINVAL; 266 + 267 + ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg); 268 + if (ret < 0) 269 + return ret; 270 + 271 + data = kzalloc(sizeof(*data), GFP_KERNEL); 272 + if (!data) { 273 + irq_domain_free_irqs_common(domain, virq, nr_irqs); 274 + return -ENOMEM; 275 + } 276 + 277 + irq_data = irq_domain_get_irq_data(domain, virq); 278 + if (!irq_data) { 279 + kfree(data); 280 + irq_domain_free_irqs_common(domain, virq, nr_irqs); 281 + return -EINVAL; 282 + } 283 + 284 + data->ioapic_id = info->devid; 285 + data->is_level = info->ioapic.is_level; 286 + 287 + irq_data->chip = &hyperv_root_ir_chip; 288 + irq_data->chip_data = data; 289 + 290 + return 0; 291 + } 292 + 293 + static void hyperv_root_irq_remapping_free(struct irq_domain *domain, 294 + unsigned int virq, unsigned int nr_irqs) 295 + { 296 + struct irq_data *irq_data; 297 + struct hyperv_root_ir_data *data; 298 + struct hv_interrupt_entry *e; 299 + int i; 300 + 301 + for (i = 0; i < nr_irqs; i++) { 302 + irq_data = irq_domain_get_irq_data(domain, virq + i); 303 + 304 + if (irq_data && irq_data->chip_data) { 305 + data = irq_data->chip_data; 306 + e = &data->entry; 307 + 308 + if (e->source == HV_DEVICE_TYPE_IOAPIC 309 + && e->ioapic_rte.as_uint64) 310 + hv_unmap_ioapic_interrupt(data->ioapic_id, 311 + &data->entry); 312 + 313 + kfree(data); 314 + } 315 + } 316 + 317 + irq_domain_free_irqs_common(domain, virq, nr_irqs); 318 + } 319 + 320 + static const struct irq_domain_ops hyperv_root_ir_domain_ops = { 321 + .select = hyperv_irq_remapping_select, 322 + .alloc = hyperv_root_irq_remapping_alloc, 323 + .free = hyperv_root_irq_remapping_free, 181 324 }; 182 325 183 326 #endif