/root/src/xen/xen/arch/x86/hvm/vmsi.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2001 MandrakeSoft S.A. |
3 | | * |
4 | | * MandrakeSoft S.A. |
5 | | * 43, rue d'Aboukir |
6 | | * 75002 Paris - France |
7 | | * http://www.linux-mandrake.com/ |
8 | | * http://www.mandrakesoft.com/ |
9 | | * |
10 | | * This library is free software; you can redistribute it and/or |
11 | | * modify it under the terms of the GNU Lesser General Public |
12 | | * License as published by the Free Software Foundation; either |
13 | | * version 2 of the License, or (at your option) any later version. |
14 | | * |
15 | | * This library is distributed in the hope that it will be useful, |
16 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 | | * Lesser General Public License for more details. |
19 | | * |
20 | | * You should have received a copy of the GNU Lesser General Public |
21 | | * License along with this library; If not, see <http://www.gnu.org/licenses/>. |
22 | | * |
23 | | * Support for virtual MSI logic |
24 | | * Will be merged it with virtual IOAPIC logic, since most is the same |
25 | | */ |
26 | | |
27 | | #include <xen/types.h> |
28 | | #include <xen/mm.h> |
29 | | #include <xen/xmalloc.h> |
30 | | #include <xen/lib.h> |
31 | | #include <xen/errno.h> |
32 | | #include <xen/sched.h> |
33 | | #include <xen/irq.h> |
34 | | #include <xen/vpci.h> |
35 | | #include <public/hvm/ioreq.h> |
36 | | #include <asm/hvm/io.h> |
37 | | #include <asm/hvm/vpic.h> |
38 | | #include <asm/hvm/vlapic.h> |
39 | | #include <asm/hvm/support.h> |
40 | | #include <asm/current.h> |
41 | | #include <asm/event.h> |
42 | | #include <asm/io_apic.h> |
43 | | |
44 | | static void vmsi_inj_irq( |
45 | | struct vlapic *target, |
46 | | uint8_t vector, |
47 | | uint8_t trig_mode, |
48 | | uint8_t delivery_mode) |
49 | 3.84k | { |
50 | 3.84k | HVM_DBG_LOG(DBG_LEVEL_VLAPIC, "vmsi_inj_irq: vec %02x trig %d dm %d\n", |
51 | 3.84k | vector, trig_mode, delivery_mode); |
52 | 3.84k | |
53 | 3.84k | switch ( delivery_mode ) |
54 | 3.84k | { |
55 | 3.84k | case dest_Fixed: |
56 | 3.84k | case dest_LowestPrio: |
57 | 3.84k | vlapic_set_irq(target, vector, trig_mode); |
58 | 3.84k | break; |
59 | 0 | default: |
60 | 0 | BUG(); |
61 | 3.84k | } |
62 | 3.84k | } |
63 | | |
64 | | int vmsi_deliver( |
65 | | struct domain *d, int vector, |
66 | | uint8_t dest, uint8_t dest_mode, |
67 | | uint8_t delivery_mode, uint8_t trig_mode) |
68 | 3.84k | { |
69 | 3.84k | struct vlapic *target; |
70 | 3.84k | struct vcpu *v; |
71 | 3.84k | |
72 | 3.84k | switch ( delivery_mode ) |
73 | 3.84k | { |
74 | 0 | case dest_LowestPrio: |
75 | 0 | target = vlapic_lowest_prio(d, NULL, 0, dest, dest_mode); |
76 | 0 | if ( target != NULL ) |
77 | 0 | { |
78 | 0 | vmsi_inj_irq(target, vector, trig_mode, delivery_mode); |
79 | 0 | break; |
80 | 0 | } |
81 | 0 | HVM_DBG_LOG(DBG_LEVEL_VLAPIC, "null MSI round robin: vector=%02x\n", |
82 | 0 | vector); |
83 | 0 | return -ESRCH; |
84 | 0 |
|
85 | 3.84k | case dest_Fixed: |
86 | 3.84k | for_each_vcpu ( d, v ) |
87 | 46.1k | if ( vlapic_match_dest(vcpu_vlapic(v), NULL, |
88 | 46.1k | 0, dest, dest_mode) ) |
89 | 3.84k | vmsi_inj_irq(vcpu_vlapic(v), vector, |
90 | 3.84k | trig_mode, delivery_mode); |
91 | 3.84k | break; |
92 | 0 |
|
93 | 0 | default: |
94 | 0 | printk(XENLOG_G_WARNING |
95 | 0 | "%pv: Unsupported MSI delivery mode %d for Dom%d\n", |
96 | 0 | current, delivery_mode, d->domain_id); |
97 | 0 | return -EINVAL; |
98 | 3.84k | } |
99 | 3.84k | |
100 | 3.84k | return 0; |
101 | 3.84k | } |
102 | | |
103 | | void vmsi_deliver_pirq(struct domain *d, const struct hvm_pirq_dpci *pirq_dpci) |
104 | 3.84k | { |
105 | 3.84k | uint32_t flags = pirq_dpci->gmsi.gflags; |
106 | 3.84k | int vector = pirq_dpci->gmsi.gvec; |
107 | 3.84k | uint8_t dest = (uint8_t)flags; |
108 | 3.84k | bool dest_mode = flags & XEN_DOMCTL_VMSI_X86_DM_MASK; |
109 | 3.84k | uint8_t delivery_mode = MASK_EXTR(flags, XEN_DOMCTL_VMSI_X86_DELIV_MASK); |
110 | 3.84k | bool trig_mode = flags & XEN_DOMCTL_VMSI_X86_TRIG_MASK; |
111 | 3.84k | |
112 | 3.84k | HVM_DBG_LOG(DBG_LEVEL_IOAPIC, |
113 | 3.84k | "msi: dest=%x dest_mode=%x delivery_mode=%x " |
114 | 3.84k | "vector=%x trig_mode=%x\n", |
115 | 3.84k | dest, dest_mode, delivery_mode, vector, trig_mode); |
116 | 3.84k | |
117 | 3.84k | ASSERT(pirq_dpci->flags & HVM_IRQ_DPCI_GUEST_MSI); |
118 | 3.84k | |
119 | 3.84k | vmsi_deliver(d, vector, dest, dest_mode, delivery_mode, trig_mode); |
120 | 3.84k | } |
121 | | |
122 | | /* Return value, -1 : multi-dests, non-negative value: dest_vcpu_id */ |
123 | | int hvm_girq_dest_2_vcpu_id(struct domain *d, uint8_t dest, uint8_t dest_mode) |
124 | 42 | { |
125 | 42 | int dest_vcpu_id = -1, w = 0; |
126 | 42 | struct vcpu *v; |
127 | 42 | |
128 | 42 | if ( d->max_vcpus == 1 ) |
129 | 0 | return 0; |
130 | 42 | |
131 | 42 | for_each_vcpu ( d, v ) |
132 | 504 | { |
133 | 504 | if ( vlapic_match_dest(vcpu_vlapic(v), NULL, 0, dest, dest_mode) ) |
134 | 42 | { |
135 | 42 | w++; |
136 | 42 | dest_vcpu_id = v->vcpu_id; |
137 | 42 | } |
138 | 504 | } |
139 | 42 | if ( w > 1 ) |
140 | 0 | return -1; |
141 | 42 | |
142 | 42 | return dest_vcpu_id; |
143 | 42 | } |
144 | | |
145 | | /* MSI-X mask bit hypervisor interception */ |
146 | | struct msixtbl_entry |
147 | | { |
148 | | struct list_head list; |
149 | | atomic_t refcnt; /* how many bind_pt_irq called for the device */ |
150 | | |
151 | | /* TODO: resolve the potential race by destruction of pdev */ |
152 | | struct pci_dev *pdev; |
153 | | unsigned long gtable; /* gpa of msix table */ |
154 | | DECLARE_BITMAP(table_flags, MAX_MSIX_TABLE_ENTRIES); |
155 | 0 | #define MAX_MSIX_ACC_ENTRIES 3 |
156 | | unsigned int table_len; |
157 | | struct { |
158 | | uint32_t msi_ad[3]; /* Shadow of address low, high and data */ |
159 | | } gentries[MAX_MSIX_ACC_ENTRIES]; |
160 | | DECLARE_BITMAP(acc_valid, 3 * MAX_MSIX_ACC_ENTRIES); |
161 | | #define acc_bit(what, ent, slot, idx) \ |
162 | 0 | what##_bit((slot) * 3 + (idx), (ent)->acc_valid) |
163 | | struct rcu_head rcu; |
164 | | }; |
165 | | |
166 | | static DEFINE_RCU_READ_LOCK(msixtbl_rcu_lock); |
167 | | |
168 | | /* |
169 | | * MSI-X table infrastructure is dynamically initialised when an MSI-X capable |
170 | | * device is passed through to a domain, rather than unconditionally for all |
171 | | * domains. |
172 | | */ |
173 | | static bool msixtbl_initialised(const struct domain *d) |
174 | 0 | { |
175 | 0 | return !!d->arch.hvm_domain.msixtbl_list.next; |
176 | 0 | } |
177 | | |
178 | | static struct msixtbl_entry *msixtbl_find_entry( |
179 | | struct vcpu *v, unsigned long addr) |
180 | 0 | { |
181 | 0 | struct msixtbl_entry *entry; |
182 | 0 | struct domain *d = v->domain; |
183 | 0 |
|
184 | 0 | list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list ) |
185 | 0 | if ( addr >= entry->gtable && |
186 | 0 | addr < entry->gtable + entry->table_len ) |
187 | 0 | return entry; |
188 | 0 |
|
189 | 0 | return NULL; |
190 | 0 | } |
191 | | |
192 | | static struct msi_desc *msixtbl_addr_to_desc( |
193 | | const struct msixtbl_entry *entry, unsigned long addr) |
194 | 0 | { |
195 | 0 | unsigned int nr_entry; |
196 | 0 | struct msi_desc *desc; |
197 | 0 |
|
198 | 0 | if ( !entry || !entry->pdev ) |
199 | 0 | return NULL; |
200 | 0 |
|
201 | 0 | nr_entry = (addr - entry->gtable) / PCI_MSIX_ENTRY_SIZE; |
202 | 0 |
|
203 | 0 | list_for_each_entry( desc, &entry->pdev->msi_list, list ) |
204 | 0 | if ( desc->msi_attrib.type == PCI_CAP_ID_MSIX && |
205 | 0 | desc->msi_attrib.entry_nr == nr_entry ) |
206 | 0 | return desc; |
207 | 0 |
|
208 | 0 | return NULL; |
209 | 0 | } |
210 | | |
211 | | static int msixtbl_read(const struct hvm_io_handler *handler, |
212 | | uint64_t address, uint32_t len, uint64_t *pval) |
213 | 0 | { |
214 | 0 | unsigned long offset; |
215 | 0 | struct msixtbl_entry *entry; |
216 | 0 | unsigned int nr_entry, index; |
217 | 0 | int r = X86EMUL_UNHANDLEABLE; |
218 | 0 |
|
219 | 0 | if ( (len != 4 && len != 8) || (address & (len - 1)) ) |
220 | 0 | return r; |
221 | 0 |
|
222 | 0 | rcu_read_lock(&msixtbl_rcu_lock); |
223 | 0 |
|
224 | 0 | entry = msixtbl_find_entry(current, address); |
225 | 0 | if ( !entry ) |
226 | 0 | goto out; |
227 | 0 | offset = address & (PCI_MSIX_ENTRY_SIZE - 1); |
228 | 0 |
|
229 | 0 | if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET ) |
230 | 0 | { |
231 | 0 | nr_entry = (address - entry->gtable) / PCI_MSIX_ENTRY_SIZE; |
232 | 0 | index = offset / sizeof(uint32_t); |
233 | 0 | if ( nr_entry >= MAX_MSIX_ACC_ENTRIES || |
234 | 0 | !acc_bit(test, entry, nr_entry, index) ) |
235 | 0 | goto out; |
236 | 0 | *pval = entry->gentries[nr_entry].msi_ad[index]; |
237 | 0 | if ( len == 8 ) |
238 | 0 | { |
239 | 0 | if ( index ) |
240 | 0 | offset = PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET; |
241 | 0 | else if ( acc_bit(test, entry, nr_entry, 1) ) |
242 | 0 | *pval |= (u64)entry->gentries[nr_entry].msi_ad[1] << 32; |
243 | 0 | else |
244 | 0 | goto out; |
245 | 0 | } |
246 | 0 | } |
247 | 0 | if ( offset == PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET ) |
248 | 0 | { |
249 | 0 | const struct msi_desc *msi_desc = msixtbl_addr_to_desc(entry, address); |
250 | 0 |
|
251 | 0 | if ( !msi_desc ) |
252 | 0 | goto out; |
253 | 0 | if ( len == 4 ) |
254 | 0 | *pval = MASK_INSR(msi_desc->msi_attrib.guest_masked, |
255 | 0 | PCI_MSIX_VECTOR_BITMASK); |
256 | 0 | else |
257 | 0 | *pval |= (u64)MASK_INSR(msi_desc->msi_attrib.guest_masked, |
258 | 0 | PCI_MSIX_VECTOR_BITMASK) << 32; |
259 | 0 | } |
260 | 0 | |
261 | 0 | r = X86EMUL_OKAY; |
262 | 0 | out: |
263 | 0 | rcu_read_unlock(&msixtbl_rcu_lock); |
264 | 0 | return r; |
265 | 0 | } |
266 | | |
267 | | static int msixtbl_write(struct vcpu *v, unsigned long address, |
268 | | unsigned int len, unsigned long val) |
269 | 0 | { |
270 | 0 | unsigned long offset; |
271 | 0 | struct msixtbl_entry *entry; |
272 | 0 | const struct msi_desc *msi_desc; |
273 | 0 | unsigned int nr_entry, index; |
274 | 0 | int r = X86EMUL_UNHANDLEABLE; |
275 | 0 | unsigned long flags; |
276 | 0 | struct irq_desc *desc; |
277 | 0 |
|
278 | 0 | if ( (len != 4 && len != 8) || (address & (len - 1)) ) |
279 | 0 | return r; |
280 | 0 |
|
281 | 0 | rcu_read_lock(&msixtbl_rcu_lock); |
282 | 0 |
|
283 | 0 | entry = msixtbl_find_entry(v, address); |
284 | 0 | if ( !entry ) |
285 | 0 | goto out; |
286 | 0 | nr_entry = (address - entry->gtable) / PCI_MSIX_ENTRY_SIZE; |
287 | 0 |
|
288 | 0 | offset = address & (PCI_MSIX_ENTRY_SIZE - 1); |
289 | 0 | if ( offset != PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET ) |
290 | 0 | { |
291 | 0 | index = offset / sizeof(uint32_t); |
292 | 0 | if ( nr_entry < MAX_MSIX_ACC_ENTRIES ) |
293 | 0 | { |
294 | 0 | entry->gentries[nr_entry].msi_ad[index] = val; |
295 | 0 | acc_bit(set, entry, nr_entry, index); |
296 | 0 | if ( len == 8 && !index ) |
297 | 0 | { |
298 | 0 | entry->gentries[nr_entry].msi_ad[1] = val >> 32; |
299 | 0 | acc_bit(set, entry, nr_entry, 1); |
300 | 0 | } |
301 | 0 | } |
302 | 0 | set_bit(nr_entry, &entry->table_flags); |
303 | 0 | if ( len != 8 || !index ) |
304 | 0 | goto out; |
305 | 0 | val >>= 32; |
306 | 0 | address += 4; |
307 | 0 | } |
308 | 0 |
|
309 | 0 | /* Exit to device model when unmasking and address/data got modified. */ |
310 | 0 | if ( !(val & PCI_MSIX_VECTOR_BITMASK) && |
311 | 0 | test_and_clear_bit(nr_entry, &entry->table_flags) ) |
312 | 0 | { |
313 | 0 | v->arch.hvm_vcpu.hvm_io.msix_unmask_address = address; |
314 | 0 | goto out; |
315 | 0 | } |
316 | 0 |
|
317 | 0 | msi_desc = msixtbl_addr_to_desc(entry, address); |
318 | 0 | if ( !msi_desc || msi_desc->irq < 0 ) |
319 | 0 | goto out; |
320 | 0 | |
321 | 0 | desc = irq_to_desc(msi_desc->irq); |
322 | 0 | if ( !desc ) |
323 | 0 | goto out; |
324 | 0 |
|
325 | 0 | spin_lock_irqsave(&desc->lock, flags); |
326 | 0 |
|
327 | 0 | if ( !desc->msi_desc ) |
328 | 0 | goto unlock; |
329 | 0 |
|
330 | 0 | ASSERT(msi_desc == desc->msi_desc); |
331 | 0 | |
332 | 0 | guest_mask_msi_irq(desc, !!(val & PCI_MSIX_VECTOR_BITMASK)); |
333 | 0 |
|
334 | 0 | unlock: |
335 | 0 | spin_unlock_irqrestore(&desc->lock, flags); |
336 | 0 | if ( len == 4 ) |
337 | 0 | r = X86EMUL_OKAY; |
338 | 0 |
|
339 | 0 | out: |
340 | 0 | rcu_read_unlock(&msixtbl_rcu_lock); |
341 | 0 | return r; |
342 | 0 | } |
343 | | |
344 | | static int _msixtbl_write(const struct hvm_io_handler *handler, |
345 | | uint64_t address, uint32_t len, uint64_t val) |
346 | 0 | { |
347 | 0 | return msixtbl_write(current, address, len, val); |
348 | 0 | } |
349 | | |
350 | | static bool_t msixtbl_range(const struct hvm_io_handler *handler, |
351 | | const ioreq_t *r) |
352 | 0 | { |
353 | 0 | struct vcpu *curr = current; |
354 | 0 | unsigned long addr = r->addr; |
355 | 0 | const struct msi_desc *desc; |
356 | 0 |
|
357 | 0 | ASSERT(r->type == IOREQ_TYPE_COPY); |
358 | 0 |
|
359 | 0 | rcu_read_lock(&msixtbl_rcu_lock); |
360 | 0 | desc = msixtbl_addr_to_desc(msixtbl_find_entry(curr, addr), addr); |
361 | 0 | rcu_read_unlock(&msixtbl_rcu_lock); |
362 | 0 |
|
363 | 0 | if ( desc ) |
364 | 0 | return 1; |
365 | 0 |
|
366 | 0 | if ( r->state == STATE_IOREQ_READY && r->dir == IOREQ_WRITE ) |
367 | 0 | { |
368 | 0 | unsigned int size = r->size; |
369 | 0 |
|
370 | 0 | if ( !r->data_is_ptr ) |
371 | 0 | { |
372 | 0 | uint64_t data = r->data; |
373 | 0 |
|
374 | 0 | if ( size == 8 ) |
375 | 0 | { |
376 | 0 | BUILD_BUG_ON(!(PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET & 4)); |
377 | 0 | data >>= 32; |
378 | 0 | addr += size = 4; |
379 | 0 | } |
380 | 0 | if ( size == 4 && |
381 | 0 | ((addr & (PCI_MSIX_ENTRY_SIZE - 1)) == |
382 | 0 | PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET) && |
383 | 0 | !(data & PCI_MSIX_VECTOR_BITMASK) ) |
384 | 0 | { |
385 | 0 | curr->arch.hvm_vcpu.hvm_io.msix_snoop_address = addr; |
386 | 0 | curr->arch.hvm_vcpu.hvm_io.msix_snoop_gpa = 0; |
387 | 0 | } |
388 | 0 | } |
389 | 0 | else if ( (size == 4 || size == 8) && |
390 | 0 | /* Only support forward REP MOVS for now. */ |
391 | 0 | !r->df && |
392 | 0 | /* |
393 | 0 | * Only fully support accesses to a single table entry for |
394 | 0 | * now (if multiple ones get written to in one go, only the |
395 | 0 | * final one gets dealt with). |
396 | 0 | */ |
397 | 0 | r->count && r->count <= PCI_MSIX_ENTRY_SIZE / size && |
398 | 0 | !((addr + (size * r->count)) & (PCI_MSIX_ENTRY_SIZE - 1)) ) |
399 | 0 | { |
400 | 0 | BUILD_BUG_ON((PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET + 4) & |
401 | 0 | (PCI_MSIX_ENTRY_SIZE - 1)); |
402 | 0 |
|
403 | 0 | curr->arch.hvm_vcpu.hvm_io.msix_snoop_address = |
404 | 0 | addr + size * r->count - 4; |
405 | 0 | curr->arch.hvm_vcpu.hvm_io.msix_snoop_gpa = |
406 | 0 | r->data + size * r->count - 4; |
407 | 0 | } |
408 | 0 | } |
409 | 0 |
|
410 | 0 | return 0; |
411 | 0 | } |
412 | | |
413 | | static const struct hvm_io_ops msixtbl_mmio_ops = { |
414 | | .accept = msixtbl_range, |
415 | | .read = msixtbl_read, |
416 | | .write = _msixtbl_write, |
417 | | }; |
418 | | |
419 | | static void add_msixtbl_entry(struct domain *d, |
420 | | struct pci_dev *pdev, |
421 | | uint64_t gtable, |
422 | | struct msixtbl_entry *entry) |
423 | 0 | { |
424 | 0 | INIT_LIST_HEAD(&entry->list); |
425 | 0 | INIT_RCU_HEAD(&entry->rcu); |
426 | 0 | atomic_set(&entry->refcnt, 0); |
427 | 0 |
|
428 | 0 | entry->table_len = pdev->msix->nr_entries * PCI_MSIX_ENTRY_SIZE; |
429 | 0 | entry->pdev = pdev; |
430 | 0 | entry->gtable = (unsigned long) gtable; |
431 | 0 |
|
432 | 0 | list_add_rcu(&entry->list, &d->arch.hvm_domain.msixtbl_list); |
433 | 0 | } |
434 | | |
435 | | static void free_msixtbl_entry(struct rcu_head *rcu) |
436 | 0 | { |
437 | 0 | struct msixtbl_entry *entry; |
438 | 0 |
|
439 | 0 | entry = container_of (rcu, struct msixtbl_entry, rcu); |
440 | 0 |
|
441 | 0 | xfree(entry); |
442 | 0 | } |
443 | | |
444 | | static void del_msixtbl_entry(struct msixtbl_entry *entry) |
445 | 0 | { |
446 | 0 | list_del_rcu(&entry->list); |
447 | 0 | call_rcu(&entry->rcu, free_msixtbl_entry); |
448 | 0 | } |
449 | | |
450 | | int msixtbl_pt_register(struct domain *d, struct pirq *pirq, uint64_t gtable) |
451 | 0 | { |
452 | 0 | struct irq_desc *irq_desc; |
453 | 0 | struct msi_desc *msi_desc; |
454 | 0 | struct pci_dev *pdev; |
455 | 0 | struct msixtbl_entry *entry, *new_entry; |
456 | 0 | int r = -EINVAL; |
457 | 0 |
|
458 | 0 | ASSERT(pcidevs_locked()); |
459 | 0 | ASSERT(spin_is_locked(&d->event_lock)); |
460 | 0 |
|
461 | 0 | if ( !msixtbl_initialised(d) ) |
462 | 0 | return -ENODEV; |
463 | 0 |
|
464 | 0 | /* |
465 | 0 | * xmalloc() with irq_disabled causes the failure of check_lock() |
466 | 0 | * for xenpool->lock. So we allocate an entry beforehand. |
467 | 0 | */ |
468 | 0 | new_entry = xzalloc(struct msixtbl_entry); |
469 | 0 | if ( !new_entry ) |
470 | 0 | return -ENOMEM; |
471 | 0 |
|
472 | 0 | irq_desc = pirq_spin_lock_irq_desc(pirq, NULL); |
473 | 0 | if ( !irq_desc ) |
474 | 0 | { |
475 | 0 | xfree(new_entry); |
476 | 0 | return r; |
477 | 0 | } |
478 | 0 |
|
479 | 0 | msi_desc = irq_desc->msi_desc; |
480 | 0 | if ( !msi_desc ) |
481 | 0 | goto out; |
482 | 0 |
|
483 | 0 | pdev = msi_desc->dev; |
484 | 0 |
|
485 | 0 | list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list ) |
486 | 0 | if ( pdev == entry->pdev ) |
487 | 0 | goto found; |
488 | 0 |
|
489 | 0 | entry = new_entry; |
490 | 0 | new_entry = NULL; |
491 | 0 | add_msixtbl_entry(d, pdev, gtable, entry); |
492 | 0 |
|
493 | 0 | found: |
494 | 0 | atomic_inc(&entry->refcnt); |
495 | 0 | r = 0; |
496 | 0 |
|
497 | 0 | out: |
498 | 0 | spin_unlock_irq(&irq_desc->lock); |
499 | 0 | xfree(new_entry); |
500 | 0 |
|
501 | 0 | if ( !r ) |
502 | 0 | { |
503 | 0 | struct vcpu *v; |
504 | 0 |
|
505 | 0 | for_each_vcpu ( d, v ) |
506 | 0 | { |
507 | 0 | if ( (v->pause_flags & VPF_blocked_in_xen) && |
508 | 0 | !v->arch.hvm_vcpu.hvm_io.msix_snoop_gpa && |
509 | 0 | v->arch.hvm_vcpu.hvm_io.msix_snoop_address == |
510 | 0 | (gtable + msi_desc->msi_attrib.entry_nr * |
511 | 0 | PCI_MSIX_ENTRY_SIZE + |
512 | 0 | PCI_MSIX_ENTRY_VECTOR_CTRL_OFFSET) ) |
513 | 0 | v->arch.hvm_vcpu.hvm_io.msix_unmask_address = |
514 | 0 | v->arch.hvm_vcpu.hvm_io.msix_snoop_address; |
515 | 0 | } |
516 | 0 | } |
517 | 0 |
|
518 | 0 | return r; |
519 | 0 | } |
520 | | |
521 | | void msixtbl_pt_unregister(struct domain *d, struct pirq *pirq) |
522 | 0 | { |
523 | 0 | struct irq_desc *irq_desc; |
524 | 0 | struct msi_desc *msi_desc; |
525 | 0 | struct pci_dev *pdev; |
526 | 0 | struct msixtbl_entry *entry; |
527 | 0 |
|
528 | 0 | ASSERT(pcidevs_locked()); |
529 | 0 | ASSERT(spin_is_locked(&d->event_lock)); |
530 | 0 |
|
531 | 0 | if ( !msixtbl_initialised(d) ) |
532 | 0 | return; |
533 | 0 |
|
534 | 0 | irq_desc = pirq_spin_lock_irq_desc(pirq, NULL); |
535 | 0 | if ( !irq_desc ) |
536 | 0 | return; |
537 | 0 |
|
538 | 0 | msi_desc = irq_desc->msi_desc; |
539 | 0 | if ( !msi_desc ) |
540 | 0 | goto out; |
541 | 0 |
|
542 | 0 | pdev = msi_desc->dev; |
543 | 0 |
|
544 | 0 | list_for_each_entry( entry, &d->arch.hvm_domain.msixtbl_list, list ) |
545 | 0 | if ( pdev == entry->pdev ) |
546 | 0 | goto found; |
547 | 0 |
|
548 | 0 | out: |
549 | 0 | spin_unlock_irq(&irq_desc->lock); |
550 | 0 | return; |
551 | 0 |
|
552 | 0 | found: |
553 | 0 | if ( !atomic_dec_and_test(&entry->refcnt) ) |
554 | 0 | del_msixtbl_entry(entry); |
555 | 0 |
|
556 | 0 | spin_unlock_irq(&irq_desc->lock); |
557 | 0 | } |
558 | | |
559 | | void msixtbl_init(struct domain *d) |
560 | 0 | { |
561 | 0 | struct hvm_io_handler *handler; |
562 | 0 |
|
563 | 0 | if ( !is_hvm_domain(d) || !has_vlapic(d) || msixtbl_initialised(d) ) |
564 | 0 | return; |
565 | 0 |
|
566 | 0 | INIT_LIST_HEAD(&d->arch.hvm_domain.msixtbl_list); |
567 | 0 |
|
568 | 0 | handler = hvm_next_io_handler(d); |
569 | 0 | if ( handler ) |
570 | 0 | { |
571 | 0 | handler->type = IOREQ_TYPE_COPY; |
572 | 0 | handler->ops = &msixtbl_mmio_ops; |
573 | 0 | } |
574 | 0 | } |
575 | | |
576 | | void msixtbl_pt_cleanup(struct domain *d) |
577 | 0 | { |
578 | 0 | struct msixtbl_entry *entry, *temp; |
579 | 0 |
|
580 | 0 | if ( !msixtbl_initialised(d) ) |
581 | 0 | return; |
582 | 0 |
|
583 | 0 | spin_lock(&d->event_lock); |
584 | 0 |
|
585 | 0 | list_for_each_entry_safe( entry, temp, |
586 | 0 | &d->arch.hvm_domain.msixtbl_list, list ) |
587 | 0 | del_msixtbl_entry(entry); |
588 | 0 |
|
589 | 0 | spin_unlock(&d->event_lock); |
590 | 0 | } |
591 | | |
592 | | void msix_write_completion(struct vcpu *v) |
593 | 0 | { |
594 | 0 | unsigned long ctrl_address = v->arch.hvm_vcpu.hvm_io.msix_unmask_address; |
595 | 0 | unsigned long snoop_addr = v->arch.hvm_vcpu.hvm_io.msix_snoop_address; |
596 | 0 |
|
597 | 0 | v->arch.hvm_vcpu.hvm_io.msix_snoop_address = 0; |
598 | 0 |
|
599 | 0 | if ( !ctrl_address && snoop_addr && |
600 | 0 | v->arch.hvm_vcpu.hvm_io.msix_snoop_gpa ) |
601 | 0 | { |
602 | 0 | const struct msi_desc *desc; |
603 | 0 | uint32_t data; |
604 | 0 |
|
605 | 0 | rcu_read_lock(&msixtbl_rcu_lock); |
606 | 0 | desc = msixtbl_addr_to_desc(msixtbl_find_entry(v, snoop_addr), |
607 | 0 | snoop_addr); |
608 | 0 | rcu_read_unlock(&msixtbl_rcu_lock); |
609 | 0 |
|
610 | 0 | if ( desc && |
611 | 0 | hvm_copy_from_guest_phys(&data, |
612 | 0 | v->arch.hvm_vcpu.hvm_io.msix_snoop_gpa, |
613 | 0 | sizeof(data)) == HVMTRANS_okay && |
614 | 0 | !(data & PCI_MSIX_VECTOR_BITMASK) ) |
615 | 0 | ctrl_address = snoop_addr; |
616 | 0 | } |
617 | 0 |
|
618 | 0 | if ( !ctrl_address ) |
619 | 0 | return; |
620 | 0 |
|
621 | 0 | v->arch.hvm_vcpu.hvm_io.msix_unmask_address = 0; |
622 | 0 | if ( msixtbl_write(v, ctrl_address, 4, 0) != X86EMUL_OKAY ) |
623 | 0 | gdprintk(XENLOG_WARNING, "MSI-X write completion failure\n"); |
624 | 0 | } |
625 | | |
626 | | static unsigned int msi_gflags(uint16_t data, uint64_t addr) |
627 | 42 | { |
628 | 42 | /* |
629 | 42 | * We need to use the DOMCTL constants here because the output of this |
630 | 42 | * function is used as input to pt_irq_create_bind, which also takes the |
631 | 42 | * input from the DOMCTL itself. |
632 | 42 | */ |
633 | 42 | return MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DEST_ID_MASK), |
634 | 42 | XEN_DOMCTL_VMSI_X86_DEST_ID_MASK) | |
635 | 42 | MASK_INSR(MASK_EXTR(addr, MSI_ADDR_REDIRECTION_MASK), |
636 | 42 | XEN_DOMCTL_VMSI_X86_RH_MASK) | |
637 | 42 | MASK_INSR(MASK_EXTR(addr, MSI_ADDR_DESTMODE_MASK), |
638 | 42 | XEN_DOMCTL_VMSI_X86_DM_MASK) | |
639 | 42 | MASK_INSR(MASK_EXTR(data, MSI_DATA_DELIVERY_MODE_MASK), |
640 | 42 | XEN_DOMCTL_VMSI_X86_DELIV_MASK) | |
641 | 42 | MASK_INSR(MASK_EXTR(data, MSI_DATA_TRIGGER_MASK), |
642 | 42 | XEN_DOMCTL_VMSI_X86_TRIG_MASK); |
643 | 42 | } |
644 | | |
645 | | static void vpci_mask_pirq(struct domain *d, int pirq, bool mask) |
646 | 36 | { |
647 | 36 | unsigned long flags; |
648 | 36 | struct irq_desc *desc = domain_spin_lock_irq_desc(d, pirq, &flags); |
649 | 36 | |
650 | 36 | if ( !desc ) |
651 | 0 | return; |
652 | 36 | guest_mask_msi_irq(desc, mask); |
653 | 36 | spin_unlock_irqrestore(&desc->lock, flags); |
654 | 36 | } |
655 | | |
656 | | void vpci_msi_arch_mask(struct vpci_msi *msi, const struct pci_dev *pdev, |
657 | | unsigned int entry, bool mask) |
658 | 0 | { |
659 | 0 | vpci_mask_pirq(pdev->domain, msi->arch.pirq + entry, mask); |
660 | 0 | } |
661 | | |
662 | | static int vpci_msi_enable(const struct pci_dev *pdev, uint32_t data, |
663 | | uint64_t address, unsigned int nr, |
664 | | paddr_t table_base) |
665 | 42 | { |
666 | 42 | struct msi_info msi_info = { |
667 | 42 | .seg = pdev->seg, |
668 | 42 | .bus = pdev->bus, |
669 | 42 | .devfn = pdev->devfn, |
670 | 42 | .table_base = table_base, |
671 | 42 | .entry_nr = nr, |
672 | 42 | }; |
673 | 36 | unsigned int i, vectors = table_base ? 1 : nr; |
674 | 42 | int rc, pirq = INVALID_PIRQ; |
675 | 42 | |
676 | 42 | /* Get a PIRQ. */ |
677 | 42 | rc = allocate_and_map_msi_pirq(pdev->domain, -1, &pirq, |
678 | 36 | table_base ? MAP_PIRQ_TYPE_MSI |
679 | 6 | : MAP_PIRQ_TYPE_MULTI_MSI, |
680 | 42 | &msi_info); |
681 | 42 | if ( rc ) |
682 | 0 | { |
683 | 0 | gdprintk(XENLOG_ERR, "%04x:%02x:%02x.%u: failed to map PIRQ: %d\n", |
684 | 0 | pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), |
685 | 0 | PCI_FUNC(pdev->devfn), rc); |
686 | 0 | return rc; |
687 | 0 | } |
688 | 42 | |
689 | 84 | for ( i = 0; i < vectors; i++ ) |
690 | 42 | { |
691 | 42 | uint8_t vector = MASK_EXTR(data, MSI_DATA_VECTOR_MASK); |
692 | 42 | uint8_t vector_mask = 0xff >> (8 - fls(vectors) + 1); |
693 | 42 | struct xen_domctl_bind_pt_irq bind = { |
694 | 42 | .machine_irq = pirq + i, |
695 | 42 | .irq_type = PT_IRQ_TYPE_MSI, |
696 | 42 | .u.msi.gvec = (vector & ~vector_mask) | |
697 | 42 | ((vector + i) & vector_mask), |
698 | 42 | .u.msi.gflags = msi_gflags(data, address), |
699 | 42 | }; |
700 | 42 | |
701 | 42 | pcidevs_lock(); |
702 | 42 | rc = pt_irq_create_bind(pdev->domain, &bind); |
703 | 42 | if ( rc ) |
704 | 0 | { |
705 | 0 | gdprintk(XENLOG_ERR, |
706 | 0 | "%04x:%02x:%02x.%u: failed to bind PIRQ %u: %d\n", |
707 | 0 | pdev->seg, pdev->bus, PCI_SLOT(pdev->devfn), |
708 | 0 | PCI_FUNC(pdev->devfn), pirq + i, rc); |
709 | 0 | while ( bind.machine_irq-- ) |
710 | 0 | pt_irq_destroy_bind(pdev->domain, &bind); |
711 | 0 | spin_lock(&pdev->domain->event_lock); |
712 | 0 | unmap_domain_pirq(pdev->domain, pirq); |
713 | 0 | spin_unlock(&pdev->domain->event_lock); |
714 | 0 | pcidevs_unlock(); |
715 | 0 | return rc; |
716 | 0 | } |
717 | 42 | pcidevs_unlock(); |
718 | 42 | } |
719 | 42 | |
720 | 42 | return pirq; |
721 | 42 | } |
722 | | |
723 | | int vpci_msi_arch_enable(struct vpci_msi *msi, const struct pci_dev *pdev, |
724 | | unsigned int vectors) |
725 | 6 | { |
726 | 6 | int rc; |
727 | 6 | |
728 | 6 | ASSERT(msi->arch.pirq == INVALID_PIRQ); |
729 | 6 | rc = vpci_msi_enable(pdev, msi->data, msi->address, vectors, 0); |
730 | 6 | if ( rc >= 0 ) |
731 | 6 | { |
732 | 6 | msi->arch.pirq = rc; |
733 | 6 | rc = 0; |
734 | 6 | } |
735 | 6 | |
736 | 6 | return rc; |
737 | 6 | } |
738 | | |
739 | | static void vpci_msi_disable(const struct pci_dev *pdev, int pirq, |
740 | | unsigned int nr) |
741 | 0 | { |
742 | 0 | unsigned int i; |
743 | 0 |
|
744 | 0 | ASSERT(pirq != INVALID_PIRQ); |
745 | 0 |
|
746 | 0 | pcidevs_lock(); |
747 | 0 | for ( i = 0; i < nr; i++ ) |
748 | 0 | { |
749 | 0 | struct xen_domctl_bind_pt_irq bind = { |
750 | 0 | .machine_irq = pirq + i, |
751 | 0 | .irq_type = PT_IRQ_TYPE_MSI, |
752 | 0 | }; |
753 | 0 | int rc; |
754 | 0 |
|
755 | 0 | rc = pt_irq_destroy_bind(pdev->domain, &bind); |
756 | 0 | ASSERT(!rc); |
757 | 0 | } |
758 | 0 |
|
759 | 0 | spin_lock(&pdev->domain->event_lock); |
760 | 0 | unmap_domain_pirq(pdev->domain, pirq); |
761 | 0 | spin_unlock(&pdev->domain->event_lock); |
762 | 0 | pcidevs_unlock(); |
763 | 0 | } |
764 | | |
765 | | void vpci_msi_arch_disable(struct vpci_msi *msi, const struct pci_dev *pdev) |
766 | 0 | { |
767 | 0 | vpci_msi_disable(pdev, msi->arch.pirq, msi->vectors); |
768 | 0 | msi->arch.pirq = INVALID_PIRQ; |
769 | 0 | } |
770 | | |
771 | | void vpci_msi_arch_init(struct vpci_msi *msi) |
772 | 21 | { |
773 | 21 | msi->arch.pirq = INVALID_PIRQ; |
774 | 21 | } |
775 | | |
776 | | void vpci_msi_arch_print(const struct vpci_msi *msi) |
777 | 0 | { |
778 | 0 | printk("vec=%#02x%7s%6s%3sassert%5s%7s dest_id=%lu pirq: %d\n", |
779 | 0 | MASK_EXTR(msi->data, MSI_DATA_VECTOR_MASK), |
780 | 0 | msi->data & MSI_DATA_DELIVERY_LOWPRI ? "lowest" : "fixed", |
781 | 0 | msi->data & MSI_DATA_TRIGGER_LEVEL ? "level" : "edge", |
782 | 0 | msi->data & MSI_DATA_LEVEL_ASSERT ? "" : "de", |
783 | 0 | msi->address & MSI_ADDR_DESTMODE_LOGIC ? "log" : "phys", |
784 | 0 | msi->address & MSI_ADDR_REDIRECTION_LOWPRI ? "lowest" : "fixed", |
785 | 0 | MASK_EXTR(msi->address, MSI_ADDR_DEST_ID_MASK), |
786 | 0 | msi->arch.pirq); |
787 | 0 | } |
788 | | |
789 | | void vpci_msix_arch_mask_entry(struct vpci_msix_entry *entry, |
790 | | const struct pci_dev *pdev, bool mask) |
791 | 36 | { |
792 | 36 | ASSERT(entry->arch.pirq != INVALID_PIRQ); |
793 | 36 | vpci_mask_pirq(pdev->domain, entry->arch.pirq, mask); |
794 | 36 | } |
795 | | |
796 | | int vpci_msix_arch_enable_entry(struct vpci_msix_entry *entry, |
797 | | const struct pci_dev *pdev, paddr_t table_base) |
798 | 36 | { |
799 | 36 | int rc; |
800 | 36 | |
801 | 36 | ASSERT(entry->arch.pirq == INVALID_PIRQ); |
802 | 36 | rc = vpci_msi_enable(pdev, entry->data, entry->addr, |
803 | 36 | VMSIX_ENTRY_NR(pdev->vpci->msix, entry), |
804 | 36 | table_base); |
805 | 36 | if ( rc >= 0 ) |
806 | 36 | { |
807 | 36 | entry->arch.pirq = rc; |
808 | 36 | rc = 0; |
809 | 36 | } |
810 | 36 | |
811 | 36 | return rc; |
812 | 36 | } |
813 | | |
814 | | int vpci_msix_arch_disable_entry(struct vpci_msix_entry *entry, |
815 | | const struct pci_dev *pdev) |
816 | 36 | { |
817 | 36 | if ( entry->arch.pirq == INVALID_PIRQ ) |
818 | 36 | return -ENOENT; |
819 | 36 | |
820 | 0 | vpci_msi_disable(pdev, entry->arch.pirq, 1); |
821 | 0 | entry->arch.pirq = INVALID_PIRQ; |
822 | 0 |
|
823 | 0 | return 0; |
824 | 36 | } |
825 | | |
826 | | void vpci_msix_arch_init_entry(struct vpci_msix_entry *entry) |
827 | 48 | { |
828 | 48 | entry->arch.pirq = INVALID_PIRQ; |
829 | 48 | } |
830 | | |
831 | | void vpci_msix_arch_print_entry(const struct vpci_msix_entry *entry) |
832 | 0 | { |
833 | 0 | printk("vec=%#02x%7s%6s%3sassert%5s%7s dest_id=%lu mask=%u pirq: %d\n", |
834 | 0 | MASK_EXTR(entry->data, MSI_DATA_VECTOR_MASK), |
835 | 0 | entry->data & MSI_DATA_DELIVERY_LOWPRI ? "lowest" : "fixed", |
836 | 0 | entry->data & MSI_DATA_TRIGGER_LEVEL ? "level" : "edge", |
837 | 0 | entry->data & MSI_DATA_LEVEL_ASSERT ? "" : "de", |
838 | 0 | entry->addr & MSI_ADDR_DESTMODE_LOGIC ? "log" : "phys", |
839 | 0 | entry->addr & MSI_ADDR_REDIRECTION_LOWPRI ? "lowest" : "fixed", |
840 | 0 | MASK_EXTR(entry->addr, MSI_ADDR_DEST_ID_MASK), |
841 | 0 | entry->masked, entry->arch.pirq); |
842 | 0 | } |