|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * Copyright (c) 2023, Microsoft Corporation. | 
|  | * | 
|  | * Authors: Microsoft Linux virtualization team | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/slab.h> | 
|  | #include <asm/mshyperv.h> | 
|  |  | 
|  | #include "mshv_eventfd.h" | 
|  | #include "mshv.h" | 
|  | #include "mshv_root.h" | 
|  |  | 
|  | /* called from the ioctl code, user wants to update the guest irq table */ | 
|  | int mshv_update_routing_table(struct mshv_partition *partition, | 
|  | const struct mshv_user_irq_entry *ue, | 
|  | unsigned int numents) | 
|  | { | 
|  | struct mshv_girq_routing_table *new = NULL, *old; | 
|  | u32 i, nr_rt_entries = 0; | 
|  | int r = 0; | 
|  |  | 
|  | if (numents == 0) | 
|  | goto swap_routes; | 
|  |  | 
|  | for (i = 0; i < numents; i++) { | 
|  | if (ue[i].gsi >= MSHV_MAX_GUEST_IRQS) | 
|  | return -EINVAL; | 
|  |  | 
|  | if (ue[i].address_hi) | 
|  | return -EINVAL; | 
|  |  | 
|  | nr_rt_entries = max(nr_rt_entries, ue[i].gsi); | 
|  | } | 
|  | nr_rt_entries += 1; | 
|  |  | 
|  | new = kzalloc(struct_size(new, mshv_girq_info_tbl, nr_rt_entries), | 
|  | GFP_KERNEL_ACCOUNT); | 
|  | if (!new) | 
|  | return -ENOMEM; | 
|  |  | 
|  | new->num_rt_entries = nr_rt_entries; | 
|  | for (i = 0; i < numents; i++) { | 
|  | struct mshv_guest_irq_ent *girq; | 
|  |  | 
|  | girq = &new->mshv_girq_info_tbl[ue[i].gsi]; | 
|  |  | 
|  | /* | 
|  | * Allow only one to one mapping between GSI and MSI routing. | 
|  | */ | 
|  | if (girq->guest_irq_num != 0) { | 
|  | r = -EINVAL; | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | girq->guest_irq_num = ue[i].gsi; | 
|  | girq->girq_addr_lo = ue[i].address_lo; | 
|  | girq->girq_addr_hi = ue[i].address_hi; | 
|  | girq->girq_irq_data = ue[i].data; | 
|  | girq->girq_entry_valid = true; | 
|  | } | 
|  |  | 
|  | swap_routes: | 
|  | mutex_lock(&partition->pt_irq_lock); | 
|  | old = rcu_dereference_protected(partition->pt_girq_tbl, 1); | 
|  | rcu_assign_pointer(partition->pt_girq_tbl, new); | 
|  | mshv_irqfd_routing_update(partition); | 
|  | mutex_unlock(&partition->pt_irq_lock); | 
|  |  | 
|  | synchronize_srcu_expedited(&partition->pt_irq_srcu); | 
|  | new = old; | 
|  |  | 
|  | out: | 
|  | kfree(new); | 
|  |  | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /* vm is going away, kfree the irq routing table */ | 
|  | void mshv_free_routing_table(struct mshv_partition *partition) | 
|  | { | 
|  | struct mshv_girq_routing_table *rt = | 
|  | rcu_access_pointer(partition->pt_girq_tbl); | 
|  |  | 
|  | kfree(rt); | 
|  | } | 
|  |  | 
|  | struct mshv_guest_irq_ent | 
|  | mshv_ret_girq_entry(struct mshv_partition *partition, u32 irqnum) | 
|  | { | 
|  | struct mshv_guest_irq_ent entry = { 0 }; | 
|  | struct mshv_girq_routing_table *girq_tbl; | 
|  |  | 
|  | girq_tbl = srcu_dereference_check(partition->pt_girq_tbl, | 
|  | &partition->pt_irq_srcu, | 
|  | lockdep_is_held(&partition->pt_irq_lock)); | 
|  | if (!girq_tbl || irqnum >= girq_tbl->num_rt_entries) { | 
|  | /* | 
|  | * Premature register_irqfd, setting valid_entry = 0 | 
|  | * would ignore this entry anyway | 
|  | */ | 
|  | entry.guest_irq_num = irqnum; | 
|  | return entry; | 
|  | } | 
|  |  | 
|  | return girq_tbl->mshv_girq_info_tbl[irqnum]; | 
|  | } | 
|  |  | 
|  | void mshv_copy_girq_info(struct mshv_guest_irq_ent *ent, | 
|  | struct mshv_lapic_irq *lirq) | 
|  | { | 
|  | memset(lirq, 0, sizeof(*lirq)); | 
|  | if (!ent || !ent->girq_entry_valid) | 
|  | return; | 
|  |  | 
|  | lirq->lapic_vector = ent->girq_irq_data & 0xFF; | 
|  | lirq->lapic_apic_id = (ent->girq_addr_lo >> 12) & 0xFF; | 
|  | lirq->lapic_control.interrupt_type = (ent->girq_irq_data & 0x700) >> 8; | 
|  | lirq->lapic_control.level_triggered = (ent->girq_irq_data >> 15) & 0x1; | 
|  | lirq->lapic_control.logical_dest_mode = (ent->girq_addr_lo >> 2) & 0x1; | 
|  | } |