summaryrefslogtreecommitdiff
path: root/drivers/hv/mshv_irq.c
blob: d0fb9ef734f426dfa02c05e479949ac45c0f0ae4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// 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;
}