1 /*
2 * Copyright (C) 2018-2022 Intel Corporation.
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <err.h>
11 #include <errno.h>
12 #include <sys/queue.h>
13 #include <pciaccess.h>
14
15 #include "pci_core.h"
16 #include "mevent.h"
17
18 #define MAX_DEV_PER_GSI 4
19
20 /* physical info of the GSI sharing group */
21 struct gsi_sharing_group {
22 uint8_t gsi;
23 /* the number of devices that are sharing the same GSI */
24 int shared_dev_num;
25 struct {
26 char *dev_name;
27 int assigned_to_this_vm;
28 } dev[MAX_DEV_PER_GSI];
29
30 LIST_ENTRY(gsi_sharing_group) gsg_list;
31 };
32 static LIST_HEAD(gsg_struct, gsi_sharing_group) gsg_head;
33
34 static int
update_gsi_sharing_info(char * dev_name,uint8_t gsi)35 update_gsi_sharing_info(char *dev_name, uint8_t gsi)
36 {
37 int gsi_shared = 0;
38 struct gsi_sharing_group *group = NULL;
39
40 LIST_FOREACH(group, &gsg_head, gsg_list) {
41 if (gsi != group->gsi)
42 continue;
43
44 if (group->shared_dev_num >= MAX_DEV_PER_GSI) {
45 pr_err("max %d devices share one GSI", MAX_DEV_PER_GSI);
46 return -EINVAL;
47 }
48
49 gsi_shared = 1;
50 break;
51 }
52
53 if (gsi_shared == 0) {
54 group = calloc(1, sizeof(struct gsi_sharing_group));
55 if (!group) {
56 pr_err("%s: calloc FAIL!", __func__);
57 return -ENOMEM;
58 }
59
60 group->gsi = gsi;
61 group->shared_dev_num = 0;
62
63 LIST_INSERT_HEAD(&gsg_head, group, gsg_list);
64 }
65
66 if (group != NULL) {
67 group->dev[group->shared_dev_num].dev_name = dev_name;
68 group->dev[group->shared_dev_num].assigned_to_this_vm = 0;
69 group->shared_dev_num++;
70 }
71
72 return 0;
73 }
74
75 /*
76 * return 1 if MSI/MSI-x is supported
77 * return 0 if MSI/MSI-x is NOT supported
78 */
79 static int
check_msi_capability(char * dev_name)80 check_msi_capability(char *dev_name)
81 {
82 int bus, slot, func;
83 uint16_t status;
84 uint8_t cap_ptr, cap_id;
85 struct pci_device *phys_dev;
86
87 /* only check the MSI/MSI-x capability for PCI device */
88 if (parse_bdf(dev_name, &bus, &slot, &func, 16) != 0)
89 return 0;
90
91 phys_dev = pci_device_find_by_slot(0, bus, slot, func);
92 if (!phys_dev)
93 return 0;
94
95 pci_device_cfg_read_u16(phys_dev, &status, PCIR_STATUS);
96 if (status & PCIM_STATUS_CAPPRESENT) {
97 pci_device_cfg_read_u8(phys_dev, &cap_ptr, PCIR_CAP_PTR);
98
99 while (cap_ptr != 0 && cap_ptr != 0xff) {
100 pci_device_cfg_read_u8(phys_dev, &cap_id,
101 cap_ptr + PCICAP_ID);
102
103 if (cap_id == PCIY_MSI)
104 return 1;
105 else if (cap_id == PCIY_MSIX)
106 return 1;
107
108 pci_device_cfg_read_u8(phys_dev, &cap_ptr,
109 cap_ptr + PCICAP_NEXTPTR);
110 }
111 }
112 return 0;
113 }
114
115 int
create_gsi_sharing_groups(void)116 create_gsi_sharing_groups(void)
117 {
118 uint8_t gsi;
119 char *dev_name;
120 int i, error, msi_support;
121 struct gsi_sharing_group *group = NULL, *temp = NULL;
122
123 error = pciaccess_init();
124 if (error < 0)
125 return error;
126
127 for (i = 0; i < num_gsi_dev_mapping_tables; i++) {
128 dev_name = gsi_dev_mapping_tables[i].dev_name;
129 gsi = gsi_dev_mapping_tables[i].gsi;
130
131 /* skip the devices that support MSI/MSI-x */
132 msi_support = check_msi_capability(dev_name);
133 if (msi_support == 1)
134 continue;
135
136 /* insert the device into gsi_sharing_group */
137 error = update_gsi_sharing_info(dev_name, gsi);
138 if (error < 0)
139 return error;
140 }
141
142 pciaccess_cleanup();
143
144 /*
145 * clean up gsg_head - the list for gsi_sharing_group
146 * delete the element without gsi sharing condition (shared_dev_num < 2)
147 */
148 list_foreach_safe(group, &gsg_head, gsg_list, temp) {
149 if (group->shared_dev_num < 2) {
150 LIST_REMOVE(group, gsg_list);
151 free(group);
152 }
153 }
154
155 return 0;
156 }
157
158 /*
159 * update passthrough info in gsi_sharing_group
160 * set assigned_to_this_vm as 1 if the PCI device is assigned to current VM
161 */
162 void
update_pt_info(uint16_t phys_bdf)163 update_pt_info(uint16_t phys_bdf)
164 {
165 char *name;
166 int i, bus, slot, func;
167 struct gsi_sharing_group *group;
168
169 LIST_FOREACH(group, &gsg_head, gsg_list) {
170 for (i = 0; i < (group->shared_dev_num); i++) {
171 name = group->dev[i].dev_name;
172 if (parse_bdf(name, &bus, &slot, &func, 16) != 0)
173 continue;
174
175 if (phys_bdf == (PCI_BDF(bus, slot, func)))
176 group->dev[i].assigned_to_this_vm = 1;
177 }
178 }
179 }
180
181 /* check if all devices in one GSI sharing group are assigned to same VM */
182 int
check_gsi_sharing_violation(void)183 check_gsi_sharing_violation(void)
184 {
185 struct gsi_sharing_group *group, *temp;
186 int i, error, violation;
187
188 error = 0;
189 LIST_FOREACH(group, &gsg_head, gsg_list) {
190 /*
191 * All the PCI devices that are sharing the same GSI should be
192 * assigned to same VM to avoid physical GSI sharing between
193 * multiple VMs.
194 *
195 * If the value of 'assigned_to_this_vm' for each device in one
196 * gsi_sharing_group are all the same, either all 0 or all 1, it
197 * indicates that there is no GSI sharing between multiple VMs.
198 * All 0 means that all devices are NOT assigned to current VM.
199 * All 1 means that all devices are assigned to current VM.
200 *
201 * Otherwise, the passthrough will be rejected due to violation.
202 */
203 violation = 0;
204 for (i = 1; i < (group->shared_dev_num); i++) {
205 if (group->dev[i].assigned_to_this_vm !=
206 group->dev[i - 1].assigned_to_this_vm) {
207 violation = 1;
208 break;
209 }
210 }
211
212 if (violation == 0)
213 continue;
214
215 /* reject the passthrough since gsi sharing violation occurs */
216 pr_err("GSI SHARING VIOLATION!");
217 pr_err("following physical devices are sharing same GSI, please "
218 "assign them to same VM to avoid physical GSI sharing "
219 "between multiple VMs");
220 for (i = 0; i < (group->shared_dev_num); i++) {
221 pr_info("device %s \t assigned_to_this_vm %d",
222 group->dev[i].dev_name,
223 group->dev[i].assigned_to_this_vm);
224 }
225 error = -EINVAL;
226 break;
227 }
228
229 /* destroy the gsg_head after all the checks have been done */
230 list_foreach_safe(group, &gsg_head, gsg_list, temp) {
231 LIST_REMOVE(group, gsg_list);
232 free(group);
233 }
234
235 return error;
236 }
237