mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
125 lines
3.1 KiB
C
125 lines
3.1 KiB
C
![]() |
// 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;
|
||
|
}
|