VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/PDMDevice.cpp@ 58122

Last change on this file since 58122 was 58122, checked in by vboxsync, 9 years ago

VMM: Made @param pVM more uniform and to the point.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Revision
File size: 40.1 KB
Line 
1/* $Id: PDMDevice.cpp 58122 2015-10-08 17:11:58Z vboxsync $ */
2/** @file
3 * PDM - Pluggable Device and Driver Manager, Device parts.
4 */
5
6/*
7 * Copyright (C) 2006-2015 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/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_PDM_DEVICE
23#include "PDMInternal.h"
24#include <VBox/vmm/pdm.h>
25#include <VBox/vmm/mm.h>
26#include <VBox/vmm/pgm.h>
27#include <VBox/vmm/iom.h>
28#include <VBox/vmm/hm.h>
29#include <VBox/vmm/cfgm.h>
30#ifdef VBOX_WITH_REM
31# include <VBox/vmm/rem.h>
32#endif
33#include <VBox/vmm/dbgf.h>
34#include <VBox/vmm/vm.h>
35#include <VBox/vmm/uvm.h>
36#include <VBox/vmm/vmm.h>
37
38#include <VBox/version.h>
39#include <VBox/log.h>
40#include <VBox/err.h>
41#include <iprt/alloc.h>
42#include <iprt/alloca.h>
43#include <iprt/asm.h>
44#include <iprt/assert.h>
45#include <iprt/path.h>
46#include <iprt/semaphore.h>
47#include <iprt/string.h>
48#include <iprt/thread.h>
49
50
51/*********************************************************************************************************************************
52* Structures and Typedefs *
53*********************************************************************************************************************************/
54/**
55 * Internal callback structure pointer.
56 * The main purpose is to define the extra data we associate
57 * with PDMDEVREGCB so we can find the VM instance and so on.
58 */
59typedef struct PDMDEVREGCBINT
60{
61 /** The callback structure. */
62 PDMDEVREGCB Core;
63 /** A bit of padding. */
64 uint32_t u32[4];
65 /** VM Handle. */
66 PVM pVM;
67 /** Pointer to the configuration node the registrations should be
68 * associated with. Can be NULL. */
69 PCFGMNODE pCfgNode;
70} PDMDEVREGCBINT;
71/** Pointer to a PDMDEVREGCBINT structure. */
72typedef PDMDEVREGCBINT *PPDMDEVREGCBINT;
73/** Pointer to a const PDMDEVREGCBINT structure. */
74typedef const PDMDEVREGCBINT *PCPDMDEVREGCBINT;
75
76
77/*********************************************************************************************************************************
78* Internal Functions *
79*********************************************************************************************************************************/
80static DECLCALLBACK(int) pdmR3DevReg_Register(PPDMDEVREGCB pCallbacks, PCPDMDEVREG pReg);
81static int pdmR3DevLoadModules(PVM pVM);
82static int pdmR3DevLoad(PVM pVM, PPDMDEVREGCBINT pRegCB, const char *pszFilename, const char *pszName);
83
84
85
86
87/**
88 * This function will initialize the devices for this VM instance.
89 *
90 *
91 * First of all this mean loading the builtin device and letting them
92 * register themselves. Beyond that any additional device modules are
93 * loaded and called for registration.
94 *
95 * Then the device configuration is enumerated, the instantiation order
96 * is determined, and finally they are instantiated.
97 *
98 * After all devices have been successfully instantiated the primary
99 * PCI Bus device is called to emulate the PCI BIOS, i.e. making the
100 * resource assignments. If there is no PCI device, this step is of course
101 * skipped.
102 *
103 * Finally the init completion routines of the instantiated devices
104 * are called.
105 *
106 * @returns VBox status code.
107 * @param pVM The cross context VM structure.
108 */
109int pdmR3DevInit(PVM pVM)
110{
111 LogFlow(("pdmR3DevInit:\n"));
112
113 AssertRelease(!(RT_OFFSETOF(PDMDEVINS, achInstanceData) & 15));
114 AssertRelease(sizeof(pVM->pdm.s.pDevInstances->Internal.s) <= sizeof(pVM->pdm.s.pDevInstances->Internal.padding));
115
116 /*
117 * Load device modules.
118 */
119 int rc = pdmR3DevLoadModules(pVM);
120 if (RT_FAILURE(rc))
121 return rc;
122
123#ifdef VBOX_WITH_USB
124 /* ditto for USB Devices. */
125 rc = pdmR3UsbLoadModules(pVM);
126 if (RT_FAILURE(rc))
127 return rc;
128#endif
129
130 /*
131 * Get the RC & R0 devhlps and create the devhlp R3 task queue.
132 */
133 PCPDMDEVHLPRC pHlpRC = NIL_RTRCPTR;
134 if (!HMIsEnabled(pVM))
135 {
136 rc = PDMR3LdrGetSymbolRC(pVM, NULL, "g_pdmRCDevHlp", &pHlpRC);
137 AssertReleaseRCReturn(rc, rc);
138 }
139
140 PCPDMDEVHLPR0 pHlpR0;
141 rc = PDMR3LdrGetSymbolR0(pVM, NULL, "g_pdmR0DevHlp", &pHlpR0);
142 AssertReleaseRCReturn(rc, rc);
143
144 rc = PDMR3QueueCreateInternal(pVM, sizeof(PDMDEVHLPTASK), 8, 0, pdmR3DevHlpQueueConsumer, true, "DevHlp",
145 &pVM->pdm.s.pDevHlpQueueR3);
146 AssertRCReturn(rc, rc);
147 pVM->pdm.s.pDevHlpQueueR0 = PDMQueueR0Ptr(pVM->pdm.s.pDevHlpQueueR3);
148 pVM->pdm.s.pDevHlpQueueRC = PDMQueueRCPtr(pVM->pdm.s.pDevHlpQueueR3);
149
150
151 /*
152 *
153 * Enumerate the device instance configurations
154 * and come up with a instantiation order.
155 *
156 */
157 /* Switch to /Devices, which contains the device instantiations. */
158 PCFGMNODE pDevicesNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "Devices");
159
160 /*
161 * Count the device instances.
162 */
163 PCFGMNODE pCur;
164 PCFGMNODE pInstanceNode;
165 unsigned cDevs = 0;
166 for (pCur = CFGMR3GetFirstChild(pDevicesNode); pCur; pCur = CFGMR3GetNextChild(pCur))
167 for (pInstanceNode = CFGMR3GetFirstChild(pCur); pInstanceNode; pInstanceNode = CFGMR3GetNextChild(pInstanceNode))
168 cDevs++;
169 if (!cDevs)
170 {
171 Log(("PDM: No devices were configured!\n"));
172 return VINF_SUCCESS;
173 }
174 Log2(("PDM: cDevs=%u\n", cDevs));
175
176 /*
177 * Collect info on each device instance.
178 */
179 struct DEVORDER
180 {
181 /** Configuration node. */
182 PCFGMNODE pNode;
183 /** Pointer to device. */
184 PPDMDEV pDev;
185 /** Init order. */
186 uint32_t u32Order;
187 /** VBox instance number. */
188 uint32_t iInstance;
189 } *paDevs = (struct DEVORDER *)alloca(sizeof(paDevs[0]) * (cDevs + 1)); /* (One extra for swapping) */
190 Assert(paDevs);
191 unsigned i = 0;
192 for (pCur = CFGMR3GetFirstChild(pDevicesNode); pCur; pCur = CFGMR3GetNextChild(pCur))
193 {
194 /* Get the device name. */
195 char szName[sizeof(paDevs[0].pDev->pReg->szName)];
196 rc = CFGMR3GetName(pCur, szName, sizeof(szName));
197 AssertMsgRCReturn(rc, ("Configuration error: device name is too long (or something)! rc=%Rrc\n", rc), rc);
198
199 /* Find the device. */
200 PPDMDEV pDev = pdmR3DevLookup(pVM, szName);
201 AssertLogRelMsgReturn(pDev, ("Configuration error: device '%s' not found!\n", szName), VERR_PDM_DEVICE_NOT_FOUND);
202
203 /* Configured priority or use default based on device class? */
204 uint32_t u32Order;
205 rc = CFGMR3QueryU32(pCur, "Priority", &u32Order);
206 if (rc == VERR_CFGM_VALUE_NOT_FOUND)
207 {
208 uint32_t u32 = pDev->pReg->fClass;
209 for (u32Order = 1; !(u32 & u32Order); u32Order <<= 1)
210 /* nop */;
211 }
212 else
213 AssertMsgRCReturn(rc, ("Configuration error: reading \"Priority\" for the '%s' device failed rc=%Rrc!\n", szName, rc), rc);
214
215 /* Enumerate the device instances. */
216 uint32_t const iStart = i;
217 for (pInstanceNode = CFGMR3GetFirstChild(pCur); pInstanceNode; pInstanceNode = CFGMR3GetNextChild(pInstanceNode))
218 {
219 paDevs[i].pNode = pInstanceNode;
220 paDevs[i].pDev = pDev;
221 paDevs[i].u32Order = u32Order;
222
223 /* Get the instance number. */
224 char szInstance[32];
225 rc = CFGMR3GetName(pInstanceNode, szInstance, sizeof(szInstance));
226 AssertMsgRCReturn(rc, ("Configuration error: instance name is too long (or something)! rc=%Rrc\n", rc), rc);
227 char *pszNext = NULL;
228 rc = RTStrToUInt32Ex(szInstance, &pszNext, 0, &paDevs[i].iInstance);
229 AssertMsgRCReturn(rc, ("Configuration error: RTStrToInt32Ex failed on the instance name '%s'! rc=%Rrc\n", szInstance, rc), rc);
230 AssertMsgReturn(!*pszNext, ("Configuration error: the instance name '%s' isn't all digits. (%s)\n", szInstance, pszNext), VERR_INVALID_PARAMETER);
231
232 /* next instance */
233 i++;
234 }
235
236 /* check the number of instances */
237 if (i - iStart > pDev->pReg->cMaxInstances)
238 AssertLogRelMsgFailedReturn(("Configuration error: Too many instances of %s was configured: %u, max %u\n",
239 szName, i - iStart, pDev->pReg->cMaxInstances),
240 VERR_PDM_TOO_MANY_DEVICE_INSTANCES);
241 } /* devices */
242 Assert(i == cDevs);
243
244 /*
245 * Sort the device array ascending on u32Order. (bubble)
246 */
247 unsigned c = cDevs - 1;
248 while (c)
249 {
250 unsigned j = 0;
251 for (i = 0; i < c; i++)
252 if (paDevs[i].u32Order > paDevs[i + 1].u32Order)
253 {
254 paDevs[cDevs] = paDevs[i + 1];
255 paDevs[i + 1] = paDevs[i];
256 paDevs[i] = paDevs[cDevs];
257 j = i;
258 }
259 c = j;
260 }
261
262
263 /*
264 *
265 * Instantiate the devices.
266 *
267 */
268 for (i = 0; i < cDevs; i++)
269 {
270 /*
271 * Gather a bit of config.
272 */
273 /* trusted */
274 bool fTrusted;
275 rc = CFGMR3QueryBool(paDevs[i].pNode, "Trusted", &fTrusted);
276 if (rc == VERR_CFGM_VALUE_NOT_FOUND)
277 fTrusted = false;
278 else if (RT_FAILURE(rc))
279 {
280 AssertMsgFailed(("configuration error: failed to query boolean \"Trusted\", rc=%Rrc\n", rc));
281 return rc;
282 }
283 /* config node */
284 PCFGMNODE pConfigNode = CFGMR3GetChild(paDevs[i].pNode, "Config");
285 if (!pConfigNode)
286 {
287 rc = CFGMR3InsertNode(paDevs[i].pNode, "Config", &pConfigNode);
288 if (RT_FAILURE(rc))
289 {
290 AssertMsgFailed(("Failed to create Config node! rc=%Rrc\n", rc));
291 return rc;
292 }
293 }
294 CFGMR3SetRestrictedRoot(pConfigNode);
295
296 /*
297 * Allocate the device instance and critical section.
298 */
299 AssertReturn(paDevs[i].pDev->cInstances < paDevs[i].pDev->pReg->cMaxInstances, VERR_PDM_TOO_MANY_DEVICE_INSTANCES);
300 size_t cb = RT_OFFSETOF(PDMDEVINS, achInstanceData[paDevs[i].pDev->pReg->cbInstance]);
301 cb = RT_ALIGN_Z(cb, 16);
302 PPDMDEVINS pDevIns;
303 if (paDevs[i].pDev->pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0))
304 rc = MMR3HyperAllocOnceNoRel(pVM, cb, 0, MM_TAG_PDM_DEVICE, (void **)&pDevIns);
305 else
306 rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_DEVICE, cb, (void **)&pDevIns);
307 AssertLogRelMsgRCReturn(rc,
308 ("Failed to allocate %d bytes of instance data for device '%s'. rc=%Rrc\n",
309 cb, paDevs[i].pDev->pReg->szName, rc),
310 rc);
311 PPDMCRITSECT pCritSect;
312 if (paDevs[i].pDev->pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0))
313 rc = MMHyperAlloc(pVM, sizeof(*pCritSect), 0, MM_TAG_PDM_DEVICE, (void **)&pCritSect);
314 else
315 rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_DEVICE, sizeof(*pCritSect), (void **)&pCritSect);
316 AssertLogRelMsgRCReturn(rc, ("Failed to allocate a critical section for the device (%Rrc)\n", rc), rc);
317
318 /*
319 * Initialize it.
320 */
321 pDevIns->u32Version = PDM_DEVINS_VERSION;
322 pDevIns->iInstance = paDevs[i].iInstance;
323 //pDevIns->Internal.s.pNextR3 = NULL;
324 //pDevIns->Internal.s.pPerDeviceNextR3 = NULL;
325 pDevIns->Internal.s.pDevR3 = paDevs[i].pDev;
326 pDevIns->Internal.s.pVMR3 = pVM;
327 pDevIns->Internal.s.pVMR0 = pVM->pVMR0;
328 pDevIns->Internal.s.pVMRC = pVM->pVMRC;
329 //pDevIns->Internal.s.pLunsR3 = NULL;
330 pDevIns->Internal.s.pCfgHandle = paDevs[i].pNode;
331 //pDevIns->Internal.s.pPciDeviceR3 = NULL;
332 //pDevIns->Internal.s.pPciBusR3 = NULL;
333 //pDevIns->Internal.s.pPciDeviceR0 = 0;
334 //pDevIns->Internal.s.pPciBusR0 = 0;
335 //pDevIns->Internal.s.pPciDeviceRC = 0;
336 //pDevIns->Internal.s.pPciBusRC = 0;
337 pDevIns->Internal.s.fIntFlags = PDMDEVINSINT_FLAGS_SUSPENDED;
338 //pDevIns->Internal.s.uLastIrqTag = 0;
339 pDevIns->pHlpR3 = fTrusted ? &g_pdmR3DevHlpTrusted : &g_pdmR3DevHlpUnTrusted;
340 pDevIns->pHlpRC = pHlpRC;
341 pDevIns->pHlpR0 = pHlpR0;
342 pDevIns->pReg = paDevs[i].pDev->pReg;
343 pDevIns->pCfg = pConfigNode;
344 //pDevIns->IBase.pfnQueryInterface = NULL;
345 //pDevIns->fTracing = 0;
346 pDevIns->idTracing = ++pVM->pdm.s.idTracingDev;
347 pDevIns->pvInstanceDataR3 = &pDevIns->achInstanceData[0];
348 pDevIns->pvInstanceDataRC = pDevIns->pReg->fFlags & PDM_DEVREG_FLAGS_RC
349 ? MMHyperR3ToRC(pVM, pDevIns->pvInstanceDataR3) : NIL_RTRCPTR;
350 pDevIns->pvInstanceDataR0 = pDevIns->pReg->fFlags & PDM_DEVREG_FLAGS_R0
351 ? MMHyperR3ToR0(pVM, pDevIns->pvInstanceDataR3) : NIL_RTR0PTR;
352
353 pDevIns->pCritSectRoR3 = pCritSect;
354 pDevIns->pCritSectRoRC = pDevIns->pReg->fFlags & PDM_DEVREG_FLAGS_RC
355 ? MMHyperR3ToRC(pVM, pCritSect) : NIL_RTRCPTR;
356 pDevIns->pCritSectRoR0 = pDevIns->pReg->fFlags & PDM_DEVREG_FLAGS_R0
357 ? MMHyperR3ToR0(pVM, pCritSect) : NIL_RTR0PTR;
358
359 rc = pdmR3CritSectInitDeviceAuto(pVM, pDevIns, pCritSect, RT_SRC_POS,
360 "%s#%uAuto", pDevIns->pReg->szName, pDevIns->iInstance);
361 AssertLogRelRCReturn(rc, rc);
362
363 /*
364 * Link it into all the lists.
365 */
366 /* The global instance FIFO. */
367 PPDMDEVINS pPrev1 = pVM->pdm.s.pDevInstances;
368 if (!pPrev1)
369 pVM->pdm.s.pDevInstances = pDevIns;
370 else
371 {
372 while (pPrev1->Internal.s.pNextR3)
373 pPrev1 = pPrev1->Internal.s.pNextR3;
374 pPrev1->Internal.s.pNextR3 = pDevIns;
375 }
376
377 /* The per device instance FIFO. */
378 PPDMDEVINS pPrev2 = paDevs[i].pDev->pInstances;
379 if (!pPrev2)
380 paDevs[i].pDev->pInstances = pDevIns;
381 else
382 {
383 while (pPrev2->Internal.s.pPerDeviceNextR3)
384 pPrev2 = pPrev2->Internal.s.pPerDeviceNextR3;
385 pPrev2->Internal.s.pPerDeviceNextR3 = pDevIns;
386 }
387
388 /*
389 * Call the constructor.
390 */
391 paDevs[i].pDev->cInstances++;
392 Log(("PDM: Constructing device '%s' instance %d...\n", pDevIns->pReg->szName, pDevIns->iInstance));
393 rc = pDevIns->pReg->pfnConstruct(pDevIns, pDevIns->iInstance, pDevIns->pCfg);
394 if (RT_FAILURE(rc))
395 {
396 LogRel(("PDM: Failed to construct '%s'/%d! %Rra\n", pDevIns->pReg->szName, pDevIns->iInstance, rc));
397 paDevs[i].pDev->cInstances--;
398 /* Because we're damn lazy, the destructor will be called even if
399 the constructor fails. So, no unlinking. */
400 return rc == VERR_VERSION_MISMATCH ? VERR_PDM_DEVICE_VERSION_MISMATCH : rc;
401 }
402 } /* for device instances */
403
404#ifdef VBOX_WITH_USB
405 /* ditto for USB Devices. */
406 rc = pdmR3UsbInstantiateDevices(pVM);
407 if (RT_FAILURE(rc))
408 return rc;
409#endif
410
411
412 /*
413 *
414 * PCI BIOS Fake and Init Complete.
415 *
416 */
417 if (pVM->pdm.s.aPciBuses[0].pDevInsR3)
418 {
419 pdmLock(pVM);
420 rc = pVM->pdm.s.aPciBuses[0].pfnFakePCIBIOSR3(pVM->pdm.s.aPciBuses[0].pDevInsR3);
421 pdmUnlock(pVM);
422 if (RT_FAILURE(rc))
423 {
424 AssertMsgFailed(("PCI BIOS fake failed rc=%Rrc\n", rc));
425 return rc;
426 }
427 }
428
429 for (PPDMDEVINS pDevIns = pVM->pdm.s.pDevInstances; pDevIns; pDevIns = pDevIns->Internal.s.pNextR3)
430 {
431 if (pDevIns->pReg->pfnInitComplete)
432 {
433 PDMCritSectEnter(pDevIns->pCritSectRoR3, VERR_IGNORED);
434 rc = pDevIns->pReg->pfnInitComplete(pDevIns);
435 PDMCritSectLeave(pDevIns->pCritSectRoR3);
436 if (RT_FAILURE(rc))
437 {
438 AssertMsgFailed(("InitComplete on device '%s'/%d failed with rc=%Rrc\n",
439 pDevIns->pReg->szName, pDevIns->iInstance, rc));
440 return rc;
441 }
442 }
443 }
444
445#ifdef VBOX_WITH_USB
446 /* ditto for USB Devices. */
447 rc = pdmR3UsbVMInitComplete(pVM);
448 if (RT_FAILURE(rc))
449 return rc;
450#endif
451
452 LogFlow(("pdmR3DevInit: returns %Rrc\n", VINF_SUCCESS));
453 return VINF_SUCCESS;
454}
455
456
457/**
458 * Lookups a device structure by name.
459 * @internal
460 */
461PPDMDEV pdmR3DevLookup(PVM pVM, const char *pszName)
462{
463 size_t cchName = strlen(pszName);
464 for (PPDMDEV pDev = pVM->pdm.s.pDevs; pDev; pDev = pDev->pNext)
465 if ( pDev->cchName == cchName
466 && !strcmp(pDev->pReg->szName, pszName))
467 return pDev;
468 return NULL;
469}
470
471
472/**
473 * Loads the device modules.
474 *
475 * @returns VBox status code.
476 * @param pVM The cross context VM structure.
477 */
478static int pdmR3DevLoadModules(PVM pVM)
479{
480 /*
481 * Initialize the callback structure.
482 */
483 PDMDEVREGCBINT RegCB;
484 RegCB.Core.u32Version = PDM_DEVREG_CB_VERSION;
485 RegCB.Core.pfnRegister = pdmR3DevReg_Register;
486 RegCB.pVM = pVM;
487 RegCB.pCfgNode = NULL;
488
489 /*
490 * Load the builtin module
491 */
492 PCFGMNODE pDevicesNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "PDM/Devices");
493 bool fLoadBuiltin;
494 int rc = CFGMR3QueryBool(pDevicesNode, "LoadBuiltin", &fLoadBuiltin);
495 if (rc == VERR_CFGM_VALUE_NOT_FOUND || rc == VERR_CFGM_NO_PARENT)
496 fLoadBuiltin = true;
497 else if (RT_FAILURE(rc))
498 {
499 AssertMsgFailed(("Configuration error: Querying boolean \"LoadBuiltin\" failed with %Rrc\n", rc));
500 return rc;
501 }
502 if (fLoadBuiltin)
503 {
504 /* make filename */
505 char *pszFilename = pdmR3FileR3("VBoxDD", true /*fShared*/);
506 if (!pszFilename)
507 return VERR_NO_TMP_MEMORY;
508 rc = pdmR3DevLoad(pVM, &RegCB, pszFilename, "VBoxDD");
509 RTMemTmpFree(pszFilename);
510 if (RT_FAILURE(rc))
511 return rc;
512
513 /* make filename */
514 pszFilename = pdmR3FileR3("VBoxDD2", true /*fShared*/);
515 if (!pszFilename)
516 return VERR_NO_TMP_MEMORY;
517 rc = pdmR3DevLoad(pVM, &RegCB, pszFilename, "VBoxDD2");
518 RTMemTmpFree(pszFilename);
519 if (RT_FAILURE(rc))
520 return rc;
521 }
522
523 /*
524 * Load additional device modules.
525 */
526 PCFGMNODE pCur;
527 for (pCur = CFGMR3GetFirstChild(pDevicesNode); pCur; pCur = CFGMR3GetNextChild(pCur))
528 {
529 /*
530 * Get the name and path.
531 */
532 char szName[PDMMOD_NAME_LEN];
533 rc = CFGMR3GetName(pCur, &szName[0], sizeof(szName));
534 if (rc == VERR_CFGM_NOT_ENOUGH_SPACE)
535 {
536 AssertMsgFailed(("configuration error: The module name is too long, cchName=%zu.\n", CFGMR3GetNameLen(pCur)));
537 return VERR_PDM_MODULE_NAME_TOO_LONG;
538 }
539 else if (RT_FAILURE(rc))
540 {
541 AssertMsgFailed(("CFGMR3GetName -> %Rrc.\n", rc));
542 return rc;
543 }
544
545 /* the path is optional, if no path the module name + path is used. */
546 char szFilename[RTPATH_MAX];
547 rc = CFGMR3QueryString(pCur, "Path", &szFilename[0], sizeof(szFilename));
548 if (rc == VERR_CFGM_VALUE_NOT_FOUND)
549 strcpy(szFilename, szName);
550 else if (RT_FAILURE(rc))
551 {
552 AssertMsgFailed(("configuration error: Failure to query the module path, rc=%Rrc.\n", rc));
553 return rc;
554 }
555
556 /* prepend path? */
557 if (!RTPathHavePath(szFilename))
558 {
559 char *psz = pdmR3FileR3(szFilename, false /*fShared*/);
560 if (!psz)
561 return VERR_NO_TMP_MEMORY;
562 size_t cch = strlen(psz) + 1;
563 if (cch > sizeof(szFilename))
564 {
565 RTMemTmpFree(psz);
566 AssertMsgFailed(("Filename too long! cch=%d '%s'\n", cch, psz));
567 return VERR_FILENAME_TOO_LONG;
568 }
569 memcpy(szFilename, psz, cch);
570 RTMemTmpFree(psz);
571 }
572
573 /*
574 * Load the module and register it's devices.
575 */
576 RegCB.pCfgNode = pCur;
577 rc = pdmR3DevLoad(pVM, &RegCB, szFilename, szName);
578 if (RT_FAILURE(rc))
579 return rc;
580 }
581
582 return VINF_SUCCESS;
583}
584
585
586/**
587 * Loads one device module and call the registration entry point.
588 *
589 * @returns VBox status code.
590 * @param pVM The cross context VM structure.
591 * @param pRegCB The registration callback stuff.
592 * @param pszFilename Module filename.
593 * @param pszName Module name.
594 */
595static int pdmR3DevLoad(PVM pVM, PPDMDEVREGCBINT pRegCB, const char *pszFilename, const char *pszName)
596{
597 /*
598 * Load it.
599 */
600 int rc = pdmR3LoadR3U(pVM->pUVM, pszFilename, pszName);
601 if (RT_SUCCESS(rc))
602 {
603 /*
604 * Get the registration export and call it.
605 */
606 FNPDMVBOXDEVICESREGISTER *pfnVBoxDevicesRegister;
607 rc = PDMR3LdrGetSymbolR3(pVM, pszName, "VBoxDevicesRegister", (void **)&pfnVBoxDevicesRegister);
608 if (RT_SUCCESS(rc))
609 {
610 Log(("PDM: Calling VBoxDevicesRegister (%p) of %s (%s)\n", pfnVBoxDevicesRegister, pszName, pszFilename));
611 rc = pfnVBoxDevicesRegister(&pRegCB->Core, VBOX_VERSION);
612 if (RT_SUCCESS(rc))
613 Log(("PDM: Successfully loaded device module %s (%s).\n", pszName, pszFilename));
614 else
615 AssertMsgFailed(("VBoxDevicesRegister failed with rc=%Rrc for module %s (%s)\n", rc, pszName, pszFilename));
616 }
617 else
618 {
619 AssertMsgFailed(("Failed to locate 'VBoxDevicesRegister' in %s (%s) rc=%Rrc\n", pszName, pszFilename, rc));
620 if (rc == VERR_SYMBOL_NOT_FOUND)
621 rc = VERR_PDM_NO_REGISTRATION_EXPORT;
622 }
623 }
624 else
625 AssertMsgFailed(("Failed to load %s %s!\n", pszFilename, pszName));
626 return rc;
627}
628
629
630/**
631 * @interface_method_impl{PDMDEVREGCB,pfnRegister}
632 */
633static DECLCALLBACK(int) pdmR3DevReg_Register(PPDMDEVREGCB pCallbacks, PCPDMDEVREG pReg)
634{
635 /*
636 * Validate the registration structure.
637 */
638 Assert(pReg);
639 AssertMsgReturn(pReg->u32Version == PDM_DEVREG_VERSION,
640 ("Unknown struct version %#x!\n", pReg->u32Version),
641 VERR_PDM_UNKNOWN_DEVREG_VERSION);
642
643 AssertMsgReturn( pReg->szName[0]
644 && strlen(pReg->szName) < sizeof(pReg->szName)
645 && pdmR3IsValidName(pReg->szName),
646 ("Invalid name '%.*s'\n", sizeof(pReg->szName), pReg->szName),
647 VERR_PDM_INVALID_DEVICE_REGISTRATION);
648 AssertMsgReturn( !(pReg->fFlags & PDM_DEVREG_FLAGS_RC)
649 || ( pReg->szRCMod[0]
650 && strlen(pReg->szRCMod) < sizeof(pReg->szRCMod)),
651 ("Invalid GC module name '%s' - (Device %s)\n", pReg->szRCMod, pReg->szName),
652 VERR_PDM_INVALID_DEVICE_REGISTRATION);
653 AssertMsgReturn( !(pReg->fFlags & PDM_DEVREG_FLAGS_R0)
654 || ( pReg->szR0Mod[0]
655 && strlen(pReg->szR0Mod) < sizeof(pReg->szR0Mod)),
656 ("Invalid R0 module name '%s' - (Device %s)\n", pReg->szR0Mod, pReg->szName),
657 VERR_PDM_INVALID_DEVICE_REGISTRATION);
658 AssertMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_HOST_BITS_MASK) == PDM_DEVREG_FLAGS_HOST_BITS_DEFAULT,
659 ("Invalid host bits flags! fFlags=%#x (Device %s)\n", pReg->fFlags, pReg->szName),
660 VERR_PDM_INVALID_DEVICE_HOST_BITS);
661 AssertMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_GUEST_BITS_MASK),
662 ("Invalid guest bits flags! fFlags=%#x (Device %s)\n", pReg->fFlags, pReg->szName),
663 VERR_PDM_INVALID_DEVICE_REGISTRATION);
664 AssertMsgReturn(pReg->fClass,
665 ("No class! (Device %s)\n", pReg->szName),
666 VERR_PDM_INVALID_DEVICE_REGISTRATION);
667 AssertMsgReturn(pReg->cMaxInstances > 0,
668 ("Max instances %u! (Device %s)\n", pReg->cMaxInstances, pReg->szName),
669 VERR_PDM_INVALID_DEVICE_REGISTRATION);
670 AssertMsgReturn(pReg->cbInstance <= (uint32_t)(pReg->fFlags & (PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0) ? 96 * _1K : _1M),
671 ("Instance size %d bytes! (Device %s)\n", pReg->cbInstance, pReg->szName),
672 VERR_PDM_INVALID_DEVICE_REGISTRATION);
673 AssertMsgReturn(pReg->pfnConstruct,
674 ("No constructor! (Device %s)\n", pReg->szName),
675 VERR_PDM_INVALID_DEVICE_REGISTRATION);
676 AssertLogRelMsgReturn((pReg->fFlags & PDM_DEVREG_FLAGS_GUEST_BITS_MASK) == PDM_DEVREG_FLAGS_GUEST_BITS_DEFAULT,
677 ("PDM: Rejected device '%s' because it didn't match the guest bits.\n", pReg->szName),
678 VERR_PDM_INVALID_DEVICE_GUEST_BITS);
679 AssertLogRelMsg(pReg->u32VersionEnd == PDM_DEVREG_VERSION,
680 ("u32VersionEnd=%#x, expected %#x. (szName=%s)\n",
681 pReg->u32VersionEnd, PDM_DEVREG_VERSION, pReg->szName));
682
683 /*
684 * Check for duplicate and find FIFO entry at the same time.
685 */
686 PCPDMDEVREGCBINT pRegCB = (PCPDMDEVREGCBINT)pCallbacks;
687 PPDMDEV pDevPrev = NULL;
688 PPDMDEV pDev = pRegCB->pVM->pdm.s.pDevs;
689 for (; pDev; pDevPrev = pDev, pDev = pDev->pNext)
690 AssertMsgReturn(strcmp(pDev->pReg->szName, pReg->szName),
691 ("Device '%s' already exists\n", pReg->szName),
692 VERR_PDM_DEVICE_NAME_CLASH);
693
694 /*
695 * Allocate new device structure, initialize and insert it into the list.
696 */
697 int rc;
698 pDev = (PPDMDEV)MMR3HeapAlloc(pRegCB->pVM, MM_TAG_PDM_DEVICE, sizeof(*pDev));
699 if (pDev)
700 {
701 pDev->pNext = NULL;
702 pDev->cInstances = 0;
703 pDev->pInstances = NULL;
704 pDev->pReg = pReg;
705 pDev->cchName = (uint32_t)strlen(pReg->szName);
706 rc = CFGMR3QueryStringAllocDef( pRegCB->pCfgNode, "RCSearchPath", &pDev->pszRCSearchPath, NULL);
707 if (RT_SUCCESS(rc))
708 rc = CFGMR3QueryStringAllocDef(pRegCB->pCfgNode, "R0SearchPath", &pDev->pszR0SearchPath, NULL);
709 if (RT_SUCCESS(rc))
710 {
711 if (pDevPrev)
712 pDevPrev->pNext = pDev;
713 else
714 pRegCB->pVM->pdm.s.pDevs = pDev;
715 Log(("PDM: Registered device '%s'\n", pReg->szName));
716 return VINF_SUCCESS;
717 }
718
719 MMR3HeapFree(pDev);
720 }
721 else
722 rc = VERR_NO_MEMORY;
723 return rc;
724}
725
726
727/**
728 * Locates a LUN.
729 *
730 * @returns VBox status code.
731 * @param pVM The cross context VM structure.
732 * @param pszDevice Device name.
733 * @param iInstance Device instance.
734 * @param iLun The Logical Unit to obtain the interface of.
735 * @param ppLun Where to store the pointer to the LUN if found.
736 * @thread Try only do this in EMT...
737 */
738int pdmR3DevFindLun(PVM pVM, const char *pszDevice, unsigned iInstance, unsigned iLun, PPPDMLUN ppLun)
739{
740 /*
741 * Iterate registered devices looking for the device.
742 */
743 size_t cchDevice = strlen(pszDevice);
744 for (PPDMDEV pDev = pVM->pdm.s.pDevs; pDev; pDev = pDev->pNext)
745 {
746 if ( pDev->cchName == cchDevice
747 && !memcmp(pDev->pReg->szName, pszDevice, cchDevice))
748 {
749 /*
750 * Iterate device instances.
751 */
752 for (PPDMDEVINS pDevIns = pDev->pInstances; pDevIns; pDevIns = pDevIns->Internal.s.pPerDeviceNextR3)
753 {
754 if (pDevIns->iInstance == iInstance)
755 {
756 /*
757 * Iterate luns.
758 */
759 for (PPDMLUN pLun = pDevIns->Internal.s.pLunsR3; pLun; pLun = pLun->pNext)
760 {
761 if (pLun->iLun == iLun)
762 {
763 *ppLun = pLun;
764 return VINF_SUCCESS;
765 }
766 }
767 return VERR_PDM_LUN_NOT_FOUND;
768 }
769 }
770 return VERR_PDM_DEVICE_INSTANCE_NOT_FOUND;
771 }
772 }
773 return VERR_PDM_DEVICE_NOT_FOUND;
774}
775
776
777/**
778 * Attaches a preconfigured driver to an existing device instance.
779 *
780 * This is used to change drivers and suchlike at runtime.
781 *
782 * @returns VBox status code.
783 * @param pUVM The user mode VM handle.
784 * @param pszDevice Device name.
785 * @param iInstance Device instance.
786 * @param iLun The Logical Unit to obtain the interface of.
787 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
788 * @param ppBase Where to store the base interface pointer. Optional.
789 * @thread EMT
790 */
791VMMR3DECL(int) PDMR3DeviceAttach(PUVM pUVM, const char *pszDevice, unsigned iInstance, unsigned iLun, uint32_t fFlags, PPPDMIBASE ppBase)
792{
793 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
794 PVM pVM = pUVM->pVM;
795 VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
796 VM_ASSERT_EMT(pVM);
797 LogFlow(("PDMR3DeviceAttach: pszDevice=%p:{%s} iInstance=%d iLun=%d fFlags=%#x ppBase=%p\n",
798 pszDevice, pszDevice, iInstance, iLun, fFlags, ppBase));
799
800 /*
801 * Find the LUN in question.
802 */
803 PPDMLUN pLun;
804 int rc = pdmR3DevFindLun(pVM, pszDevice, iInstance, iLun, &pLun);
805 if (RT_SUCCESS(rc))
806 {
807 /*
808 * Can we attach anything at runtime?
809 */
810 PPDMDEVINS pDevIns = pLun->pDevIns;
811 if (pDevIns->pReg->pfnAttach)
812 {
813 if (!pLun->pTop)
814 {
815 PDMCritSectEnter(pDevIns->pCritSectRoR3, VERR_IGNORED);
816 rc = pDevIns->pReg->pfnAttach(pDevIns, iLun, fFlags);
817 PDMCritSectLeave(pDevIns->pCritSectRoR3);
818 }
819 else
820 rc = VERR_PDM_DRIVER_ALREADY_ATTACHED;
821 }
822 else
823 rc = VERR_PDM_DEVICE_NO_RT_ATTACH;
824
825 if (ppBase)
826 *ppBase = pLun->pTop ? &pLun->pTop->IBase : NULL;
827 }
828 else if (ppBase)
829 *ppBase = NULL;
830
831 if (ppBase)
832 LogFlow(("PDMR3DeviceAttach: returns %Rrc *ppBase=%p\n", rc, *ppBase));
833 else
834 LogFlow(("PDMR3DeviceAttach: returns %Rrc\n", rc));
835 return rc;
836}
837
838
839/**
840 * Detaches a driver chain from an existing device instance.
841 *
842 * This is used to change drivers and suchlike at runtime.
843 *
844 * @returns VBox status code.
845 * @param pUVM The user mode VM handle.
846 * @param pszDevice Device name.
847 * @param iInstance Device instance.
848 * @param iLun The Logical Unit to obtain the interface of.
849 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
850 * @thread EMT
851 */
852VMMR3DECL(int) PDMR3DeviceDetach(PUVM pUVM, const char *pszDevice, unsigned iInstance, unsigned iLun, uint32_t fFlags)
853{
854 return PDMR3DriverDetach(pUVM, pszDevice, iInstance, iLun, NULL, 0, fFlags);
855}
856
857
858/**
859 * References the critical section associated with a device for the use by a
860 * timer or similar created by the device.
861 *
862 * @returns Pointer to the critical section.
863 * @param pVM The cross context VM structure.
864 * @param pDevIns The device instance in question.
865 *
866 * @internal
867 */
868VMMR3_INT_DECL(PPDMCRITSECT) PDMR3DevGetCritSect(PVM pVM, PPDMDEVINS pDevIns)
869{
870 VM_ASSERT_EMT(pVM);
871 VM_ASSERT_STATE(pVM, VMSTATE_CREATING);
872 AssertPtr(pDevIns);
873
874 PPDMCRITSECT pCritSect = pDevIns->pCritSectRoR3;
875 AssertPtr(pCritSect);
876 pCritSect->s.fUsedByTimerOrSimilar = true;
877
878 return pCritSect;
879}
880
881
882/**
883 * Attaches a preconfigured driver to an existing device or driver instance.
884 *
885 * This is used to change drivers and suchlike at runtime. The driver or device
886 * at the end of the chain will be told to attach to whatever is configured
887 * below it.
888 *
889 * @returns VBox status code.
890 * @param pUVM The user mode VM handle.
891 * @param pszDevice Device name.
892 * @param iInstance Device instance.
893 * @param iLun The Logical Unit to obtain the interface of.
894 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
895 * @param ppBase Where to store the base interface pointer. Optional.
896 *
897 * @thread EMT
898 */
899VMMR3DECL(int) PDMR3DriverAttach(PUVM pUVM, const char *pszDevice, unsigned iInstance, unsigned iLun, uint32_t fFlags, PPPDMIBASE ppBase)
900{
901 LogFlow(("PDMR3DriverAttach: pszDevice=%p:{%s} iInstance=%d iLun=%d fFlags=%#x ppBase=%p\n",
902 pszDevice, pszDevice, iInstance, iLun, fFlags, ppBase));
903 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
904 PVM pVM = pUVM->pVM;
905 VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
906 VM_ASSERT_EMT(pVM);
907
908 if (ppBase)
909 *ppBase = NULL;
910
911 /*
912 * Find the LUN in question.
913 */
914 PPDMLUN pLun;
915 int rc = pdmR3DevFindLun(pVM, pszDevice, iInstance, iLun, &pLun);
916 if (RT_SUCCESS(rc))
917 {
918 /*
919 * Anything attached to the LUN?
920 */
921 PPDMDRVINS pDrvIns = pLun->pTop;
922 if (!pDrvIns)
923 {
924 /* No, ask the device to attach to the new stuff. */
925 PPDMDEVINS pDevIns = pLun->pDevIns;
926 if (pDevIns->pReg->pfnAttach)
927 {
928 PDMCritSectEnter(pDevIns->pCritSectRoR3, VERR_IGNORED);
929 rc = pDevIns->pReg->pfnAttach(pDevIns, iLun, fFlags);
930 if (RT_SUCCESS(rc) && ppBase)
931 *ppBase = pLun->pTop ? &pLun->pTop->IBase : NULL;
932 PDMCritSectLeave(pDevIns->pCritSectRoR3);
933 }
934 else
935 rc = VERR_PDM_DEVICE_NO_RT_ATTACH;
936 }
937 else
938 {
939 /* Yes, find the bottom most driver and ask it to attach to the new stuff. */
940 while (pDrvIns->Internal.s.pDown)
941 pDrvIns = pDrvIns->Internal.s.pDown;
942 if (pDrvIns->pReg->pfnAttach)
943 {
944 rc = pDrvIns->pReg->pfnAttach(pDrvIns, fFlags);
945 if (RT_SUCCESS(rc) && ppBase)
946 *ppBase = pDrvIns->Internal.s.pDown
947 ? &pDrvIns->Internal.s.pDown->IBase
948 : NULL;
949 }
950 else
951 rc = VERR_PDM_DRIVER_NO_RT_ATTACH;
952 }
953 }
954
955 if (ppBase)
956 LogFlow(("PDMR3DriverAttach: returns %Rrc *ppBase=%p\n", rc, *ppBase));
957 else
958 LogFlow(("PDMR3DriverAttach: returns %Rrc\n", rc));
959 return rc;
960}
961
962
963/**
964 * Detaches the specified driver instance.
965 *
966 * This is used to replumb drivers at runtime for simulating hot plugging and
967 * media changes.
968 *
969 * This is a superset of PDMR3DeviceDetach. It allows detaching drivers from
970 * any driver or device by specifying the driver to start detaching at. The
971 * only prerequisite is that the driver or device above implements the
972 * pfnDetach callback (PDMDRVREG / PDMDEVREG).
973 *
974 * @returns VBox status code.
975 * @param pUVM The user mode VM handle.
976 * @param pszDevice Device name.
977 * @param iDevIns Device instance.
978 * @param iLun The Logical Unit in which to look for the driver.
979 * @param pszDriver The name of the driver which to detach. If NULL
980 * then the entire driver chain is detatched.
981 * @param iOccurance The occurrence of that driver in the chain. This is
982 * usually 0.
983 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
984 * @thread EMT
985 */
986VMMR3DECL(int) PDMR3DriverDetach(PUVM pUVM, const char *pszDevice, unsigned iDevIns, unsigned iLun,
987 const char *pszDriver, unsigned iOccurance, uint32_t fFlags)
988{
989 LogFlow(("PDMR3DriverDetach: pszDevice=%p:{%s} iDevIns=%u iLun=%u pszDriver=%p:{%s} iOccurance=%u fFlags=%#x\n",
990 pszDevice, pszDevice, iDevIns, iLun, pszDriver, pszDriver, iOccurance, fFlags));
991 UVM_ASSERT_VALID_EXT_RETURN(pUVM, VERR_INVALID_VM_HANDLE);
992 PVM pVM = pUVM->pVM;
993 VM_ASSERT_VALID_EXT_RETURN(pVM, VERR_INVALID_VM_HANDLE);
994 VM_ASSERT_EMT(pVM);
995 AssertPtr(pszDevice);
996 AssertPtrNull(pszDriver);
997 Assert(iOccurance == 0 || pszDriver);
998 Assert(!(fFlags & ~(PDM_TACH_FLAGS_NOT_HOT_PLUG)));
999
1000 /*
1001 * Find the LUN in question.
1002 */
1003 PPDMLUN pLun;
1004 int rc = pdmR3DevFindLun(pVM, pszDevice, iDevIns, iLun, &pLun);
1005 if (RT_SUCCESS(rc))
1006 {
1007 /*
1008 * Locate the driver.
1009 */
1010 PPDMDRVINS pDrvIns = pLun->pTop;
1011 if (pDrvIns)
1012 {
1013 if (pszDriver)
1014 {
1015 while (pDrvIns)
1016 {
1017 if (!strcmp(pDrvIns->pReg->szName, pszDriver))
1018 {
1019 if (iOccurance == 0)
1020 break;
1021 iOccurance--;
1022 }
1023 pDrvIns = pDrvIns->Internal.s.pDown;
1024 }
1025 }
1026 if (pDrvIns)
1027 rc = pdmR3DrvDetach(pDrvIns, fFlags);
1028 else
1029 rc = VERR_PDM_DRIVER_INSTANCE_NOT_FOUND;
1030 }
1031 else
1032 rc = VINF_PDM_NO_DRIVER_ATTACHED_TO_LUN;
1033 }
1034
1035 LogFlow(("PDMR3DriverDetach: returns %Rrc\n", rc));
1036 return rc;
1037}
1038
1039
1040/**
1041 * Runtime detach and reattach of a new driver chain or sub chain.
1042 *
1043 * This is intended to be called on a non-EMT thread, this will instantiate the
1044 * new driver (sub-)chain, and then the EMTs will do the actual replumbing. The
1045 * destruction of the old driver chain will be taken care of on the calling
1046 * thread.
1047 *
1048 * @returns VBox status code.
1049 * @param pUVM The user mode VM handle.
1050 * @param pszDevice Device name.
1051 * @param iDevIns Device instance.
1052 * @param iLun The Logical Unit in which to look for the driver.
1053 * @param pszDriver The name of the driver which to detach and replace.
1054 * If NULL then the entire driver chain is to be
1055 * reattached.
1056 * @param iOccurance The occurrence of that driver in the chain. This is
1057 * usually 0.
1058 * @param fFlags Flags, combination of the PDMDEVATT_FLAGS_* \#defines.
1059 * @param pCfg The configuration of the new driver chain that is
1060 * going to be attached. The subtree starts with the
1061 * node containing a Driver key, a Config subtree and
1062 * optionally an AttachedDriver subtree.
1063 * If this parameter is NULL, then this call will work
1064 * like at a non-pause version of PDMR3DriverDetach.
1065 * @param ppBase Where to store the base interface pointer to the new
1066 * driver. Optional.
1067 *
1068 * @thread Any thread. The EMTs will be involved at some point though.
1069 */
1070VMMR3DECL(int) PDMR3DriverReattach(PUVM pUVM, const char *pszDevice, unsigned iDevIns, unsigned iLun,
1071 const char *pszDriver, unsigned iOccurance, uint32_t fFlags,
1072 PCFGMNODE pCfg, PPPDMIBASE ppBase)
1073{
1074 NOREF(pUVM); NOREF(pszDevice); NOREF(iDevIns); NOREF(iLun); NOREF(pszDriver); NOREF(iOccurance);
1075 NOREF(fFlags); NOREF(pCfg); NOREF(ppBase);
1076 return VERR_NOT_IMPLEMENTED;
1077}
1078
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