Changeset 81814 in vbox for trunk/src/VBox/Devices/VirtIO
- Timestamp:
- Nov 12, 2019 6:21:33 PM (5 years ago)
- Location:
- trunk/src/VBox/Devices/VirtIO
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Devices/VirtIO/Virtio_1_0.cpp
r81690 r81814 85 85 uint16_t uIdx; /**< idx Index of next free ring slot */ 86 86 uint16_t auRing[RT_FLEXIBLE_ARRAY]; /**< ring Ring: avail drv to dev bufs */ 87 /* uint16_t uUsedEventIdx; - used_event (if VIRTQ_USED_F_EVENT_IDX) */88 87 } VIRTQ_AVAIL_T, *PVIRTQ_AVAIL_T; 89 88 … … 99 98 uint16_t uIdx; /**< idx Index of next ring slot */ 100 99 VIRTQ_USED_ELEM_T aRing[RT_FLEXIBLE_ARRAY]; /**< ring Ring: used dev to drv bufs */ 101 /** @todo r=bird: From the usage, this member shouldn't be here and will only102 * confuse compilers . */103 /* uint16_t uAvailEventIdx; - avail_event if (VIRTQ_USED_F_EVENT_IDX) */104 100 } VIRTQ_USED_T, *PVIRTQ_USED_T; 105 101 … … 272 268 273 269 #ifdef LOG_ENABLED 270 271 void virtioCoreSgBufInit(PVIRTIOSGBUF pGcSgBuf, PCVIRTIOSGSEG paSegs, size_t cSegs) 272 { 273 AssertPtr(pGcSgBuf); 274 Assert( (cSegs > 0 && VALID_PTR(paSegs)) 275 || (!cSegs && !paSegs)); 276 Assert(cSegs < (~(unsigned)0 >> 1)); 277 278 pGcSgBuf->paSegs = paSegs; 279 pGcSgBuf->cSegs = (unsigned)cSegs; 280 pGcSgBuf->idxSeg = 0; 281 if (cSegs && paSegs) 282 { 283 pGcSgBuf->pGcSegCur = paSegs[0].pGcSeg; 284 pGcSgBuf->cbSegLeft = paSegs[0].cbSeg; 285 } 286 else 287 { 288 pGcSgBuf->pGcSegCur = 0; 289 pGcSgBuf->cbSegLeft = 0; 290 } 291 } 292 293 static RTGCPHYS virtioCoreSgBufGet(PVIRTIOSGBUF pGcSgBuf, size_t *pcbData) 294 { 295 size_t cbData; 296 RTGCPHYS pGcBuf; 297 /* Check that the S/G buffer has memory left. */ 298 if (RT_LIKELY(pGcSgBuf->idxSeg < pGcSgBuf->cSegs && pGcSgBuf->cbSegLeft)) 299 { /* likely */ } 300 else 301 { 302 *pcbData = 0; 303 return 0; 304 } 305 306 AssertMsg( pGcSgBuf->cbSegLeft <= 128 * _1M 307 && (RTGCPHYS)pGcSgBuf->pGcSegCur >= (RTGCPHYS)pGcSgBuf->paSegs[pGcSgBuf->idxSeg].pGcSeg 308 && (RTGCPHYS)pGcSgBuf->pGcSegCur + pGcSgBuf->cbSegLeft <= 309 (RTGCPHYS)pGcSgBuf->paSegs[pGcSgBuf->idxSeg].pGcSeg + pGcSgBuf->paSegs[pGcSgBuf->idxSeg].cbSeg, 310 ("pGcSgBuf->idxSeg=%d pGcSgBuf->cSegs=%d pGcSgBuf->pGcSegCur=%p pGcSgBuf->cbSegLeft=%zd " 311 "pGcSgBuf->paSegs[%d].pGcSeg=%p pGcSgBuf->paSegs[%d].cbSeg=%zd\n", 312 pGcSgBuf->idxSeg, pGcSgBuf->cSegs, pGcSgBuf->pGcSegCur, pGcSgBuf->cbSegLeft, 313 pGcSgBuf->idxSeg, pGcSgBuf->paSegs[pGcSgBuf->idxSeg].pGcSeg, pGcSgBuf->idxSeg, 314 pGcSgBuf->paSegs[pGcSgBuf->idxSeg].cbSeg)); 315 316 cbData = RT_MIN(*pcbData, pGcSgBuf->cbSegLeft); 317 pGcBuf = pGcSgBuf->pGcSegCur; 318 pGcSgBuf->cbSegLeft -= cbData; 319 if (!pGcSgBuf->cbSegLeft) 320 { 321 pGcSgBuf->idxSeg++; 322 323 if (pGcSgBuf->idxSeg < pGcSgBuf->cSegs) 324 { 325 pGcSgBuf->pGcSegCur = pGcSgBuf->paSegs[pGcSgBuf->idxSeg].pGcSeg; 326 pGcSgBuf->cbSegLeft = pGcSgBuf->paSegs[pGcSgBuf->idxSeg].cbSeg; 327 } 328 *pcbData = cbData; 329 } 330 else 331 pGcSgBuf->pGcSegCur = pGcSgBuf->pGcSegCur + cbData; 332 333 return pGcBuf; 334 } 335 336 void virtioCoreSgBufReset(PVIRTIOSGBUF pGcSgBuf) 337 { 338 AssertPtrReturnVoid(pGcSgBuf); 339 340 pGcSgBuf->idxSeg = 0; 341 if (pGcSgBuf->cSegs) 342 { 343 pGcSgBuf->pGcSegCur = pGcSgBuf->paSegs[0].pGcSeg; 344 pGcSgBuf->cbSegLeft = pGcSgBuf->paSegs[0].cbSeg; 345 } 346 else 347 { 348 pGcSgBuf->pGcSegCur = 0; 349 pGcSgBuf->cbSegLeft = 0; 350 } 351 } 352 353 RTGCPHYS virtioCoreSgBufAdvance(PVIRTIOSGBUF pGcSgBuf, size_t cbAdvance) 354 { 355 AssertReturn(pGcSgBuf, 0); 356 357 size_t cbLeft = cbAdvance; 358 while (cbLeft) 359 { 360 size_t cbThisAdvance = cbLeft; 361 virtioCoreSgBufGet(pGcSgBuf, &cbThisAdvance); 362 if (!cbThisAdvance) 363 break; 364 365 cbLeft -= cbThisAdvance; 366 } 367 return cbAdvance - cbLeft; 368 } 369 370 RTGCPHYS virtioCoreSgBufGetNextSegment(PVIRTIOSGBUF pGcSgBuf, size_t *pcbSeg) 371 { 372 AssertReturn(pGcSgBuf, 0); 373 AssertPtrReturn(pcbSeg, 0); 374 375 if (!*pcbSeg) 376 *pcbSeg = pGcSgBuf->cbSegLeft; 377 378 return virtioCoreSgBufGet(pGcSgBuf, pcbSeg); 379 } 274 380 275 381 /** … … 359 465 uValue.u = 0; 360 466 memcpy(uValue.au8, pv, cb); 361 Log6 Func(("%s: Guest %s %s %#0*RX64\n",467 Log6(("%s: Guest %s %s %#0*RX64\n", 362 468 pszFunc, fWrite ? "wrote" : "read ", szDepiction, 2 + cb * 2, uValue.u)); 363 469 } 364 470 else /* odd number or oversized access, ... log inline hex-dump style */ 365 471 { 366 Log6 Func(("%s: Guest %s %s%s[%d:%d]: %.*Rhxs\n",472 Log6(("%s: Guest %s %s%s[%d:%d]: %.*Rhxs\n", 367 473 pszFunc, fWrite ? "wrote" : "read ", pszMember, 368 474 szIdx, uOffset, uOffset + cb, cb, pv)); … … 491 597 PVIRTQSTATE pVirtq = &pVirtio->virtqState[idxQueue]; 492 598 493 P RTSGSEG paSegsIn = (PRTSGSEG)RTMemAlloc(VIRTQ_MAX_SIZE * sizeof(RTSGSEG));599 PVIRTIOSGSEG paSegsIn = (PVIRTIOSGSEG)RTMemAlloc(VIRTQ_MAX_SIZE * sizeof(VIRTIOSGSEG)); 494 600 AssertReturn(paSegsIn, VERR_NO_MEMORY); 495 601 496 P RTSGSEG paSegsOut = (PRTSGSEG)RTMemAlloc(VIRTQ_MAX_SIZE * sizeof(RTSGSEG));602 PVIRTIOSGSEG paSegsOut = (PVIRTIOSGSEG)RTMemAlloc(VIRTQ_MAX_SIZE * sizeof(VIRTIOSGSEG)); 497 603 AssertReturn(paSegsOut, VERR_NO_MEMORY); 498 604 … … 517 623 do 518 624 { 519 RTSGSEG *pSeg;625 PVIRTIOSGSEG pSeg; 520 626 521 627 /* … … 555 661 } 556 662 557 pSeg->p vSeg = (void *)desc.GCPhysBuf;663 pSeg->pGcSeg = desc.GCPhysBuf; 558 664 pSeg->cbSeg = desc.cb; 559 665 … … 561 667 } while (desc.fFlags & VIRTQ_DESC_F_NEXT); 562 668 563 P RTSGBUF pSgPhysIn = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF));669 PVIRTIOSGBUF pSgPhysIn = (PVIRTIOSGBUF)RTMemAllocZ(sizeof(VIRTIOSGBUF)); 564 670 AssertReturn(pSgPhysIn, VERR_NO_MEMORY); 565 671 566 RTSgBufInit(pSgPhysIn, (PCRTSGSEG)paSegsIn, cSegsIn);567 568 P RTSGBUF pSgPhysOut = (PRTSGBUF)RTMemAllocZ(sizeof(RTSGBUF));672 virtioCoreSgBufInit(pSgPhysIn, (PCVIRTIOSGSEG)paSegsIn, cSegsIn); 673 674 PVIRTIOSGBUF pSgPhysOut = (PVIRTIOSGBUF)RTMemAllocZ(sizeof(VIRTIOSGBUF)); 569 675 AssertReturn(pSgPhysOut, VERR_NO_MEMORY); 570 676 571 RTSgBufInit(pSgPhysOut, (PCRTSGSEG)paSegsOut, cSegsOut);677 virtioCoreSgBufInit(pSgPhysOut, (PCVIRTIOSGSEG)paSegsOut, cSegsOut); 572 678 573 679 PVIRTIO_DESC_CHAIN_T pDescChain = (PVIRTIO_DESC_CHAIN_T)RTMemAllocZ(sizeof(VIRTIO_DESC_CHAIN_T)); 574 680 AssertReturn(pDescChain, VERR_NO_MEMORY); 575 681 576 pDescChain->uHeadIdx = uHeadIdx;577 pDescChain->cbPhysSend = cbOut;578 pDescChain->pSgPhysSend = pSgPhysOut;682 pDescChain->uHeadIdx = uHeadIdx; 683 pDescChain->cbPhysSend = cbOut; 684 pDescChain->pSgPhysSend = pSgPhysOut; 579 685 pDescChain->cbPhysReturn = cbIn; 580 686 pDescChain->pSgPhysReturn = pSgPhysIn; … … 622 728 Assert(idxQueue < RT_ELEMENTS(pVirtio->virtqState)); 623 729 PVIRTQSTATE pVirtq = &pVirtio->virtqState[idxQueue]; 624 P RTSGBUF pSgPhysReturn = pDescChain->pSgPhysReturn;730 PVIRTIOSGBUF pSgPhysReturn = pDescChain->pSgPhysReturn; 625 731 626 732 AssertMsgReturn(IS_DRIVER_OK(pVirtio) /*&& pVirtio->uQueueEnable[idxQueue]*/, … … 638 744 size_t cbCopy = 0; 639 745 size_t cbRemain = RTSgBufCalcTotalLength(pSgVirtReturn); 640 RTSgBufReset(pSgPhysReturn); /* Reset ptr because req data may have already been written */746 virtioCoreSgBufReset(pSgPhysReturn); /* Reset ptr because req data may have already been written */ 641 747 while (cbRemain) 642 748 { 643 PC RTSGSEG paSeg = &pSgPhysReturn->paSegs[pSgPhysReturn->idxSeg];644 uint64_t dstSgStart = (uint64_t)paSeg->p vSeg;749 PCVIRTIOSGSEG paSeg = &pSgPhysReturn->paSegs[pSgPhysReturn->idxSeg]; 750 uint64_t dstSgStart = (uint64_t)paSeg->pGcSeg; 645 751 uint64_t dstSgLen = (uint64_t)paSeg->cbSeg; 646 uint64_t dstSgCur = (uint64_t)pSgPhysReturn->p vSegCur;752 uint64_t dstSgCur = (uint64_t)pSgPhysReturn->pGcSegCur; 647 753 cbCopy = RT_MIN((uint64_t)pSgVirtReturn->cbSegLeft, dstSgLen - (dstSgCur - dstSgStart)); 648 PDMDevHlpPhysWrite(pDevIns, (RTGCPHYS)pSgPhysReturn->p vSegCur, pSgVirtReturn->pvSegCur, cbCopy);754 PDMDevHlpPhysWrite(pDevIns, (RTGCPHYS)pSgPhysReturn->pGcSegCur, pSgVirtReturn->pvSegCur, cbCopy); 649 755 RTSgBufAdvance(pSgVirtReturn, cbCopy); 650 RTSgBufAdvance(pSgPhysReturn, cbCopy);756 virtioCoreSgBufAdvance(pSgPhysReturn, cbCopy); 651 757 cbRemain -= cbCopy; 652 758 } … … 688 794 * current write-head index, thus exposing the data added to the used ring by all 689 795 * virtioCoreR3QueuePut() calls since the last sync. This should be called after one or 690 * more virt QueuePut() calls to inform the guest driver there is data in the queue.796 * more virtioCoreR3QueuePut() calls to inform the guest driver there is data in the queue. 691 797 * Explicit notifications (e.g. interrupt or MSI-X) will be sent to the guest, 692 798 * depending on VirtIO features negotiated and conditions, otherwise the guest … … 884 990 #endif 885 991 886 #if 0 /** @todo r=bird: This isn't invoked by anyone. Why? */ 992 #if 0 /** @todo r=bird: This isn't invoked by anyone. Why? 993 For this and the previous I was just trying to provide flexibility 994 for other devices that might use this code r=paul */ 887 995 /** 888 996 * Initiate orderly reset procedure. … … 1700 1808 pCfg->uBar = VIRTIO_REGION_PCI_CAP; 1701 1809 pCfg->uOffset = pVirtioCC->pCommonCfgCap->uOffset + pVirtioCC->pCommonCfgCap->uLength; 1702 pCfg->uOffset = RT_ALIGN_32(pCfg->uOffset, 2); /** @todo r=bird: Why is this word aligned rather than dword? If there is a1703 * theoretical chance we won't allways be on a dword boundrary here, the 1704 * read/write really will need to handle cross capability reads. */ 1810 pCfg->uOffset = RT_ALIGN_32(pCfg->uOffset, 4); 1811 1812 1705 1813 pCfg->uLength = VIRTQ_MAX_CNT * VIRTIO_NOTIFY_OFFSET_MULTIPLIER + 2; /* will change in VirtIO 1.1 */ 1706 1814 cbRegion += pCfg->uLength; … … 1721 1829 pCfg->uCapNext = CFG_ADDR_2_IDX(pCfg) + pCfg->uCapLen; 1722 1830 pCfg->uBar = VIRTIO_REGION_PCI_CAP; 1723 pCfg->uOffset = pVirtioCC->pNotifyCap->pciCap.uOffset + pVirtioCC->pNotifyCap->pciCap.uLength; /** @todo r=bird: This probably is _not_ dword aligned, given that the previous structure is 0x32 (50) bytes long. */ 1831 pCfg->uOffset = pVirtioCC->pNotifyCap->pciCap.uOffset + pVirtioCC->pNotifyCap->pciCap.uLength; 1832 pCfg->uOffset = RT_ALIGN_32(pCfg->uOffset, 4); 1724 1833 pCfg->uLength = sizeof(uint8_t); 1725 1834 cbRegion += pCfg->uLength; -
trunk/src/VBox/Devices/VirtIO/Virtio_1_0.h
r81678 r81814 60 60 #endif 61 61 62 63 /** 64 * The following structure holds the pre-processed context of descriptor chain pulled from a virtio queue 65 * to conduct a transaction between the client of this virtio implementation and the guest VM's virtio driver. 66 * It contains the head index of the descriptor chain, the output data from the client that has been 67 * converted to a contiguous virtual memory and a physical memory scatter-gather buffer for use by by 68 * the virtio framework to complete the transaction in the final phase of round-trip processing. 69 * 70 * The client should not modify the contents of this buffer. The primary field of interest to the 71 * client is pVirtSrc, which contains the VirtIO "OUT" (to device) buffer from the guest. 72 * 73 * Typical use is, When the client (worker thread) detects available data on the queue, it pulls the 74 * next one of these descriptor chain structs off the queue using virtioCoreR3QueueGet(), processes the 75 * virtual memory buffer pVirtSrc, produces result data to pass back to the guest driver and calls 76 * virtioCoreR3QueuePut() to return the result data to the client. 77 */ 62 typedef struct VIRTIOSGSEG /**< An S/G entry */ 63 { 64 RTGCPHYS pGcSeg; /**< Pointer to the segment buffer */ 65 size_t cbSeg; /**< Size of the segment buffer */ 66 } VIRTIOSGSEG; 67 68 typedef VIRTIOSGSEG *PVIRTIOSGSEG; 69 typedef const VIRTIOSGSEG *PCVIRTIOSGSEG; 70 typedef PVIRTIOSGSEG *PPVIRTIOSGSEG; 71 72 typedef struct VIRTIOSGBUF 73 { 74 PCVIRTIOSGSEG paSegs; /**< Pointer to the scatter/gather array */ 75 unsigned cSegs; /**< Number of segments */ 76 unsigned idxSeg; /**< Current segment we are in */ 77 RTGCPHYS pGcSegCur; /**< Ptr to byte within the current seg */ 78 size_t cbSegLeft; /**< # of bytes left in the current segment */ 79 } VIRTIOSGBUF; 80 81 typedef VIRTIOSGBUF *PVIRTIOSGBUF; 82 typedef const VIRTIOSGBUF *PCVIRTIOSGBUF; 83 typedef PVIRTIOSGBUF *PPVIRTIOSGBUF; 84 78 85 typedef struct VIRTIO_DESC_CHAIN 79 86 { 80 uint32_t uHeadIdx;/**< Head idx of associated desc chain */81 uint32_t cbPhysSend;/**< Total size of src buffer */82 P RTSGBUF pSgPhysSend;/**< Phys S/G/ buf for data from guest */83 uint32_t cbPhysReturn;/**< Total size of dst buffer */84 P RTSGBUF pSgPhysReturn;/**< Phys S/G buf to store result for guest */87 uint32_t uHeadIdx; /**< Head idx of associated desc chain */ 88 uint32_t cbPhysSend; /**< Total size of src buffer */ 89 PVIRTIOSGBUF pSgPhysSend; /**< Phys S/G/ buf for data from guest */ 90 uint32_t cbPhysReturn; /**< Total size of dst buffer */ 91 PVIRTIOSGBUF pSgPhysReturn; /**< Phys S/G buf to store result for guest */ 85 92 } VIRTIO_DESC_CHAIN_T, *PVIRTIO_DESC_CHAIN_T, **PPVIRTIO_DESC_CHAIN_T; 86 93 87 /**88 * The following structure is used to pass the PCI parameters from the consumer89 * to this generic VirtIO framework. This framework provides the Vendor ID as Virtio.90 */91 94 typedef struct VIRTIOPCIPARAMS 92 95 { … … 96 99 uint16_t uClassProg; /**< PCI Cfg Programming Interface Class */ 97 100 uint16_t uSubsystemId; /**< PCI Cfg Card Manufacturer Vendor ID */ 98 uint16_t uSubsystemVendorId; /**< PCI Cfg Chipset Manufacturer Vendor ID */99 uint16_t uRevisionId; /**< PCI Cfg Revision ID */100 101 uint16_t uInterruptLine; /**< PCI Cfg Interrupt line */ 101 102 uint16_t uInterruptPin; /**< PCI Cfg Interrupt pin */ 102 103 } VIRTIOPCIPARAMS, *PVIRTIOPCIPARAMS; 103 104 104 105 105 #define VIRTIO_F_VERSION_1 RT_BIT_64(32) /**< Required feature bit for 1.0 devices */ … … 388 388 389 389 /** 390 * Reset the device and driver (see VirtIO 1.0 section 2.1.1/2.1.2) 391 * 392 * @param pVirtio Pointer to the virtio state. 393 */ 394 void virtioCoreResetAll(PVIRTIOCORE pVirtio); 395 396 /** 390 397 * Return queue enable state 391 398 * … … 437 444 } 438 445 446 DECLINLINE(size_t) virtioCoreSgBufCalcTotalLength(PCVIRTIOSGBUF pGcSgBuf) 447 { 448 size_t cb = 0; 449 unsigned i = pGcSgBuf->cSegs; 450 while (i-- > 0) 451 cb += pGcSgBuf->paSegs[i].cbSeg; 452 return cb; 453 } 439 454 440 455 void virtioCoreLogMappedIoValue(const char *pszFunc, const char *pszMember, uint32_t uMemberSize, 441 456 const void *pv, uint32_t cb, uint32_t uOffset, 442 457 int fWrite, int fHasIndex, uint32_t idx); 458 443 459 void virtioCoreHexDump(uint8_t *pv, uint32_t cb, uint32_t uBase, const char *pszTitle); 444 460 445 446 int virtioCoreR3SaveExec(PVIRTIOCORE pVirtio, PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM); 447 int virtioCoreR3LoadExec(PVIRTIOCORE pVirtio, PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM); 448 #if 0 /** @todo unused */ 449 void virtioCoreResetAll(PVIRTIOCORE pVirtio); 450 #endif 451 void virtioCoreR3PropagateResetNotification(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio); 452 void virtioCoreR3PropagateResumeNotification(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio); 453 void virtioCoreR3Term(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC); 454 int virtioCoreR3Init(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC, PVIRTIOPCIPARAMS pPciParams, 461 RTGCPHYS virtioCoreSgBufGetNextSegment(PVIRTIOSGBUF pSgBuf, size_t *pcbSeg); 462 RTGCPHYS virtioCoreSgBufAdvance(PVIRTIOSGBUF pSgBuf, size_t cbAdvance); 463 void virtioCoreSgBufInit(PVIRTIOSGBUF pSgBuf, PVIRTIOSGSEG paSegs, size_t cSegs); 464 size_t virtioCoreSgBufCalcTotalLength(PCVIRTIOSGBUF pGcSgBuf); 465 void virtioCoreSgBufReset(PVIRTIOSGBUF pGcSgBuf); 466 int virtioCoreR3SaveExec(PVIRTIOCORE pVirtio, PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM); 467 int virtioCoreR3LoadExec(PVIRTIOCORE pVirtio, PCPDMDEVHLPR3 pHlp, PSSMHANDLE pSSM); 468 void virtioCoreR3PropagateResetNotification(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio); 469 void virtioCoreR3PropagateResumeNotification(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio); 470 void virtioCoreR3Term(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC); 471 int virtioCoreR3Init(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC, PVIRTIOPCIPARAMS pPciParams, 455 472 const char *pcszInstance, uint64_t fDevSpecificFeatures, void *pvDevSpecificCfg, uint16_t cbDevSpecificCfg); 456 int virtioCoreRZInit(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC);473 int virtioCoreRZInit(PPDMDEVINS pDevIns, PVIRTIOCORE pVirtio, PVIRTIOCORECC pVirtioCC); 457 474 /** @} */ 458 475
Note:
See TracChangeset
for help on using the changeset viewer.