VirtualBox

source: vbox/trunk/src/VBox/Devices/Bus/MsixCommon.cpp@ 76681

Last change on this file since 76681 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.3 KB
Line 
1/* $Id: MsixCommon.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * MSI-X support routines
4 */
5
6/*
7 * Copyright (C) 2010-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19#define LOG_GROUP LOG_GROUP_DEV_PCI
20#define PDMPCIDEV_INCLUDE_PRIVATE /* Hack to get pdmpcidevint.h included at the right point. */
21#include <VBox/pci.h>
22#include <VBox/msi.h>
23#include <VBox/vmm/pdmdev.h>
24#include <VBox/log.h>
25#include <VBox/vmm/mm.h>
26
27#include <iprt/assert.h>
28
29#include "MsiCommon.h"
30#include "PciInline.h"
31
32typedef struct
33{
34 uint32_t u32MsgAddressLo;
35 uint32_t u32MsgAddressHi;
36 uint32_t u32MsgData;
37 uint32_t u32VectorControl;
38} MsixTableRecord;
39AssertCompileSize(MsixTableRecord, VBOX_MSIX_ENTRY_SIZE);
40
41
42/** @todo use accessors so that raw PCI devices work correctly with MSI-X. */
43DECLINLINE(uint16_t) msixGetMessageControl(PPDMPCIDEV pDev)
44{
45 return PCIDevGetWord(pDev, pDev->Int.s.u8MsixCapOffset + VBOX_MSIX_CAP_MESSAGE_CONTROL);
46}
47
48DECLINLINE(bool) msixIsEnabled(PPDMPCIDEV pDev)
49{
50 return (msixGetMessageControl(pDev) & VBOX_PCI_MSIX_FLAGS_ENABLE) != 0;
51}
52
53DECLINLINE(bool) msixIsMasked(PPDMPCIDEV pDev)
54{
55 return (msixGetMessageControl(pDev) & VBOX_PCI_MSIX_FLAGS_FUNCMASK) != 0;
56}
57
58#ifdef IN_RING3
59DECLINLINE(uint16_t) msixTableSize(PPDMPCIDEV pDev)
60{
61 return (msixGetMessageControl(pDev) & 0x3ff) + 1;
62}
63#endif
64
65DECLINLINE(uint8_t *) msixGetPageOffset(PPDMPCIDEV pDev, uint32_t off)
66{
67 return (uint8_t *)pDev->Int.s.CTX_SUFF(pMsixPage) + off;
68}
69
70DECLINLINE(MsixTableRecord *) msixGetVectorRecord(PPDMPCIDEV pDev, uint32_t iVector)
71{
72 return (MsixTableRecord *)msixGetPageOffset(pDev, iVector * VBOX_MSIX_ENTRY_SIZE);
73}
74
75DECLINLINE(RTGCPHYS) msixGetMsiAddress(PPDMPCIDEV pDev, uint32_t iVector)
76{
77 MsixTableRecord *pRec = msixGetVectorRecord(pDev, iVector);
78 return RT_MAKE_U64(pRec->u32MsgAddressLo & ~UINT32_C(0x3), pRec->u32MsgAddressHi);
79}
80
81DECLINLINE(uint32_t) msixGetMsiData(PPDMPCIDEV pDev, uint32_t iVector)
82{
83 return msixGetVectorRecord(pDev, iVector)->u32MsgData;
84}
85
86DECLINLINE(uint32_t) msixIsVectorMasked(PPDMPCIDEV pDev, uint32_t iVector)
87{
88 return (msixGetVectorRecord(pDev, iVector)->u32VectorControl & 0x1) != 0;
89}
90
91DECLINLINE(uint8_t *) msixPendingByte(PPDMPCIDEV pDev, uint32_t iVector)
92{
93 return msixGetPageOffset(pDev, pDev->Int.s.offMsixPba + iVector / 8);
94}
95
96DECLINLINE(void) msixSetPending(PPDMPCIDEV pDev, uint32_t iVector)
97{
98 *msixPendingByte(pDev, iVector) |= (1 << (iVector & 0x7));
99}
100
101DECLINLINE(void) msixClearPending(PPDMPCIDEV pDev, uint32_t iVector)
102{
103 *msixPendingByte(pDev, iVector) &= ~(1 << (iVector & 0x7));
104}
105
106#ifdef IN_RING3
107
108DECLINLINE(bool) msixR3IsPending(PPDMPCIDEV pDev, uint32_t iVector)
109{
110 return (*msixPendingByte(pDev, iVector) & (1 << (iVector & 0x7))) != 0;
111}
112
113static void msixR3CheckPendingVector(PPDMDEVINS pDevIns, PCPDMPCIHLP pPciHlp, PPDMPCIDEV pDev, uint32_t iVector)
114{
115 if (msixR3IsPending(pDev, iVector) && !msixIsVectorMasked(pDev, iVector))
116 MsixNotify(pDevIns, pPciHlp, pDev, iVector, 1 /* iLevel */, 0 /*uTagSrc*/);
117}
118
119
120PDMBOTHCBDECL(int) msixR3MMIORead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
121{
122 LogFlowFunc(("\n"));
123
124 uint32_t off = (uint32_t)(GCPhysAddr & 0xffff);
125 PPDMPCIDEV pPciDev = (PPDMPCIDEV)pvUser;
126
127 /// @todo qword accesses?
128 RT_NOREF(pDevIns);
129 AssertMsgReturn(cb == 4,
130 ("MSI-X must be accessed with 4-byte reads"),
131 VERR_INTERNAL_ERROR);
132 AssertMsgReturn(off + cb <= pPciDev->Int.s.cbMsixRegion,
133 ("Out of bounds access for the MSI-X region\n"),
134 VINF_IOM_MMIO_UNUSED_FF);
135
136 *(uint32_t *)pv = *(uint32_t *)msixGetPageOffset(pPciDev, off);
137 return VINF_SUCCESS;
138}
139
140PDMBOTHCBDECL(int) msixR3MMIOWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
141{
142 LogFlowFunc(("\n"));
143
144 PPDMPCIDEV pPciDev = (PPDMPCIDEV)pvUser;
145 uint32_t off = (uint32_t)(GCPhysAddr & 0xffff);
146
147 /// @todo qword accesses?
148 AssertMsgReturn(cb == 4,
149 ("MSI-X must be accessed with 4-byte reads"),
150 VERR_INTERNAL_ERROR);
151 AssertMsgReturn(off + cb <= pPciDev->Int.s.offMsixPba,
152 ("Trying to write to PBA\n"), VINF_SUCCESS);
153
154 *(uint32_t *)msixGetPageOffset(pPciDev, off) = *(uint32_t *)pv;
155
156 msixR3CheckPendingVector(pDevIns, (PCPDMPCIHLP)pPciDev->Int.s.pPciBusPtrR3, pPciDev, off / VBOX_MSIX_ENTRY_SIZE);
157 return VINF_SUCCESS;
158}
159
160/**
161 * @callback_method_impl{FNPCIIOREGIONMAP}
162 */
163static DECLCALLBACK(int) msixR3Map(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
164 RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
165{
166 Assert(enmType == PCI_ADDRESS_SPACE_MEM);
167 NOREF(iRegion); NOREF(enmType);
168
169 int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, pPciDev,
170 IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
171 msixR3MMIOWrite, msixR3MMIORead, "MSI-X tables");
172
173 if (RT_FAILURE(rc))
174 return rc;
175
176 return VINF_SUCCESS;
177}
178
179/**
180 * Initalizes MSI-X support for the given PCI device.
181 */
182int MsixR3Init(PCPDMPCIHLP pPciHlp, PPDMPCIDEV pDev, PPDMMSIREG pMsiReg)
183{
184 if (pMsiReg->cMsixVectors == 0)
185 return VINF_SUCCESS;
186
187 /* We cannot init MSI-X on raw devices yet. */
188 Assert(!pciDevIsPassthrough(pDev));
189
190 uint16_t cVectors = pMsiReg->cMsixVectors;
191 uint8_t iCapOffset = pMsiReg->iMsixCapOffset;
192 uint8_t iNextOffset = pMsiReg->iMsixNextOffset;
193 uint8_t iBar = pMsiReg->iMsixBar;
194
195 AssertMsgReturn(cVectors <= VBOX_MSIX_MAX_ENTRIES,
196 ("Too many MSI-X vectors: %d\n", cVectors),
197 VERR_TOO_MUCH_DATA);
198 AssertMsgReturn(iBar <= 5,
199 ("Using wrong BAR for MSI-X: %d\n", iBar),
200 VERR_INVALID_PARAMETER);
201
202 Assert(iCapOffset != 0 && iCapOffset < 0xff && iNextOffset < 0xff);
203
204 int rc = VINF_SUCCESS;
205 uint16_t cbPba = cVectors / 8;
206 if (cVectors % 8)
207 cbPba++;
208 uint16_t cbMsixRegion = RT_ALIGN_T(cVectors * sizeof(MsixTableRecord) + cbPba, _4K, uint16_t);
209
210 /* If device is passthrough, BAR is registered using common mechanism. */
211 if (!pciDevIsPassthrough(pDev))
212 {
213 rc = PDMDevHlpPCIIORegionRegister(pDev->Int.s.CTX_SUFF(pDevIns), iBar, cbMsixRegion, PCI_ADDRESS_SPACE_MEM, msixR3Map);
214 if (RT_FAILURE (rc))
215 return rc;
216 }
217
218 uint16_t offTable = 0;
219 uint16_t offPBA = cVectors * sizeof(MsixTableRecord);
220
221 pDev->Int.s.u8MsixCapOffset = iCapOffset;
222 pDev->Int.s.u8MsixCapSize = VBOX_MSIX_CAP_SIZE;
223 pDev->Int.s.cbMsixRegion = cbMsixRegion;
224 pDev->Int.s.offMsixPba = offPBA;
225 PVM pVM = PDMDevHlpGetVM(pDev->Int.s.CTX_SUFF(pDevIns));
226
227 pDev->Int.s.pMsixPageR3 = NULL;
228
229 rc = MMHyperAlloc(pVM, cbMsixRegion, 1, MM_TAG_PDM_DEVICE_USER, (void **)&pDev->Int.s.pMsixPageR3);
230 if (RT_FAILURE(rc) || (pDev->Int.s.pMsixPageR3 == NULL))
231 return VERR_NO_VM_MEMORY;
232 RT_BZERO(pDev->Int.s.pMsixPageR3, cbMsixRegion);
233 pDev->Int.s.pMsixPageR0 = MMHyperR3ToR0(pVM, pDev->Int.s.pMsixPageR3);
234 pDev->Int.s.pMsixPageRC = MMHyperR3ToRC(pVM, pDev->Int.s.pMsixPageR3);
235
236 /* R3 PCI helper */
237 pDev->Int.s.pPciBusPtrR3 = pPciHlp;
238
239 PCIDevSetByte(pDev, iCapOffset + 0, VBOX_PCI_CAP_ID_MSIX);
240 PCIDevSetByte(pDev, iCapOffset + 1, iNextOffset); /* next */
241 PCIDevSetWord(pDev, iCapOffset + VBOX_MSIX_CAP_MESSAGE_CONTROL, cVectors - 1);
242
243 PCIDevSetDWord(pDev, iCapOffset + VBOX_MSIX_TABLE_BIROFFSET, offTable | iBar);
244 PCIDevSetDWord(pDev, iCapOffset + VBOX_MSIX_PBA_BIROFFSET, offPBA | iBar);
245
246 pciDevSetMsixCapable(pDev);
247
248 return VINF_SUCCESS;
249}
250
251#endif /* IN_RING3 */
252
253/**
254 * Checks if MSI-X is enabled for the tiven PCI device.
255 *
256 * (Must use MSIXNotify() for notifications when true.)
257 */
258bool MsixIsEnabled(PPDMPCIDEV pDev)
259{
260 return pciDevIsMsixCapable(pDev) && msixIsEnabled(pDev);
261}
262
263/**
264 * Device notification (aka interrupt).
265 */
266void MsixNotify(PPDMDEVINS pDevIns, PCPDMPCIHLP pPciHlp, PPDMPCIDEV pDev, int iVector, int iLevel, uint32_t uTagSrc)
267{
268 AssertMsg(msixIsEnabled(pDev), ("Must be enabled to use that"));
269
270 Assert(pPciHlp->pfnIoApicSendMsi != NULL);
271
272 /* We only trigger MSI-X on level up */
273 if ((iLevel & PDM_IRQ_LEVEL_HIGH) == 0)
274 {
275 return;
276 }
277
278 // if this vector is somehow disabled
279 if (msixIsMasked(pDev) || msixIsVectorMasked(pDev, iVector))
280 {
281 // mark pending bit
282 msixSetPending(pDev, iVector);
283 return;
284 }
285
286 // clear pending bit
287 msixClearPending(pDev, iVector);
288
289 RTGCPHYS GCAddr = msixGetMsiAddress(pDev, iVector);
290 uint32_t u32Value = msixGetMsiData(pDev, iVector);
291
292 pPciHlp->pfnIoApicSendMsi(pDevIns, GCAddr, u32Value, uTagSrc);
293}
294
295#ifdef IN_RING3
296
297DECLINLINE(bool) msixR3BitJustCleared(uint32_t uOldValue, uint32_t uNewValue, uint32_t uMask)
298{
299 return !!(uOldValue & uMask) && !(uNewValue & uMask);
300}
301
302
303static void msixR3CheckPendingVectors(PPDMDEVINS pDevIns, PCPDMPCIHLP pPciHlp, PPDMPCIDEV pDev)
304{
305 for (uint32_t i = 0; i < msixTableSize(pDev); i++)
306 msixR3CheckPendingVector(pDevIns, pPciHlp, pDev, i);
307}
308
309/**
310 * PCI config space accessors for MSI-X.
311 */
312void MsixR3PciConfigWrite(PPDMDEVINS pDevIns, PCPDMPCIHLP pPciHlp, PPDMPCIDEV pDev, uint32_t u32Address, uint32_t val, unsigned len)
313{
314 int32_t iOff = u32Address - pDev->Int.s.u8MsixCapOffset;
315 Assert(iOff >= 0 && (pciDevIsMsixCapable(pDev) && iOff < pDev->Int.s.u8MsixCapSize));
316
317 Log2(("MsixR3PciConfigWrite: %d <- %x (%d)\n", iOff, val, len));
318
319 uint32_t uAddr = u32Address;
320 uint8_t u8NewVal;
321 bool fJustEnabled = false;
322
323 for (uint32_t i = 0; i < len; i++)
324 {
325 uint32_t reg = i + iOff;
326 uint8_t u8Val = (uint8_t)val;
327 switch (reg)
328 {
329 case 0: /* Capability ID, ro */
330 case 1: /* Next pointer, ro */
331 break;
332 case VBOX_MSIX_CAP_MESSAGE_CONTROL:
333 /* don't change read-only bits: 0-7 */
334 break;
335 case VBOX_MSIX_CAP_MESSAGE_CONTROL + 1:
336 {
337 /* don't change read-only bits 8-13 */
338 u8NewVal = (u8Val & UINT8_C(~0x3f)) | (pDev->abConfig[uAddr] & UINT8_C(0x3f));
339 /* If just enabled globally - check pending vectors */
340 fJustEnabled |= msixR3BitJustCleared(pDev->abConfig[uAddr], u8NewVal, VBOX_PCI_MSIX_FLAGS_ENABLE >> 8);
341 fJustEnabled |= msixR3BitJustCleared(pDev->abConfig[uAddr], u8NewVal, VBOX_PCI_MSIX_FLAGS_FUNCMASK >> 8);
342 pDev->abConfig[uAddr] = u8NewVal;
343 break;
344 }
345 default:
346 /* other fields read-only too */
347 break;
348 }
349 uAddr++;
350 val >>= 8;
351 }
352
353 if (fJustEnabled)
354 msixR3CheckPendingVectors(pDevIns, pPciHlp, pDev);
355}
356
357#endif /* IN_RING3 */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette