Changeset 108864 in vbox
- Timestamp:
- Apr 7, 2025 9:10:44 AM (13 days ago)
- svn:sync-xref-src-repo-rev:
- 168314
- Location:
- trunk
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/include/VBox/gic-its.h
r108835 r108864 45 45 /** Size of the ITS register frame. */ 46 46 #define GITS_REG_FRAME_SIZE _64K 47 48 /** The GITS command queue page size. */ 49 #define GITS_CMD_QUEUE_PAGE_SIZE 0x1000 50 /** The GITS command queue page offset mask. */ 51 #define GITS_CMD_QUEUE_PAGE_OFFSET_MASK 0xfff 52 /** The guest page shift (x86). */ 53 #define GITS_CMD_QUEUE_PAGE_SHIFT 12 47 54 48 55 /** GITS_CTLR: Control register - RW. */ … … 228 235 (SIZE, RSVD_9_8, SHAREABILITY, PHYS_ADDR, RSVD_52, OUTER_CACHE, RSVD_58_56, INNER_CACHE, RSVD_62, 229 236 VALID)); 237 /** GITS_CBASER: Physical address bits [15:12] are reserved MBZ. */ 238 #define GITS_CTRL_REG_CBASER_PHYS_ADDR_RSVD_15_12_MASK UINT64_C(0x000000000000f000) 230 239 /** GITS_CBASER: Mask of valid read-write bits. */ 231 240 #define GITS_CTRL_REG_CBASER_RW_MASK (UINT64_MAX & ~(GITS_BF_CTRL_REG_CBASER_RSVD_9_8_MASK | \ 232 241 GITS_BF_CTRL_REG_CBASER_RSVD_52_MASK | \ 233 242 GITS_BF_CTRL_REG_CBASER_RSVD_58_56_MASK | \ 234 GITS_BF_CTRL_REG_CBASER_RSVD_62_MASK)) 243 GITS_BF_CTRL_REG_CBASER_RSVD_62_MASK | \ 244 GITS_CTRL_REG_CBASER_PHYS_ADDR_RSVD_15_12_MASK)) 235 245 236 246 /** GITS_CWRITER: ITS command queue write register - RW. */ … … 370 380 } GITSMEMATTR; 371 381 372 373 382 /** 374 * TheITS entry type.383 * ITS entry type. 375 384 * In accordance to the ARM GIC spec. 376 385 */ … … 383 392 } GITSITSTYPE; 384 393 394 /** GITS command size in bytes. */ 395 #define GITS_CMD_SIZE 32 396 397 /** 398 * ITS command. 399 * In accordance to the ARM GIC spec. 400 */ 401 typedef union GITSCMD 402 { 403 RTUINT64U au64[4]; 404 struct 405 { 406 uint8_t uCmdId; 407 uint8_t au8Rsvd[3]; 408 uint32_t uDeviceId; 409 410 uint32_t uEventId; 411 uint32_t u32Rsvd; 412 413 uint64_t au64Rsvd[2]; 414 } clear; 415 } GITSCMD; 416 /** Pointer to an ITS command. */ 417 typedef GITSCMD *PGITSCMD; 418 /** Pointer to a const ITS command. */ 419 typedef GITSCMD const *PCGITSCMD; 420 421 385 422 #endif /* !VBOX_INCLUDED_gic_its_h */ 386 423 -
trunk/src/VBox/VMM/VMMAll/GICAll.cpp
r108835 r108864 3357 3357 /* Control registers space. */ 3358 3358 uint16_t const offReg = off & 0xfffc; 3359 gitsMmioWriteCtrl(p GitsDev, offReg, uValue, cb);3359 gitsMmioWriteCtrl(pDevIns, pGitsDev, offReg, uValue, cb); 3360 3360 LogFlowFunc(("offReg=%#RX16 (%s) written %#RX64\n", offReg, gitsGetCtrlRegDescription(offReg), uValue)); 3361 3361 } -
trunk/src/VBox/VMM/VMMAll/GITSAll.cpp
r108835 r108864 35 35 #include <VBox/log.h> 36 36 #include <VBox/gic.h> 37 #include <VBox/vmm/pdmdev.h> 37 38 #include <VBox/vmm/dbgf.h> 38 39 #include <iprt/errcore.h> /* VINF_SUCCESS */ 39 40 #include <iprt/string.h> /* RT_ZERO */ 41 #include <iprt/mem.h> /* RTMemAllocZ, RTMemFree */ 40 42 41 43 … … 103 105 default: 104 106 return "<UNKNOWN>"; 107 } 108 } 109 110 111 DECL_FORCE_INLINE(bool) gitsCmdQueueIsEmptyEx(PCGITSDEV pGitsDev, uint32_t *poffRead, uint32_t *poffWrite) 112 { 113 *poffRead = pGitsDev->uCmdReadReg & GITS_BF_CTRL_REG_CREADR_OFFSET_MASK; 114 *poffWrite = pGitsDev->uCmdWriteReg & GITS_BF_CTRL_REG_CWRITER_OFFSET_MASK; 115 return *poffRead == *poffWrite; 116 } 117 118 119 DECL_HIDDEN_CALLBACK(bool) gitsCmdQueueIsEmpty(PCGITSDEV pGitsDev) 120 { 121 uint32_t offRead; 122 uint32_t offWrite; 123 return gitsCmdQueueIsEmptyEx(pGitsDev, &offRead, &offWrite); 124 } 125 126 127 DECL_HIDDEN_CALLBACK(bool) gitsCmdQueueCanProcessRequests(PCGITSDEV pGitsDev) 128 { 129 if ( pGitsDev->fEnabled 130 && (pGitsDev->uCmdBaseReg.u & GITS_BF_CTRL_REG_CBASER_VALID_MASK) 131 && !(pGitsDev->uCmdReadReg & GITS_BF_CTRL_REG_CREADR_STALLED_MASK)) 132 return true; 133 return false; 134 } 135 136 137 static void gitsCmdQueueThreadWakeUpIfNeeded(PPDMDEVINS pDevIns, PGITSDEV pGitsDev) 138 { 139 Assert(PDMDevHlpCritSectIsOwner(pDevIns, pDevIns->CTX_SUFF(pCritSectRo))); 140 if ( gitsCmdQueueCanProcessRequests(pGitsDev) 141 && !gitsCmdQueueIsEmpty(pGitsDev)) 142 { 143 int const rc = PDMDevHlpSUPSemEventSignal(pDevIns, pGitsDev->hEvtCmdQueue); 144 AssertRC(rc); 105 145 } 106 146 } … … 218 258 219 259 220 DECL_HIDDEN_CALLBACK(void) gitsMmioWriteCtrl(P GITSDEV pGitsDev, uint16_t offReg, uint64_t uValue, unsigned cb)260 DECL_HIDDEN_CALLBACK(void) gitsMmioWriteCtrl(PPDMDEVINS pDevIns, PGITSDEV pGitsDev, uint16_t offReg, uint64_t uValue, unsigned cb) 221 261 { 222 262 Assert(cb == 8 || cb == 4); … … 250 290 Assert(cb == 4); 251 291 pGitsDev->fEnabled = RT_BF_GET(uValue, GITS_BF_CTRL_REG_CTLR_ENABLED); 292 gitsCmdQueueThreadWakeUpIfNeeded(pDevIns, pGitsDev); 252 293 break; 253 294 … … 258 299 else 259 300 pGitsDev->uCmdBaseReg.s.Lo = (uint32_t)uValue; 301 gitsCmdQueueThreadWakeUpIfNeeded(pDevIns, pGitsDev); 260 302 break; 261 303 … … 263 305 Assert(cb == 4); 264 306 pGitsDev->uCmdBaseReg.s.Hi = uValue & RT_HI_U32(GITS_CTRL_REG_CBASER_RW_MASK); 307 gitsCmdQueueThreadWakeUpIfNeeded(pDevIns, pGitsDev); 265 308 break; 266 309 267 310 case GITS_CTRL_REG_CWRITER_OFF: 268 311 pGitsDev->uCmdWriteReg = uValue & RT_LO_U32(GITS_CTRL_REG_CWRITER_RW_MASK); 312 gitsCmdQueueThreadWakeUpIfNeeded(pDevIns, pGitsDev); 269 313 break; 270 314 … … 272 316 /* Upper 32-bits are all reserved, ignore write. Fedora 40 arm64 guests (and probably others) do this. */ 273 317 Assert(uValue == 0); 318 gitsCmdQueueThreadWakeUpIfNeeded(pDevIns, pGitsDev); 274 319 break; 275 320 … … 371 416 } 372 417 } 418 419 420 DECL_HIDDEN_CALLBACK(int) gitsR3CmdQueueProcess(PPDMDEVINS pDevIns, PGITSDEV pGitsDev, void *pvBuf, uint32_t cbBuf) 421 { 422 int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VINF_SUCCESS); 423 PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); 424 425 if (gitsCmdQueueCanProcessRequests(pGitsDev)) 426 { 427 uint32_t offRead; 428 uint32_t offWrite; 429 bool const fIsEmpty = gitsCmdQueueIsEmptyEx(pGitsDev, &offRead, &offWrite); 430 if (!fIsEmpty) 431 { 432 uint32_t const cCmdQueuePages = (pGitsDev->uCmdBaseReg.u & GITS_BF_CTRL_REG_CBASER_SIZE_MASK) + 1; 433 uint32_t const cbCmdQueue = cCmdQueuePages << GITS_CMD_QUEUE_PAGE_SHIFT; 434 AssertRelease(cbCmdQueue <= cbBuf); /** @todo Paranoia; make this a debug assert later. */ 435 436 #if 0 437 /* 438 * Allocate space for the command-queue if we haven't done so already. 439 */ 440 if (pGitsDev->pvCmdQueue != NULL) 441 { 442 if (pGitsDev->cbCmdQueue <= cbCmdQueue) 443 { /* Already allocated sufficient space. */ } 444 else 445 { 446 /* Free old allocation and allocate a new one. */ 447 RTMemFree(pGitsDev->pvCmdQueue); 448 pGitsDev->cbCmdQueue = cbCmdQueue; 449 pGitsDev->pvCmdQueue = RTMemAllocZ(cbCmdQueue); 450 if (pGitsDev->pvCmdQueue) 451 { /* likely */ } 452 else 453 return VERR_NO_MEMORY; 454 } 455 } 456 else 457 { 458 /* Allocate one. */ 459 pGitsDev->cbCmdQueue = cbCmdQueue; 460 pGitsDev->pvCmdQueue = RTMemAllocZ(cbCmdQueue); 461 if (pGitsDev->pvCmdQueue) 462 { /* likely */ } 463 else 464 return VERR_NO_MEMORY; 465 } 466 #endif 467 468 /* 469 * Read all the commands into the command queue. 470 */ 471 RTGCPHYS const GCPhysCmds = pGitsDev->uCmdBaseReg.u & GITS_BF_CTRL_REG_CBASER_PHYS_ADDR_MASK; 472 473 /* Leave the critical section before reading (a potentially large amount of) commands. */ 474 PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); 475 476 int rc; 477 uint32_t cbCmds; 478 if (offWrite > offRead) 479 { 480 /* The commands have not wrapped around, read them in one go. */ 481 cbCmds = offWrite - offRead; 482 Assert(cbCmds <= cbBuf); 483 rc = PDMDevHlpPhysReadMeta(pDevIns, GCPhysCmds, pvBuf, cbCmds); 484 } 485 else 486 { 487 /* The commands have wrapped around, read forward and wrapped-around. */ 488 uint32_t const cbForward = cbCmdQueue - offRead; 489 uint32_t const cbWrapped = offWrite; 490 Assert(cbForward + cbWrapped <= cbBuf); 491 rc = PDMDevHlpPhysReadMeta(pDevIns, GCPhysCmds, pvBuf, cbForward); 492 if ( RT_SUCCESS(rc) 493 && cbWrapped > 0) 494 rc = PDMDevHlpPhysReadMeta(pDevIns, GCPhysCmds + cbForward, 495 (void *)((uintptr_t)pvBuf + cbForward), cbWrapped); 496 cbCmds = cbForward + cbWrapped; 497 } 498 499 /* Re-acquire the critical section as we now need to modify device state. */ 500 int const rcLock2 = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VINF_SUCCESS); 501 PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock2); 502 503 /* 504 * Process the commands in the queue. 505 */ 506 if (RT_SUCCESS(rc)) 507 { 508 509 uint32_t const cCmds = cbCmds / GITS_CMD_SIZE; 510 for (uint32_t idxCmd = 0; idxCmd < cCmds; idxCmd++) 511 { 512 PCGITSCMD pCmd = (PCGITSCMD)((uintptr_t)pvBuf + (idxCmd * sizeof(GITSCMD))); 513 AssertReleaseMsgFailed(("Cmd=%#x\n", pCmd->clear.uCmdId)); 514 } 515 516 PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); 517 return VINF_SUCCESS; 518 } 519 520 /* Failed to read command queue from the physical address specified by the guest, stall queue and retry later. */ 521 522 523 /** @todo Stall the command queue. */ 524 return VINF_TRY_AGAIN; 525 } 526 } 527 528 PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); 529 return VINF_SUCCESS; 530 } 373 531 #endif /* IN_RING3 */ 374 532 -
trunk/src/VBox/VMM/VMMR3/GICR3.cpp
r108835 r108864 309 309 return VINF_SUCCESS; 310 310 311 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 311 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 312 PGITSDEV pGitsDev = &pGicDev->Gits; 312 313 AssertPtrReturn(pGicDev, VERR_INVALID_PARAMETER); 313 314 LogFlowFunc(("Command-queue thread spawned and initialized\n")); 314 315 315 316 /* 316 * Pre-allocate the maximum size of the command queue allowed by the spec.317 * This prevents trashing the heap as well as deal with out-of-memory situations317 * Pre-allocate the maximum size of the command queue allowed by the ARM GIC spec. 318 * This prevents trashing the heap as well as dealing with out-of-memory situations 318 319 * up-front while starting the VM. It also simplifies the code from having to 319 320 * dynamically grow/shrink the allocation based on how software sizes the queue. 320 321 * Guests normally don't alter the queue size all the time, but that's not an 321 * assumption we can make. 322 */ 323 uint16_t const cMaxPages = GITS_BF_CTRL_REG_CBASER_SIZE_MASK + 1; 324 size_t const cbCmdQueue = cMaxPages << GUEST_PAGE_SHIFT; 325 void *pvCommands = RTMemAllocZ(cbCmdQueue); 326 AssertLogRelMsgReturn(pvCommands, ("Failed to alloc %.Rhcb (%zu bytes) for GITS command queue\n", cbCmdQueue, cbCmdQueue), 322 * assumption we can make. Another benefit is that we can avoid releasing and 323 * re-acquiring the device critical section if/when guests modifies the command 324 * queue size. 325 */ 326 uint16_t const cMaxPages = GITS_BF_CTRL_REG_CBASER_SIZE_MASK + 1; 327 size_t const cbCmds = cMaxPages << GITS_CMD_QUEUE_PAGE_SHIFT; 328 void *pvCmds = RTMemAllocZ(cbCmds); 329 AssertLogRelMsgReturn(pvCmds, ("Failed to alloc %.Rhcb (%zu bytes) for the GITS command queue\n", cbCmds, cbCmds), 327 330 VERR_NO_MEMORY); 328 331 329 332 while (pThread->enmState == PDMTHREADSTATE_RUNNING) 330 333 { 331 /* 332 * Sleep until we are woken up. 333 */ 334 /* Sleep until we are woken up. */ 334 335 { 335 int const rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pGicDev->hEvtCmdQueue, RT_INDEFINITE_WAIT); 336 AssertLogRelMsgReturnStmt(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), RTMemFree(pvCommands), rc); 336 int const rcLock = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pGitsDev->hEvtCmdQueue, RT_INDEFINITE_WAIT); 337 AssertLogRelMsgReturnStmt(RT_SUCCESS(rcLock) || rcLock == VERR_INTERRUPTED, ("%Rrc\n", rcLock), 338 RTMemFree(pvCmds), rcLock); 337 339 if (pThread->enmState != PDMTHREADSTATE_RUNNING) 338 340 break; 339 341 } 340 342 341 int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VINF_SUCCESS); 342 PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); 343 344 /** @todo Process commands. */ 345 346 PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); 347 } 348 349 RTMemFree(pvCommands); 343 /* Process the command queue. */ 344 int const rc = gitsR3CmdQueueProcess(pDevIns, pGitsDev, pvCmds, cbCmds); 345 if (RT_FAILURE(rc)) 346 break; 347 } 348 349 RTMemFree(pvCmds); 350 350 351 351 LogFlowFunc(("Command-queue thread terminating\n")); … … 357 357 * Wakes up the command-queue thread so it can respond to a state change. 358 358 * 359 * @return sVBox status code.359 * @return VBox status code. 360 360 * @param pDevIns The device instance. 361 361 * @param pThread The command-queue thread. … … 365 365 static DECLCALLBACK(int) gicItsR3CmdQueueThreadWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) 366 366 { 367 RT_NOREF 2(pDevIns,pThread);367 RT_NOREF(pThread); 368 368 LogFlowFunc(("\n")); 369 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 370 return PDMDevHlpSUPSemEventSignal(pDevIns, pGicDev->hEvtCmdQueue); 369 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 370 PGITSDEV pGitsDev = &pGicDev->Gits; 371 return PDMDevHlpSUPSemEventSignal(pDevIns, pGitsDev->hEvtCmdQueue); 371 372 } 372 373 … … 638 639 PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); 639 640 640 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 641 if (pGicDev->hEvtCmdQueue != NIL_SUPSEMEVENT) 642 { 643 PDMDevHlpSUPSemEventClose(pDevIns, pGicDev->hEvtCmdQueue); 644 pGicDev->hEvtCmdQueue = NIL_SUPSEMEVENT; 641 PGICDEV pGicDev = PDMDEVINS_2_DATA(pDevIns, PGICDEV); 642 PGITSDEV pGitsDev = &pGicDev->Gits; 643 if (pGitsDev->hEvtCmdQueue != NIL_SUPSEMEVENT) 644 { 645 PDMDevHlpSUPSemEventClose(pDevIns, pGitsDev->hEvtCmdQueue); 646 pGitsDev->hEvtCmdQueue = NIL_SUPSEMEVENT; 645 647 } 646 648 … … 916 918 917 919 /* Create ITS command-queue thread and semaphore. */ 920 PGITSDEV pGitsDev = &pGicDev->Gits; 918 921 char szCmdQueueThread[32]; 919 922 RT_ZERO(szCmdQueueThread); 920 923 RTStrPrintf(szCmdQueueThread, sizeof(szCmdQueueThread), "Gits-CmdQ-%u", iInstance); 921 rc = PDMDevHlpThreadCreate(pDevIns, &pGi cDev->pCmdQueueThread, &pGicDev, gicItsR3CmdQueueThread,924 rc = PDMDevHlpThreadCreate(pDevIns, &pGitsDev->pCmdQueueThread, &pGicDev, gicItsR3CmdQueueThread, 922 925 gicItsR3CmdQueueThreadWakeUp, 0 /* cbStack */, RTTHREADTYPE_IO, szCmdQueueThread); 923 926 AssertLogRelRCReturn(rc, rc); 924 927 925 rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pGi cDev->hEvtCmdQueue);928 rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pGitsDev->hEvtCmdQueue); 926 929 AssertLogRelRCReturn(rc, rc); 927 930 } -
trunk/src/VBox/VMM/include/GICInternal.h
r108835 r108864 34 34 #include <VBox/gic.h> 35 35 #include <VBox/vmm/pdmdev.h> 36 #include <VBox/vmm/pdmthread.h>37 36 #include <VBox/vmm/pdmgic.h> 38 37 #include <VBox/vmm/stam.h> … … 154 153 /** Padding. */ 155 154 bool afPadding1[7]; 156 /** The command-queue thread. */157 R3PTRTYPE(PPDMTHREAD) pCmdQueueThread;158 /** The event semaphore the command-queue thread waits on. */159 SUPSEMEVENT hEvtCmdQueue;160 155 /** @} */ 161 156 -
trunk/src/VBox/VMM/include/GITSInternal.h
r108833 r108864 32 32 #endif 33 33 34 #include <iprt/cdefs.h> 34 35 #include <VBox/types.h> 35 36 #include <VBox/gic-its.h> 37 #include <VBox/vmm/pdmthread.h> 36 38 37 39 /** @defgroup grp_gits_int Internal … … 82 84 /** Whether unmapped MSI reporting interrupt is enabled. */ 83 85 bool fUnmappedMsiReport; 84 /** Whether ITS is quiescent and can be powered down. */86 /** Whether the ITS is quiescent and can be powered down. */ 85 87 bool fQuiescent; 86 88 /** Padding. */ … … 98 100 /** @name Interrupt translation space. 99 101 * @{ */ 102 /** @} */ 103 104 /** @name Command queue thread. 105 * @{ */ 106 /** The command-queue thread. */ 107 R3PTRTYPE(PPDMTHREAD) pCmdQueueThread; 108 /** The event semaphore the command-queue thread waits on. */ 109 SUPSEMEVENT hEvtCmdQueue; 100 110 /** @} */ 101 111 … … 119 129 DECL_HIDDEN_CALLBACK(uint64_t) gitsMmioReadCtrl(PCGITSDEV pGitsDev, uint16_t offReg, unsigned cb); 120 130 DECL_HIDDEN_CALLBACK(uint64_t) gitsMmioReadTranslate(PCGITSDEV pGitsDev, uint16_t offReg, unsigned cb); 121 DECL_HIDDEN_CALLBACK(void) gitsMmioWriteCtrl(P GITSDEV pGitsDev, uint16_t offReg, uint64_t uValue, unsigned cb);131 DECL_HIDDEN_CALLBACK(void) gitsMmioWriteCtrl(PPDMDEVINS pDevIns, PGITSDEV pGitsDev, uint16_t offReg, uint64_t uValue, unsigned cb); 122 132 DECL_HIDDEN_CALLBACK(void) gitsMmioWriteTranslate(PGITSDEV pGitsDev, uint16_t offReg, uint64_t uValue, unsigned cb); 123 133 124 134 #ifdef IN_RING3 125 135 DECL_HIDDEN_CALLBACK(void) gitsR3DbgInfo(PCGITSDEV pGitsDev, PCDBGFINFOHLP pHlp, const char *pszArgs); 136 DECL_HIDDEN_CALLBACK(int) gitsR3CmdQueueProcess(PPDMDEVINS pDevIns, PGITSDEV pGitsDev, void *pvBuf, uint32_t cbBuf); 137 DECL_HIDDEN_CALLBACK(bool) gitsR3CmdQueueCanProcessRequests(PCGITSDEV pGitsDev); 138 DECL_HIDDEN_CALLBACK(bool) gitsR3CmdQueueIsEmpty(PCGITSDEV pGitsDev); 126 139 #endif 127 140
Note:
See TracChangeset
for help on using the changeset viewer.