VirtualBox

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

Last change on this file since 69498 was 68423, checked in by vboxsync, 7 years ago

Devices/Bus/PCI,MSI-X: Fix static initialization of the MSI-X memory/PCI region to allow up to 2048 vectors as defined by the specification instead of only allowing up to 128 vectors previously when allocating a static 4KB page for the structures.

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