VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/GIM.cpp@ 58170

Last change on this file since 58170 was 58126, checked in by vboxsync, 10 years ago

VMM: Fixed almost all the Doxygen warnings.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.0 KB
Line 
1/* $Id: GIM.cpp 58126 2015-10-08 20:59:48Z vboxsync $ */
2/** @file
3 * GIM - Guest Interface Manager.
4 */
5
6/*
7 * Copyright (C) 2014-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/** @page pg_gim GIM - The Guest Interface Manager
19 *
20 * The Guest Interface Manager abstracts an interface provider through which
21 * guests may interact with the hypervisor.
22 *
23 * @see grp_gim
24 *
25 *
26 * @section sec_gim_provider Providers
27 *
28 * A GIM provider implements a particular hypervisor interface such as Microsoft
29 * Hyper-V, Linux KVM and so on. It hooks into various components in the VMM to
30 * ease the guest in running under a recognized, virtualized environment.
31 *
32 * The GIM provider configured for the VM needs to be recognized by the guest OS
33 * in order to make use of features supported by the interface. Since it
34 * requires co-operation from the guest OS, a GIM provider may also referred to
35 * as a paravirtualization interface.
36 *
37 * One of the goals of having a paravirtualized interface is for enabling guests
38 * to be more accurate and efficient when operating in a virtualized
39 * environment. For instance, a guest OS which interfaces to VirtualBox through
40 * a GIM provider may rely on the provider for supplying the correct TSC
41 * frequency of the host processor. The guest can then avoid caliberating the
42 * TSC itself, resulting in higher accuracy and better performance.
43 *
44 * At most, only one GIM provider can be active for a running VM and cannot be
45 * changed during the lifetime of the VM.
46 */
47
48
49/*********************************************************************************************************************************
50* Header Files *
51*********************************************************************************************************************************/
52#define LOG_GROUP LOG_GROUP_GIM
53#include <VBox/log.h>
54#include "GIMInternal.h"
55#include <VBox/vmm/vm.h>
56#include <VBox/vmm/hm.h>
57#include <VBox/vmm/ssm.h>
58#include <VBox/vmm/pdmdev.h>
59
60#include <iprt/err.h>
61#include <iprt/string.h>
62
63/* Include all GIM providers. */
64#include "GIMMinimalInternal.h"
65#include "GIMHvInternal.h"
66#include "GIMKvmInternal.h"
67
68
69/*********************************************************************************************************************************
70* Internal Functions *
71*********************************************************************************************************************************/
72static FNSSMINTSAVEEXEC gimR3Save;
73static FNSSMINTLOADEXEC gimR3Load;
74static FNPGMPHYSHANDLER gimR3Mmio2WriteHandler;
75
76
77/**
78 * Initializes the GIM.
79 *
80 * @returns VBox status code.
81 * @param pVM The cross context VM structure.
82 */
83VMMR3_INT_DECL(int) GIMR3Init(PVM pVM)
84{
85 LogFlow(("GIMR3Init\n"));
86
87 /*
88 * Assert alignment and sizes.
89 */
90 AssertCompile(sizeof(pVM->gim.s) <= sizeof(pVM->gim.padding));
91
92 /*
93 * Initialize members.
94 */
95 pVM->gim.s.hSemiReadOnlyMmio2Handler = NIL_PGMPHYSHANDLERTYPE;
96
97 /*
98 * Register the saved state data unit.
99 */
100 int rc = SSMR3RegisterInternal(pVM, "GIM", 0 /* uInstance */, GIM_SAVED_STATE_VERSION, sizeof(GIM),
101 NULL /* pfnLivePrep */, NULL /* pfnLiveExec */, NULL /* pfnLiveVote*/,
102 NULL /* pfnSavePrep */, gimR3Save, NULL /* pfnSaveDone */,
103 NULL /* pfnLoadPrep */, gimR3Load, NULL /* pfnLoadDone */);
104 if (RT_FAILURE(rc))
105 return rc;
106
107 /*
108 * Read configuration.
109 */
110 PCFGMNODE pCfgNode = CFGMR3GetChild(CFGMR3GetRoot(pVM), "GIM/");
111
112 /** @cfgm{/GIM/Provider, string}
113 * The name of the GIM provider. The default is "none". */
114 char szProvider[64];
115 rc = CFGMR3QueryStringDef(pCfgNode, "Provider", szProvider, sizeof(szProvider), "None");
116 AssertLogRelRCReturn(rc, rc);
117
118 /** @cfgm{/GIM/Version, uint32_t}
119 * The interface version. The default is 0, which means "provide the most
120 * up-to-date implementation". */
121 uint32_t uVersion;
122 rc = CFGMR3QueryU32Def(pCfgNode, "Version", &uVersion, 0 /* default */);
123 AssertLogRelRCReturn(rc, rc);
124
125 /*
126 * Setup the GIM provider for this VM.
127 */
128 LogRel(("GIM: Using provider '%s' (Implementation version: %u)\n", szProvider, uVersion));
129 if (!RTStrCmp(szProvider, "None"))
130 pVM->gim.s.enmProviderId = GIMPROVIDERID_NONE;
131 else
132 {
133 pVM->gim.s.u32Version = uVersion;
134 /** @todo r=bird: Because u32Version is saved, it should be translated to the
135 * 'most up-to-date implementation' version number when 0. Otherwise,
136 * we'll have abiguities when loading the state of older VMs. */
137 if (!RTStrCmp(szProvider, "Minimal"))
138 {
139 pVM->gim.s.enmProviderId = GIMPROVIDERID_MINIMAL;
140 rc = gimR3MinimalInit(pVM);
141 }
142 else if (!RTStrCmp(szProvider, "HyperV"))
143 {
144 pVM->gim.s.enmProviderId = GIMPROVIDERID_HYPERV;
145 rc = gimR3HvInit(pVM);
146 }
147 else if (!RTStrCmp(szProvider, "KVM"))
148 {
149 pVM->gim.s.enmProviderId = GIMPROVIDERID_KVM;
150 rc = gimR3KvmInit(pVM);
151 }
152 else
153 rc = VMR3SetError(pVM->pUVM, VERR_GIM_INVALID_PROVIDER, RT_SRC_POS, "Provider '%s' unknown.", szProvider);
154 }
155 return rc;
156}
157
158
159/**
160 * Initializes the remaining bits of the GIM provider.
161 *
162 * This is called after initializing HM and most other VMM components.
163 *
164 * @returns VBox status code.
165 * @param pVM The cross context VM structure.
166 * @thread EMT(0)
167 */
168VMMR3_INT_DECL(int) GIMR3InitCompleted(PVM pVM)
169{
170 switch (pVM->gim.s.enmProviderId)
171 {
172 case GIMPROVIDERID_MINIMAL:
173 return gimR3MinimalInitCompleted(pVM);
174
175 case GIMPROVIDERID_HYPERV:
176 return gimR3HvInitCompleted(pVM);
177
178 case GIMPROVIDERID_KVM:
179 return gimR3KvmInitCompleted(pVM);
180
181 default:
182 break;
183 }
184
185 if (!TMR3CpuTickIsFixedRateMonotonic(pVM, true /* fWithParavirtEnabled */))
186 LogRel(("GIM: Warning!!! Host TSC is unstable. The guest may behave unpredictably with a paravirtualized clock.\n"));
187
188 return VINF_SUCCESS;
189}
190
191
192/**
193 * Applies relocations to data and code managed by this component.
194 *
195 * This function will be called at init and whenever the VMM need to relocate
196 * itself inside the GC.
197 *
198 * @param pVM The cross context VM structure.
199 * @param offDelta Relocation delta relative to old location.
200 */
201VMM_INT_DECL(void) GIMR3Relocate(PVM pVM, RTGCINTPTR offDelta)
202{
203 LogFlow(("GIMR3Relocate\n"));
204
205 if ( pVM->gim.s.enmProviderId == GIMPROVIDERID_NONE
206 || HMIsEnabled(pVM))
207 return;
208
209 switch (pVM->gim.s.enmProviderId)
210 {
211 case GIMPROVIDERID_MINIMAL:
212 {
213 gimR3MinimalRelocate(pVM, offDelta);
214 break;
215 }
216
217 case GIMPROVIDERID_HYPERV:
218 {
219 gimR3HvRelocate(pVM, offDelta);
220 break;
221 }
222
223 case GIMPROVIDERID_KVM:
224 {
225 gimR3KvmRelocate(pVM, offDelta);
226 break;
227 }
228
229 default:
230 {
231 AssertMsgFailed(("Invalid provider Id %#x\n", pVM->gim.s.enmProviderId));
232 break;
233 }
234 }
235}
236
237
238/**
239 * @callback_method_impl{FNSSMINTSAVEEXEC}
240 */
241static DECLCALLBACK(int) gimR3Save(PVM pVM, PSSMHANDLE pSSM)
242{
243 AssertReturn(pVM, VERR_INVALID_PARAMETER);
244 AssertReturn(pSSM, VERR_SSM_INVALID_STATE);
245
246 /** @todo Save per-CPU data. */
247 int rc = VINF_SUCCESS;
248#if 0
249 SSMR3PutU32(pSSM, pVM->cCpus);
250 for (VMCPUID i = 0; i < pVM->cCpus; i++)
251 {
252 rc = SSMR3PutXYZ(pSSM, pVM->aCpus[i].gim.s.XYZ);
253 }
254#endif
255
256 /*
257 * Save per-VM data.
258 */
259 SSMR3PutU32(pSSM, pVM->gim.s.enmProviderId);
260 SSMR3PutU32(pSSM, pVM->gim.s.u32Version);
261
262 /*
263 * Save provider-specific data.
264 */
265 switch (pVM->gim.s.enmProviderId)
266 {
267 case GIMPROVIDERID_HYPERV:
268 rc = gimR3HvSave(pVM, pSSM);
269 AssertRCReturn(rc, rc);
270 break;
271
272 case GIMPROVIDERID_KVM:
273 rc = gimR3KvmSave(pVM, pSSM);
274 AssertRCReturn(rc, rc);
275 break;
276
277 default:
278 break;
279 }
280
281 return rc;
282}
283
284
285/**
286 * @callback_method_impl{FNSSMINTLOADEXEC}
287 */
288static DECLCALLBACK(int) gimR3Load(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
289{
290 if (uPass != SSM_PASS_FINAL)
291 return VINF_SUCCESS;
292 if (uVersion != GIM_SAVED_STATE_VERSION)
293 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
294
295 /** @todo Load per-CPU data. */
296 int rc;
297#if 0
298 for (VMCPUID i = 0; i < pVM->cCpus; i++)
299 {
300 rc = SSMR3PutXYZ(pSSM, pVM->aCpus[i].gim.s.XYZ);
301 }
302#endif
303
304 /*
305 * Load per-VM data.
306 */
307 uint32_t uProviderId;
308 uint32_t uProviderVersion;
309
310 rc = SSMR3GetU32(pSSM, &uProviderId); AssertRCReturn(rc, rc);
311 rc = SSMR3GetU32(pSSM, &uProviderVersion); AssertRCReturn(rc, rc);
312
313 if ((GIMPROVIDERID)uProviderId != pVM->gim.s.enmProviderId)
314 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Saved GIM provider %u differs from the configured one (%u)."),
315 uProviderId, pVM->gim.s.enmProviderId);
316#if 0 /** @todo r=bird: Figure out what you mean to do here with the version. */
317 if (uProviderVersion != pVM->gim.s.u32Version)
318 return SSMR3SetCfgError(pSSM, RT_SRC_POS, N_("Saved GIM provider version %u differs from the configured one (%u)."),
319 uProviderVersion, pVM->gim.s.u32Version);
320#else
321 pVM->gim.s.u32Version = uProviderVersion;
322#endif
323
324 /*
325 * Load provider-specific data.
326 */
327 switch (pVM->gim.s.enmProviderId)
328 {
329 case GIMPROVIDERID_HYPERV:
330 rc = gimR3HvLoad(pVM, pSSM, uVersion);
331 AssertRCReturn(rc, rc);
332 break;
333
334 case GIMPROVIDERID_KVM:
335 rc = gimR3KvmLoad(pVM, pSSM, uVersion);
336 AssertRCReturn(rc, rc);
337 break;
338
339 default:
340 break;
341 }
342
343 return VINF_SUCCESS;
344}
345
346
347/**
348 * Terminates the GIM.
349 *
350 * Termination means cleaning up and freeing all resources,
351 * the VM itself is, at this point, powered off or suspended.
352 *
353 * @returns VBox status code.
354 * @param pVM The cross context VM structure.
355 */
356VMMR3_INT_DECL(int) GIMR3Term(PVM pVM)
357{
358 switch (pVM->gim.s.enmProviderId)
359 {
360 case GIMPROVIDERID_HYPERV:
361 return gimR3HvTerm(pVM);
362
363 case GIMPROVIDERID_KVM:
364 return gimR3KvmTerm(pVM);
365
366 default:
367 break;
368 }
369 return VINF_SUCCESS;
370}
371
372
373/**
374 * The VM is being reset.
375 *
376 * For the GIM component this means unmapping and unregistering MMIO2 regions
377 * and other provider-specific resets.
378 *
379 * @returns VBox status code.
380 * @param pVM The cross context VM structure.
381 */
382VMMR3_INT_DECL(void) GIMR3Reset(PVM pVM)
383{
384 switch (pVM->gim.s.enmProviderId)
385 {
386 case GIMPROVIDERID_HYPERV:
387 return gimR3HvReset(pVM);
388
389 case GIMPROVIDERID_KVM:
390 return gimR3KvmReset(pVM);
391
392 default:
393 break;
394 }
395}
396
397
398/**
399 * Registers the GIM device with VMM.
400 *
401 * @param pVM The cross context VM structure.
402 * @param pDevIns Pointer to the GIM device instance.
403 * @param pDebugStream Pointer to the GIM device debug connection, can be
404 * NULL.
405 */
406VMMR3DECL(void) GIMR3GimDeviceRegister(PVM pVM, PPDMDEVINS pDevIns, PPDMISTREAM pDebugStream)
407{
408 pVM->gim.s.pDevInsR3 = pDevIns;
409 pVM->gim.s.pDebugStreamR3 = pDebugStream;
410}
411
412
413/**
414 * Read data from a host debug session.
415 *
416 * @returns VBox status code.
417 *
418 * @param pVM The cross context VM structure.
419 * @param pvRead The read buffer.
420 * @param pcbRead The size of the read buffer as well as where to store
421 * the number of bytes read.
422 * @thread EMT.
423 */
424VMMR3_INT_DECL(int) GIMR3DebugRead(PVM pVM, void *pvRead, size_t *pcbRead)
425{
426 PPDMISTREAM pDebugStream = pVM->gim.s.pDebugStreamR3;
427 if (pDebugStream)
428 return pDebugStream->pfnRead(pDebugStream, pvRead, pcbRead);
429 return VERR_GIM_NO_DEBUG_CONNECTION;
430}
431
432
433/**
434 * Write data to a host debug session.
435 *
436 * @returns VBox status code.
437 *
438 * @param pVM The cross context VM structure.
439 * @param pvWrite The write buffer.
440 * @param pcbWrite The size of the write buffer as well as where to store
441 * the number of bytes written.
442 * @thread EMT.
443 */
444VMMR3_INT_DECL(int) GIMR3DebugWrite(PVM pVM, void *pvWrite, size_t *pcbWrite)
445{
446 PPDMISTREAM pDebugStream = pVM->gim.s.pDebugStreamR3;
447 if (pDebugStream)
448 return pDebugStream->pfnWrite(pDebugStream, pvWrite, pcbWrite);
449 return VERR_GIM_NO_DEBUG_CONNECTION;
450}
451
452
453/**
454 * Returns the array of MMIO2 regions that are expected to be registered and
455 * later mapped into the guest-physical address space for the GIM provider
456 * configured for the VM.
457 *
458 * @returns Pointer to an array of GIM MMIO2 regions, may return NULL.
459 * @param pVM The cross context VM structure.
460 * @param pcRegions Where to store the number of items in the array.
461 *
462 * @remarks The caller does not own and therefore must -NOT- try to free the
463 * returned pointer.
464 */
465VMMR3DECL(PGIMMMIO2REGION) GIMR3GetMmio2Regions(PVM pVM, uint32_t *pcRegions)
466{
467 Assert(pVM);
468 Assert(pcRegions);
469
470 *pcRegions = 0;
471 switch (pVM->gim.s.enmProviderId)
472 {
473 case GIMPROVIDERID_HYPERV:
474 return gimR3HvGetMmio2Regions(pVM, pcRegions);
475
476 default:
477 break;
478 }
479
480 return NULL;
481}
482
483
484/**
485 * Unmaps a registered MMIO2 region in the guest address space and removes any
486 * access handlers for it.
487 *
488 * @returns VBox status code.
489 * @param pVM The cross context VM structure.
490 * @param pRegion Pointer to the GIM MMIO2 region.
491 */
492VMMR3_INT_DECL(int) GIMR3Mmio2Unmap(PVM pVM, PGIMMMIO2REGION pRegion)
493{
494 AssertPtr(pVM);
495 AssertPtr(pRegion);
496
497 PPDMDEVINS pDevIns = pVM->gim.s.pDevInsR3;
498 AssertPtr(pDevIns);
499 if (pRegion->fMapped)
500 {
501 int rc = PGMHandlerPhysicalDeregister(pVM, pRegion->GCPhysPage);
502 AssertRC(rc);
503
504 rc = PDMDevHlpMMIO2Unmap(pDevIns, pRegion->iRegion, pRegion->GCPhysPage);
505 if (RT_SUCCESS(rc))
506 {
507 pRegion->fMapped = false;
508 pRegion->GCPhysPage = NIL_RTGCPHYS;
509 }
510 }
511 return VINF_SUCCESS;
512}
513
514
515/**
516 * @callback_method_impl{FNPGMPHYSHANDLER,
517 * Write access handler for mapped MMIO2 pages. Currently ignores writes.}
518 *
519 * @todo In the future we might want to let the GIM provider decide what the
520 * handler should do (like throwing \#GP faults).
521 */
522static DECLCALLBACK(VBOXSTRICTRC)
523gimR3Mmio2WriteHandler(PVM pVM, PVMCPU pVCpu, RTGCPHYS GCPhys, void *pvPhys, void *pvBuf, size_t cbBuf,
524 PGMACCESSTYPE enmAccessType, PGMACCESSORIGIN enmOrigin, void *pvUser)
525{
526 /*
527 * Ignore writes to the mapped MMIO2 page.
528 */
529 Assert(enmAccessType == PGMACCESSTYPE_WRITE);
530 return VINF_SUCCESS; /** @todo Hyper-V says we should \#GP(0) fault for writes to the Hypercall and TSC page. */
531}
532
533
534/**
535 * Maps a registered MMIO2 region in the guest address space.
536 *
537 * The region will be made read-only and writes from the guest will be ignored.
538 *
539 * @returns VBox status code.
540 * @param pVM The cross context VM structure.
541 * @param pRegion Pointer to the GIM MMIO2 region.
542 * @param GCPhysRegion Where in the guest address space to map the region.
543 */
544VMMR3_INT_DECL(int) GIMR3Mmio2Map(PVM pVM, PGIMMMIO2REGION pRegion, RTGCPHYS GCPhysRegion)
545{
546 PPDMDEVINS pDevIns = pVM->gim.s.pDevInsR3;
547 AssertPtr(pDevIns);
548
549 /* The guest-physical address must be page-aligned. */
550 if (GCPhysRegion & PAGE_OFFSET_MASK)
551 {
552 LogFunc(("%s: %#RGp not paging aligned\n", pRegion->szDescription, GCPhysRegion));
553 return VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS;
554 }
555
556 /* Allow only normal pages to be overlaid using our MMIO2 pages (disallow MMIO, ROM, reserved pages). */
557 /** @todo Hyper-V doesn't seem to be very strict about this, may be relax
558 * later if some guest really requires it. */
559 if (!PGMPhysIsGCPhysNormal(pVM, GCPhysRegion))
560 {
561 LogFunc(("%s: %#RGp is not normal memory\n", pRegion->szDescription, GCPhysRegion));
562 return VERR_PGM_INVALID_GC_PHYSICAL_ADDRESS;
563 }
564
565 if (!pRegion->fRegistered)
566 {
567 LogFunc(("%s: Region has not been registered.\n", pRegion->szDescription));
568 return VERR_GIM_IPE_1;
569 }
570
571 /*
572 * Map the MMIO2 region over the specified guest-physical address.
573 */
574 int rc = PDMDevHlpMMIO2Map(pDevIns, pRegion->iRegion, GCPhysRegion);
575 if (RT_SUCCESS(rc))
576 {
577 /*
578 * Install access-handlers for the mapped page to prevent (ignore) writes to it
579 * from the guest.
580 */
581 if (pVM->gim.s.hSemiReadOnlyMmio2Handler == NIL_PGMPHYSHANDLERTYPE)
582 rc = PGMR3HandlerPhysicalTypeRegister(pVM, PGMPHYSHANDLERKIND_WRITE,
583 gimR3Mmio2WriteHandler,
584 NULL /* pszModR0 */, NULL /* pszHandlerR0 */, NULL /* pszPfHandlerR0 */,
585 NULL /* pszModRC */, NULL /* pszHandlerRC */, NULL /* pszPfHandlerRC */,
586 "GIM read-only MMIO2 handler",
587 &pVM->gim.s.hSemiReadOnlyMmio2Handler);
588 if (RT_SUCCESS(rc))
589 {
590 rc = PGMHandlerPhysicalRegister(pVM, GCPhysRegion, GCPhysRegion + (pRegion->cbRegion - 1),
591 pVM->gim.s.hSemiReadOnlyMmio2Handler,
592 NULL /* pvUserR3 */, NIL_RTR0PTR /* pvUserR0 */, NIL_RTRCPTR /* pvUserRC */,
593 pRegion->szDescription);
594 if (RT_SUCCESS(rc))
595 {
596 pRegion->fMapped = true;
597 pRegion->GCPhysPage = GCPhysRegion;
598 return rc;
599 }
600 }
601
602 PDMDevHlpMMIO2Unmap(pDevIns, pRegion->iRegion, GCPhysRegion);
603 }
604
605 return rc;
606}
607
608#if 0
609/**
610 * Registers the physical handler for the registered and mapped MMIO2 region.
611 *
612 * @returns VBox status code.
613 * @param pVM The cross context VM structure.
614 * @param pRegion Pointer to the GIM MMIO2 region.
615 */
616VMMR3_INT_DECL(int) GIMR3Mmio2HandlerPhysicalRegister(PVM pVM, PGIMMMIO2REGION pRegion)
617{
618 AssertPtr(pRegion);
619 AssertReturn(pRegion->fRegistered, VERR_GIM_IPE_2);
620 AssertReturn(pRegion->fMapped, VERR_GIM_IPE_3);
621
622 return PGMR3HandlerPhysicalRegister(pVM,
623 PGMPHYSHANDLERKIND_WRITE,
624 pRegion->GCPhysPage, pRegion->GCPhysPage + (pRegion->cbRegion - 1),
625 gimR3Mmio2WriteHandler, NULL /* pvUserR3 */,
626 NULL /* pszModR0 */, NULL /* pszHandlerR0 */, NIL_RTR0PTR /* pvUserR0 */,
627 NULL /* pszModRC */, NULL /* pszHandlerRC */, NIL_RTRCPTR /* pvUserRC */,
628 pRegion->szDescription);
629}
630
631
632/**
633 * Deregisters the physical handler for the MMIO2 region.
634 *
635 * @returns VBox status code.
636 * @param pVM The cross context VM structure.
637 * @param pRegion Pointer to the GIM MMIO2 region.
638 */
639VMMR3_INT_DECL(int) GIMR3Mmio2HandlerPhysicalDeregister(PVM pVM, PGIMMMIO2REGION pRegion)
640{
641 return PGMHandlerPhysicalDeregister(pVM, pRegion->GCPhysPage);
642}
643#endif
644
Note: See TracBrowser for help on using the repository browser.

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