| 1 | /* |
| 2 | * QEMU MSHV support |
| 3 | * |
| 4 | * Copyright Microsoft, Corp. 2025 |
| 5 | * |
| 6 | * Authors: Ziqiao Zhou <ziqiaozhou@microsoft.com> |
| 7 | * Magnus Kulke <magnuskulke@microsoft.com> |
| 8 | * Stanislav Kinsburskii <skinsburskii@microsoft.com> |
| 9 | * |
| 10 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 11 | */ |
| 12 | |
| 13 | #include "qemu/osdep.h" |
| 14 | #include "linux/mshv.h" |
| 15 | #include "qemu/error-report.h" |
| 16 | #include "hw/hyperv/hvhdk_mini.h" |
| 17 | #include "hw/hyperv/hvgdk_mini.h" |
| 18 | #include "hw/intc/ioapic.h" |
| 19 | #include "hw/pci/msi.h" |
| 20 | #include "system/mshv.h" |
| 21 | #include "system/mshv_int.h" |
| 22 | #include "trace.h" |
| 23 | #include <sys/ioctl.h> |
| 24 | |
| 25 | #define MSHV_IRQFD_RESAMPLE_FLAG (1 << MSHV_IRQFD_BIT_RESAMPLE) |
| 26 | #define MSHV_IRQFD_BIT_DEASSIGN_FLAG (1 << MSHV_IRQFD_BIT_DEASSIGN) |
| 27 | |
| 28 | /* Pass an eventfd which is to be used for injecting interrupts from userland */ |
| 29 | static int irqfd(int vm_fd, int fd, int resample_fd, uint32_t gsi, |
| 30 | uint32_t flags) |
| 31 | { |
| 32 | int ret; |
| 33 | struct mshv_user_irqfd arg = { |
| 34 | .fd = fd, |
| 35 | .resamplefd = resample_fd, |
| 36 | .gsi = gsi, |
| 37 | .flags = flags, |
| 38 | }; |
| 39 | |
| 40 | ret = ioctl(vm_fd, MSHV_IRQFD, &arg); |
| 41 | if (ret < 0) { |
| 42 | error_report("Failed to set irqfd: gsi=%u, fd=%d", gsi, fd); |
| 43 | return -1; |
| 44 | } |
| 45 | return ret; |
| 46 | } |
| 47 | |
| 48 | static int register_irqfd(int vm_fd, int event_fd, uint32_t gsi) |
| 49 | { |
| 50 | int ret; |
| 51 | |
| 52 | trace_mshv_register_irqfd(vm_fd, event_fd, gsi); |
| 53 | |
| 54 | ret = irqfd(vm_fd, event_fd, 0, gsi, 0); |
| 55 | if (ret < 0) { |
| 56 | error_report("Failed to register irqfd: gsi=%u", gsi); |
| 57 | return -1; |
| 58 | } |
| 59 | return 0; |
| 60 | } |
| 61 | |
| 62 | static int register_irqfd_with_resample(int vm_fd, int event_fd, |
| 63 | int resample_fd, uint32_t gsi) |
| 64 | { |
| 65 | int ret; |
| 66 | uint32_t flags = MSHV_IRQFD_RESAMPLE_FLAG; |
| 67 | |
| 68 | ret = irqfd(vm_fd, event_fd, resample_fd, gsi, flags); |
| 69 | if (ret < 0) { |
| 70 | error_report("Failed to register irqfd with resample: gsi=%u", gsi); |
| 71 | return -errno; |
| 72 | } |
| 73 | return 0; |
| 74 | } |
| 75 | |
| 76 | static int unregister_irqfd(int vm_fd, int event_fd, uint32_t gsi) |
| 77 | { |
| 78 | int ret; |
| 79 | uint32_t flags = MSHV_IRQFD_BIT_DEASSIGN_FLAG; |
| 80 | |
| 81 | ret = irqfd(vm_fd, event_fd, 0, gsi, flags); |
| 82 | if (ret < 0) { |
| 83 | error_report("Failed to unregister irqfd: gsi=%u", gsi); |
| 84 | return -errno; |
| 85 | } |
| 86 | return 0; |
| 87 | } |
| 88 | |
| 89 | static int irqchip_update_irqfd_notifier_gsi(const EventNotifier *event, |
| 90 | const EventNotifier *resample, |
| 91 | int virq, bool add) |
| 92 | { |
| 93 | int fd = event_notifier_get_fd(event); |
| 94 | int rfd = resample ? event_notifier_get_fd(resample) : -1; |
| 95 | int vm_fd = mshv_state->vm; |
| 96 | |
| 97 | trace_mshv_irqchip_update_irqfd_notifier_gsi(fd, rfd, virq, add); |
| 98 | |
| 99 | if (!add) { |
| 100 | return unregister_irqfd(vm_fd, fd, virq); |
| 101 | } |
| 102 | |
| 103 | if (rfd > 0) { |
| 104 | return register_irqfd_with_resample(vm_fd, fd, rfd, virq); |
| 105 | } |
| 106 | |
| 107 | return register_irqfd(vm_fd, fd, virq); |
| 108 | } |
| 109 | |
| 110 | static int irqchip_allocate_gsi(MshvState *s, int *gsi) |
| 111 | { |
| 112 | int next_gsi; |
| 113 | |
| 114 | /* Return the lowest unused GSI in the bitmap */ |
| 115 | next_gsi = find_first_zero_bit(s->used_gsi_bitmap, s->gsi_count); |
| 116 | if (next_gsi >= s->gsi_count) { |
| 117 | return -ENOSPC; |
| 118 | } |
| 119 | |
| 120 | *gsi = next_gsi; |
| 121 | |
| 122 | return 0; |
| 123 | } |
| 124 | |
| 125 | static void irqchip_release_gsi(MshvState *s, int gsi) |
| 126 | { |
| 127 | clear_bit(gsi, s->used_gsi_bitmap); |
| 128 | } |
| 129 | |
| 130 | static void add_routing_entry(MshvState *s, struct mshv_user_irq_entry *entry) |
| 131 | { |
| 132 | struct mshv_user_irq_entry *new; |
| 133 | int n, size; |
| 134 | |
| 135 | if (s->irq_routes->nr == s->nr_allocated_irq_routes) { |
| 136 | n = s->nr_allocated_irq_routes * 2; |
| 137 | if (n < MSHV_MIN_ALLOCATED_MSI_ROUTES) { |
| 138 | n = MSHV_MIN_ALLOCATED_MSI_ROUTES; |
| 139 | } |
| 140 | size = sizeof(struct mshv_user_irq_table); |
| 141 | size += n * sizeof(*new); |
| 142 | s->irq_routes = g_realloc(s->irq_routes, size); |
| 143 | s->nr_allocated_irq_routes = n; |
| 144 | } |
| 145 | |
| 146 | n = s->irq_routes->nr; |
| 147 | s->irq_routes->nr++; |
| 148 | new = &s->irq_routes->entries[n]; |
| 149 | |
| 150 | *new = *entry; |
| 151 | |
| 152 | set_bit(entry->gsi, s->used_gsi_bitmap); |
| 153 | |
| 154 | trace_mshv_add_msi_routing(entry->address_lo | entry->address_hi, |
| 155 | entry->data); |
| 156 | } |
| 157 | |
| 158 | int mshv_irqchip_add_msi_route(AccelRouteChange *c, int vector, PCIDevice *dev) |
| 159 | { |
| 160 | struct mshv_user_irq_entry entry = { 0 }; |
| 161 | MSIMessage msg = { 0 }; |
| 162 | uint32_t data, high_addr, low_addr; |
| 163 | int gsi, ret; |
| 164 | MshvState *s = MSHV_STATE(c->accel); |
| 165 | |
| 166 | if (!pci_available || !dev) { |
| 167 | return 0; |
| 168 | } |
| 169 | |
| 170 | msg = pci_get_msi_message(dev, vector); |
| 171 | |
| 172 | ret = irqchip_allocate_gsi(mshv_state, &gsi); |
| 173 | if (ret < 0) { |
| 174 | error_report("Could not allocate GSI for MSI route"); |
| 175 | return -1; |
| 176 | } |
| 177 | high_addr = msg.address >> 32; |
| 178 | low_addr = msg.address & 0xFFFFFFFF; |
| 179 | data = le32_to_cpu(msg.data); |
| 180 | |
| 181 | entry.gsi = gsi; |
| 182 | entry.address_hi = high_addr; |
| 183 | entry.address_lo = low_addr; |
| 184 | entry.data = data; |
| 185 | |
| 186 | if (s->irq_routes->nr < s->gsi_count) { |
| 187 | add_routing_entry(s, &entry); |
| 188 | c->changes++; |
| 189 | } else { |
| 190 | irqchip_release_gsi(s, gsi); |
| 191 | return -ENOSPC; |
| 192 | } |
| 193 | |
| 194 | return gsi; |
| 195 | } |
| 196 | |
| 197 | void mshv_irqchip_release_virq(MshvState *s, int virq) |
| 198 | { |
| 199 | struct mshv_user_irq_entry *e; |
| 200 | int i; |
| 201 | |
| 202 | for (i = 0; i < s->irq_routes->nr; i++) { |
| 203 | e = &s->irq_routes->entries[i]; |
| 204 | if (e->gsi == virq) { |
| 205 | s->irq_routes->nr--; |
| 206 | *e = s->irq_routes->entries[s->irq_routes->nr]; |
| 207 | } |
| 208 | } |
| 209 | irqchip_release_gsi(s, virq); |
| 210 | |
| 211 | trace_mshv_remove_msi_routing(virq); |
| 212 | } |
| 213 | |
| 214 | static int update_routing_entry(MshvState *s, |
| 215 | struct mshv_user_irq_entry *new_entry) |
| 216 | { |
| 217 | struct mshv_user_irq_entry *entry; |
| 218 | int n; |
| 219 | |
| 220 | for (n = 0; n < s->irq_routes->nr; n++) { |
| 221 | entry = &s->irq_routes->entries[n]; |
| 222 | if (entry->gsi != new_entry->gsi) { |
| 223 | continue; |
| 224 | } |
| 225 | |
| 226 | if (!memcmp(entry, new_entry, sizeof *entry)) { |
| 227 | return 0; |
| 228 | } |
| 229 | |
| 230 | *entry = *new_entry; |
| 231 | |
| 232 | return 0; |
| 233 | } |
| 234 | |
| 235 | return -ESRCH; |
| 236 | } |
| 237 | |
| 238 | int mshv_irqchip_update_msi_route(int virq, MSIMessage msg, PCIDevice *dev) |
| 239 | { |
| 240 | uint32_t addr_hi = msg.address >> 32; |
| 241 | uint32_t addr_lo = msg.address & 0xFFFFFFFF; |
| 242 | uint32_t data = le32_to_cpu(msg.data); |
| 243 | struct mshv_user_irq_entry entry = { |
| 244 | .gsi = virq, |
| 245 | .address_hi = addr_hi, |
| 246 | .address_lo = addr_lo, |
| 247 | .data = data, |
| 248 | }; |
| 249 | int ret; |
| 250 | |
| 251 | ret = update_routing_entry(mshv_state, &entry); |
| 252 | if (ret < 0) { |
| 253 | error_report("Failed to set msi routing for gsi %d", virq); |
| 254 | abort(); |
| 255 | } |
| 256 | |
| 257 | trace_mshv_set_msi_routing(virq, msg.address, data); |
| 258 | |
| 259 | return 0; |
| 260 | } |
| 261 | |
| 262 | int mshv_request_interrupt(MshvState *mshv_state, uint32_t interrupt_type, uint32_t vector, |
| 263 | uint32_t vp_index, bool logical_dest_mode, |
| 264 | bool level_triggered) |
| 265 | { |
| 266 | int ret; |
| 267 | int vm_fd = mshv_state->vm; |
| 268 | |
| 269 | if (vector == 0) { |
| 270 | warn_report("Ignoring request for interrupt vector 0"); |
| 271 | return 0; |
| 272 | } |
| 273 | |
| 274 | union hv_interrupt_control control = { |
| 275 | .interrupt_type = interrupt_type, |
| 276 | .level_triggered = level_triggered, |
| 277 | .logical_dest_mode = logical_dest_mode, |
| 278 | .rsvd = 0, |
| 279 | }; |
| 280 | |
| 281 | struct hv_input_assert_virtual_interrupt arg = {0}; |
| 282 | arg.control = control; |
| 283 | arg.dest_addr = (uint64_t)vp_index; |
| 284 | arg.vector = vector; |
| 285 | |
| 286 | struct mshv_root_hvcall args = {0}; |
| 287 | args.code = HVCALL_ASSERT_VIRTUAL_INTERRUPT; |
| 288 | args.in_sz = sizeof(arg); |
| 289 | args.in_ptr = (uint64_t)&arg; |
| 290 | |
| 291 | ret = mshv_hvcall(vm_fd, &args); |
| 292 | if (ret < 0) { |
| 293 | error_report("Failed to request interrupt"); |
| 294 | return -errno; |
| 295 | } |
| 296 | return 0; |
| 297 | } |
| 298 | |
| 299 | void mshv_irqchip_commit_routes(MshvState *s) |
| 300 | { |
| 301 | int ret; |
| 302 | int vm_fd = s->vm; |
| 303 | |
| 304 | ret = ioctl(vm_fd, MSHV_SET_MSI_ROUTING, s->irq_routes); |
| 305 | if (ret < 0) { |
| 306 | error_report("Failed to commit msi routing table"); |
| 307 | abort(); |
| 308 | } |
| 309 | trace_mshv_commit_msi_routing_table(vm_fd, s->irq_routes->nr); |
| 310 | } |
| 311 | |
| 312 | int mshv_irqchip_add_irqfd_notifier_gsi(const EventNotifier *event, |
| 313 | const EventNotifier *resample, |
| 314 | int virq) |
| 315 | { |
| 316 | return irqchip_update_irqfd_notifier_gsi(event, resample, virq, true); |
| 317 | } |
| 318 | |
| 319 | int mshv_irqchip_remove_irqfd_notifier_gsi(const EventNotifier *event, |
| 320 | int virq) |
| 321 | { |
| 322 | return irqchip_update_irqfd_notifier_gsi(event, NULL, virq, false); |
| 323 | } |
| 324 | |
| 325 | static int mshv_reserve_ioapic_msi_routes(MshvState *s) |
| 326 | { |
| 327 | int ret, i; |
| 328 | int gsi = 0; |
| 329 | struct mshv_user_irq_entry blank_entry = { 0 }; |
| 330 | |
| 331 | /* |
| 332 | * Reserve GSI 0-23 for IOAPIC pins, to avoid conflicts of legacy |
| 333 | * peripherals with MSI-X devices |
| 334 | */ |
| 335 | for (i = 0; i < IOAPIC_NUM_PINS; i++) { |
| 336 | /* ret = add_msi_routing(0, 0); */ |
| 337 | ret = irqchip_allocate_gsi(s, &gsi); |
| 338 | if (ret < 0) { |
| 339 | error_report("Failed to reserve GSI %d: %s", gsi, strerror(-ret)); |
| 340 | return -1; |
| 341 | } |
| 342 | blank_entry.gsi = gsi; |
| 343 | add_routing_entry(s, &blank_entry); |
| 344 | } |
| 345 | |
| 346 | mshv_irqchip_commit_routes(s); |
| 347 | |
| 348 | return 0; |
| 349 | } |
| 350 | |
| 351 | void mshv_init_irq_routing(MshvState *s) |
| 352 | { |
| 353 | int ret; |
| 354 | int gsi_count = MSHV_MAX_MSI_ROUTES; |
| 355 | |
| 356 | s->irq_routes = g_malloc0(sizeof(*s->irq_routes)); |
| 357 | s->nr_allocated_irq_routes = 0; |
| 358 | s->gsi_count = gsi_count; |
| 359 | s->used_gsi_bitmap = bitmap_new(gsi_count); |
| 360 | |
| 361 | ret = mshv_reserve_ioapic_msi_routes(s); |
| 362 | if (ret < 0) { |
| 363 | error_report("Failed to reserve IOAPIC MSI routes"); |
| 364 | abort(); |
| 365 | } |
| 366 | } |