VirtualBox

source: vbox/trunk/src/VBox/VMM/VMMR3/PDMBlkCache.cpp@ 48568

Last change on this file since 48568 was 46493, checked in by vboxsync, 11 years ago

STAMR3Deregister* optimizations. Relevant for both startup and shutdown times.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 98.4 KB
Line 
1/* $Id: PDMBlkCache.cpp 46493 2013-06-11 13:34:40Z vboxsync $ */
2/** @file
3 * PDM Block Cache.
4 */
5
6/*
7 * Copyright (C) 2006-2012 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_pdm_block_cache PDM Block Cache - The I/O cache
19 * This component implements an I/O cache based on the 2Q cache algorithm.
20 */
21
22/*******************************************************************************
23* Header Files *
24*******************************************************************************/
25#define LOG_GROUP LOG_GROUP_PDM_BLK_CACHE
26#include "PDMInternal.h"
27#include <iprt/asm.h>
28#include <iprt/mem.h>
29#include <iprt/path.h>
30#include <iprt/string.h>
31#include <VBox/log.h>
32#include <VBox/vmm/stam.h>
33#include <VBox/vmm/uvm.h>
34#include <VBox/vmm/vm.h>
35
36#include "PDMBlkCacheInternal.h"
37
38#ifdef VBOX_STRICT
39# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) \
40 do \
41 { \
42 AssertMsg(RTCritSectIsOwner(&Cache->CritSect), \
43 ("Thread does not own critical section\n"));\
44 } while(0)
45
46# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) \
47 do \
48 { \
49 AssertMsg(RTSemRWIsWriteOwner(pEpCache->SemRWEntries), \
50 ("Thread is not exclusive owner of the per endpoint RW semaphore\n")); \
51 } while(0)
52
53# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) \
54 do \
55 { \
56 AssertMsg(RTSemRWIsReadOwner(pEpCache->SemRWEntries), \
57 ("Thread is not read owner of the per endpoint RW semaphore\n")); \
58 } while(0)
59
60#else
61# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) do { } while(0)
62# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) do { } while(0)
63# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) do { } while(0)
64#endif
65
66#define PDM_BLK_CACHE_SAVED_STATE_VERSION 1
67
68/*******************************************************************************
69* Internal Functions *
70*******************************************************************************/
71
72static PPDMBLKCACHEENTRY pdmBlkCacheEntryAlloc(PPDMBLKCACHE pBlkCache,
73 uint64_t off, size_t cbData, uint8_t *pbBuffer);
74static bool pdmBlkCacheAddDirtyEntry(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEENTRY pEntry);
75
76/**
77 * Decrement the reference counter of the given cache entry.
78 *
79 * @returns nothing.
80 * @param pEntry The entry to release.
81 */
82DECLINLINE(void) pdmBlkCacheEntryRelease(PPDMBLKCACHEENTRY pEntry)
83{
84 AssertMsg(pEntry->cRefs > 0, ("Trying to release a not referenced entry\n"));
85 ASMAtomicDecU32(&pEntry->cRefs);
86}
87
88/**
89 * Increment the reference counter of the given cache entry.
90 *
91 * @returns nothing.
92 * @param pEntry The entry to reference.
93 */
94DECLINLINE(void) pdmBlkCacheEntryRef(PPDMBLKCACHEENTRY pEntry)
95{
96 ASMAtomicIncU32(&pEntry->cRefs);
97}
98
99#ifdef VBOX_STRICT
100static void pdmBlkCacheValidate(PPDMBLKCACHEGLOBAL pCache)
101{
102 /* Amount of cached data should never exceed the maximum amount. */
103 AssertMsg(pCache->cbCached <= pCache->cbMax,
104 ("Current amount of cached data exceeds maximum\n"));
105
106 /* The amount of cached data in the LRU and FRU list should match cbCached */
107 AssertMsg(pCache->LruRecentlyUsedIn.cbCached + pCache->LruFrequentlyUsed.cbCached == pCache->cbCached,
108 ("Amount of cached data doesn't match\n"));
109
110 AssertMsg(pCache->LruRecentlyUsedOut.cbCached <= pCache->cbRecentlyUsedOutMax,
111 ("Paged out list exceeds maximum\n"));
112}
113#endif
114
115DECLINLINE(void) pdmBlkCacheLockEnter(PPDMBLKCACHEGLOBAL pCache)
116{
117 RTCritSectEnter(&pCache->CritSect);
118#ifdef VBOX_STRICT
119 pdmBlkCacheValidate(pCache);
120#endif
121}
122
123DECLINLINE(void) pdmBlkCacheLockLeave(PPDMBLKCACHEGLOBAL pCache)
124{
125#ifdef VBOX_STRICT
126 pdmBlkCacheValidate(pCache);
127#endif
128 RTCritSectLeave(&pCache->CritSect);
129}
130
131DECLINLINE(void) pdmBlkCacheSub(PPDMBLKCACHEGLOBAL pCache, uint32_t cbAmount)
132{
133 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
134 pCache->cbCached -= cbAmount;
135}
136
137DECLINLINE(void) pdmBlkCacheAdd(PPDMBLKCACHEGLOBAL pCache, uint32_t cbAmount)
138{
139 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
140 pCache->cbCached += cbAmount;
141}
142
143DECLINLINE(void) pdmBlkCacheListAdd(PPDMBLKLRULIST pList, uint32_t cbAmount)
144{
145 pList->cbCached += cbAmount;
146}
147
148DECLINLINE(void) pdmBlkCacheListSub(PPDMBLKLRULIST pList, uint32_t cbAmount)
149{
150 pList->cbCached -= cbAmount;
151}
152
153#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
154/**
155 * Checks consistency of a LRU list.
156 *
157 * @returns nothing
158 * @param pList The LRU list to check.
159 * @param pNotInList Element which is not allowed to occur in the list.
160 */
161static void pdmBlkCacheCheckList(PPDMBLKLRULIST pList, PPDMBLKCACHEENTRY pNotInList)
162{
163 PPDMBLKCACHEENTRY pCurr = pList->pHead;
164
165 /* Check that there are no double entries and no cycles in the list. */
166 while (pCurr)
167 {
168 PPDMBLKCACHEENTRY pNext = pCurr->pNext;
169
170 while (pNext)
171 {
172 AssertMsg(pCurr != pNext,
173 ("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
174 pCurr, pList));
175 pNext = pNext->pNext;
176 }
177
178 AssertMsg(pCurr != pNotInList, ("Not allowed entry %#p is in list\n", pCurr));
179
180 if (!pCurr->pNext)
181 AssertMsg(pCurr == pList->pTail, ("End of list reached but last element is not list tail\n"));
182
183 pCurr = pCurr->pNext;
184 }
185}
186#endif
187
188/**
189 * Unlinks a cache entry from the LRU list it is assigned to.
190 *
191 * @returns nothing.
192 * @param pEntry The entry to unlink.
193 */
194static void pdmBlkCacheEntryRemoveFromList(PPDMBLKCACHEENTRY pEntry)
195{
196 PPDMBLKLRULIST pList = pEntry->pList;
197 PPDMBLKCACHEENTRY pPrev, pNext;
198
199 LogFlowFunc((": Deleting entry %#p from list %#p\n", pEntry, pList));
200
201 AssertPtr(pList);
202
203#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
204 pdmBlkCacheCheckList(pList, NULL);
205#endif
206
207 pPrev = pEntry->pPrev;
208 pNext = pEntry->pNext;
209
210 AssertMsg(pEntry != pPrev, ("Entry links to itself as previous element\n"));
211 AssertMsg(pEntry != pNext, ("Entry links to itself as next element\n"));
212
213 if (pPrev)
214 pPrev->pNext = pNext;
215 else
216 {
217 pList->pHead = pNext;
218
219 if (pNext)
220 pNext->pPrev = NULL;
221 }
222
223 if (pNext)
224 pNext->pPrev = pPrev;
225 else
226 {
227 pList->pTail = pPrev;
228
229 if (pPrev)
230 pPrev->pNext = NULL;
231 }
232
233 pEntry->pList = NULL;
234 pEntry->pPrev = NULL;
235 pEntry->pNext = NULL;
236 pdmBlkCacheListSub(pList, pEntry->cbData);
237#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
238 pdmBlkCacheCheckList(pList, pEntry);
239#endif
240}
241
242/**
243 * Adds a cache entry to the given LRU list unlinking it from the currently
244 * assigned list if needed.
245 *
246 * @returns nothing.
247 * @param pList List to the add entry to.
248 * @param pEntry Entry to add.
249 */
250static void pdmBlkCacheEntryAddToList(PPDMBLKLRULIST pList, PPDMBLKCACHEENTRY pEntry)
251{
252 LogFlowFunc((": Adding entry %#p to list %#p\n", pEntry, pList));
253#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
254 pdmBlkCacheCheckList(pList, NULL);
255#endif
256
257 /* Remove from old list if needed */
258 if (pEntry->pList)
259 pdmBlkCacheEntryRemoveFromList(pEntry);
260
261 pEntry->pNext = pList->pHead;
262 if (pList->pHead)
263 pList->pHead->pPrev = pEntry;
264 else
265 {
266 Assert(!pList->pTail);
267 pList->pTail = pEntry;
268 }
269
270 pEntry->pPrev = NULL;
271 pList->pHead = pEntry;
272 pdmBlkCacheListAdd(pList, pEntry->cbData);
273 pEntry->pList = pList;
274#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
275 pdmBlkCacheCheckList(pList, NULL);
276#endif
277}
278
279/**
280 * Destroys a LRU list freeing all entries.
281 *
282 * @returns nothing
283 * @param pList Pointer to the LRU list to destroy.
284 *
285 * @note The caller must own the critical section of the cache.
286 */
287static void pdmBlkCacheDestroyList(PPDMBLKLRULIST pList)
288{
289 while (pList->pHead)
290 {
291 PPDMBLKCACHEENTRY pEntry = pList->pHead;
292
293 pList->pHead = pEntry->pNext;
294
295 AssertMsg(!(pEntry->fFlags & (PDMBLKCACHE_ENTRY_IO_IN_PROGRESS | PDMBLKCACHE_ENTRY_IS_DIRTY)),
296 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
297
298 RTMemPageFree(pEntry->pbData, pEntry->cbData);
299 RTMemFree(pEntry);
300 }
301}
302
303/**
304 * Tries to remove the given amount of bytes from a given list in the cache
305 * moving the entries to one of the given ghosts lists
306 *
307 * @returns Amount of data which could be freed.
308 * @param pCache Pointer to the global cache data.
309 * @param cbData The amount of the data to free.
310 * @param pListSrc The source list to evict data from.
311 * @param pGhostListSrc The ghost list removed entries should be moved to
312 * NULL if the entry should be freed.
313 * @param fReuseBuffer Flag whether a buffer should be reused if it has the same size
314 * @param ppbBuf Where to store the address of the buffer if an entry with the
315 * same size was found and fReuseBuffer is true.
316 *
317 * @note This function may return fewer bytes than requested because entries
318 * may be marked as non evictable if they are used for I/O at the
319 * moment.
320 */
321static size_t pdmBlkCacheEvictPagesFrom(PPDMBLKCACHEGLOBAL pCache, size_t cbData,
322 PPDMBLKLRULIST pListSrc, PPDMBLKLRULIST pGhostListDst,
323 bool fReuseBuffer, uint8_t **ppbBuffer)
324{
325 size_t cbEvicted = 0;
326
327 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
328
329 AssertMsg(cbData > 0, ("Evicting 0 bytes not possible\n"));
330 AssertMsg( !pGhostListDst
331 || (pGhostListDst == &pCache->LruRecentlyUsedOut),
332 ("Destination list must be NULL or the recently used but paged out list\n"));
333
334 if (fReuseBuffer)
335 {
336 AssertPtr(ppbBuffer);
337 *ppbBuffer = NULL;
338 }
339
340 /* Start deleting from the tail. */
341 PPDMBLKCACHEENTRY pEntry = pListSrc->pTail;
342
343 while ((cbEvicted < cbData) && pEntry)
344 {
345 PPDMBLKCACHEENTRY pCurr = pEntry;
346
347 pEntry = pEntry->pPrev;
348
349 /* We can't evict pages which are currently in progress or dirty but not in progress */
350 if ( !(pCurr->fFlags & PDMBLKCACHE_NOT_EVICTABLE)
351 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
352 {
353 /* Ok eviction candidate. Grab the endpoint semaphore and check again
354 * because somebody else might have raced us. */
355 PPDMBLKCACHE pBlkCache = pCurr->pBlkCache;
356 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
357
358 if (!(pCurr->fFlags & PDMBLKCACHE_NOT_EVICTABLE)
359 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
360 {
361 LogFlow(("Evicting entry %#p (%u bytes)\n", pCurr, pCurr->cbData));
362
363 if (fReuseBuffer && pCurr->cbData == cbData)
364 {
365 STAM_COUNTER_INC(&pCache->StatBuffersReused);
366 *ppbBuffer = pCurr->pbData;
367 }
368 else if (pCurr->pbData)
369 RTMemPageFree(pCurr->pbData, pCurr->cbData);
370
371 pCurr->pbData = NULL;
372 cbEvicted += pCurr->cbData;
373
374 pdmBlkCacheEntryRemoveFromList(pCurr);
375 pdmBlkCacheSub(pCache, pCurr->cbData);
376
377 if (pGhostListDst)
378 {
379 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
380
381 PPDMBLKCACHEENTRY pGhostEntFree = pGhostListDst->pTail;
382
383 /* We have to remove the last entries from the paged out list. */
384 while ( pGhostListDst->cbCached + pCurr->cbData > pCache->cbRecentlyUsedOutMax
385 && pGhostEntFree)
386 {
387 PPDMBLKCACHEENTRY pFree = pGhostEntFree;
388 PPDMBLKCACHE pBlkCacheFree = pFree->pBlkCache;
389
390 pGhostEntFree = pGhostEntFree->pPrev;
391
392 RTSemRWRequestWrite(pBlkCacheFree->SemRWEntries, RT_INDEFINITE_WAIT);
393
394 if (ASMAtomicReadU32(&pFree->cRefs) == 0)
395 {
396 pdmBlkCacheEntryRemoveFromList(pFree);
397
398 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
399 RTAvlrU64Remove(pBlkCacheFree->pTree, pFree->Core.Key);
400 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
401
402 RTMemFree(pFree);
403 }
404
405 RTSemRWReleaseWrite(pBlkCacheFree->SemRWEntries);
406 }
407
408 if (pGhostListDst->cbCached + pCurr->cbData > pCache->cbRecentlyUsedOutMax)
409 {
410 /* Couldn't remove enough entries. Delete */
411 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
412 RTAvlrU64Remove(pCurr->pBlkCache->pTree, pCurr->Core.Key);
413 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
414
415 RTMemFree(pCurr);
416 }
417 else
418 pdmBlkCacheEntryAddToList(pGhostListDst, pCurr);
419 }
420 else
421 {
422 /* Delete the entry from the AVL tree it is assigned to. */
423 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
424 RTAvlrU64Remove(pCurr->pBlkCache->pTree, pCurr->Core.Key);
425 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
426
427 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
428 RTMemFree(pCurr);
429 }
430 }
431
432 }
433 else
434 LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
435 }
436
437 return cbEvicted;
438}
439
440static bool pdmBlkCacheReclaim(PPDMBLKCACHEGLOBAL pCache, size_t cbData, bool fReuseBuffer, uint8_t **ppbBuffer)
441{
442 size_t cbRemoved = 0;
443
444 if ((pCache->cbCached + cbData) < pCache->cbMax)
445 return true;
446 else if ((pCache->LruRecentlyUsedIn.cbCached + cbData) > pCache->cbRecentlyUsedInMax)
447 {
448 /* Try to evict as many bytes as possible from A1in */
449 cbRemoved = pdmBlkCacheEvictPagesFrom(pCache, cbData, &pCache->LruRecentlyUsedIn,
450 &pCache->LruRecentlyUsedOut, fReuseBuffer, ppbBuffer);
451
452 /*
453 * If it was not possible to remove enough entries
454 * try the frequently accessed cache.
455 */
456 if (cbRemoved < cbData)
457 {
458 Assert(!fReuseBuffer || !*ppbBuffer); /* It is not possible that we got a buffer with the correct size but we didn't freed enough data. */
459
460 /*
461 * If we removed something we can't pass the reuse buffer flag anymore because
462 * we don't need to evict that much data
463 */
464 if (!cbRemoved)
465 cbRemoved += pdmBlkCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
466 NULL, fReuseBuffer, ppbBuffer);
467 else
468 cbRemoved += pdmBlkCacheEvictPagesFrom(pCache, cbData - cbRemoved, &pCache->LruFrequentlyUsed,
469 NULL, false, NULL);
470 }
471 }
472 else
473 {
474 /* We have to remove entries from frequently access list. */
475 cbRemoved = pdmBlkCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
476 NULL, fReuseBuffer, ppbBuffer);
477 }
478
479 LogFlowFunc((": removed %u bytes, requested %u\n", cbRemoved, cbData));
480 return (cbRemoved >= cbData);
481}
482
483DECLINLINE(int) pdmBlkCacheEnqueue(PPDMBLKCACHE pBlkCache, uint64_t off, size_t cbXfer, PPDMBLKCACHEIOXFER pIoXfer)
484{
485 int rc = VINF_SUCCESS;
486
487 LogFlowFunc(("%s: Enqueuing hIoXfer=%#p enmXferDir=%d\n",
488 __FUNCTION__, pIoXfer, pIoXfer->enmXferDir));
489
490 switch (pBlkCache->enmType)
491 {
492 case PDMBLKCACHETYPE_DEV:
493 {
494 rc = pBlkCache->u.Dev.pfnXferEnqueue(pBlkCache->u.Dev.pDevIns,
495 pIoXfer->enmXferDir,
496 off, cbXfer,
497 &pIoXfer->SgBuf, pIoXfer);
498 break;
499 }
500 case PDMBLKCACHETYPE_DRV:
501 {
502 rc = pBlkCache->u.Drv.pfnXferEnqueue(pBlkCache->u.Drv.pDrvIns,
503 pIoXfer->enmXferDir,
504 off, cbXfer,
505 &pIoXfer->SgBuf, pIoXfer);
506 break;
507 }
508 case PDMBLKCACHETYPE_USB:
509 {
510 rc = pBlkCache->u.Usb.pfnXferEnqueue(pBlkCache->u.Usb.pUsbIns,
511 pIoXfer->enmXferDir,
512 off, cbXfer,
513 &pIoXfer->SgBuf, pIoXfer);
514 break;
515 }
516 case PDMBLKCACHETYPE_INTERNAL:
517 {
518 rc = pBlkCache->u.Int.pfnXferEnqueue(pBlkCache->u.Int.pvUser,
519 pIoXfer->enmXferDir,
520 off, cbXfer,
521 &pIoXfer->SgBuf, pIoXfer);
522 break;
523 }
524 default:
525 AssertMsgFailed(("Unknown block cache type!\n"));
526 }
527
528 LogFlowFunc(("%s: returns rc=%Rrc\n", __FUNCTION__, rc));
529 return rc;
530}
531
532/**
533 * Initiates a read I/O task for the given entry.
534 *
535 * @returns VBox status code.
536 * @param pEntry The entry to fetch the data to.
537 */
538static int pdmBlkCacheEntryReadFromMedium(PPDMBLKCACHEENTRY pEntry)
539{
540 PPDMBLKCACHE pBlkCache = pEntry->pBlkCache;
541 LogFlowFunc((": Reading data into cache entry %#p\n", pEntry));
542
543 /* Make sure no one evicts the entry while it is accessed. */
544 pEntry->fFlags |= PDMBLKCACHE_ENTRY_IO_IN_PROGRESS;
545
546 PPDMBLKCACHEIOXFER pIoXfer = (PPDMBLKCACHEIOXFER)RTMemAllocZ(sizeof(PDMBLKCACHEIOXFER));
547 if (RT_UNLIKELY(!pIoXfer))
548 return VERR_NO_MEMORY;
549
550 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
551
552 pIoXfer->fIoCache = true;
553 pIoXfer->pEntry = pEntry;
554 pIoXfer->SgSeg.pvSeg = pEntry->pbData;
555 pIoXfer->SgSeg.cbSeg = pEntry->cbData;
556 pIoXfer->enmXferDir = PDMBLKCACHEXFERDIR_READ;
557 RTSgBufInit(&pIoXfer->SgBuf, &pIoXfer->SgSeg, 1);
558
559 return pdmBlkCacheEnqueue(pBlkCache, pEntry->Core.Key, pEntry->cbData, pIoXfer);
560}
561
562/**
563 * Initiates a write I/O task for the given entry.
564 *
565 * @returns nothing.
566 * @param pEntry The entry to read the data from.
567 */
568static int pdmBlkCacheEntryWriteToMedium(PPDMBLKCACHEENTRY pEntry)
569{
570 PPDMBLKCACHE pBlkCache = pEntry->pBlkCache;
571 LogFlowFunc((": Writing data from cache entry %#p\n", pEntry));
572
573 /* Make sure no one evicts the entry while it is accessed. */
574 pEntry->fFlags |= PDMBLKCACHE_ENTRY_IO_IN_PROGRESS;
575
576 PPDMBLKCACHEIOXFER pIoXfer = (PPDMBLKCACHEIOXFER)RTMemAllocZ(sizeof(PDMBLKCACHEIOXFER));
577 if (RT_UNLIKELY(!pIoXfer))
578 return VERR_NO_MEMORY;
579
580 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
581
582 pIoXfer->fIoCache = true;
583 pIoXfer->pEntry = pEntry;
584 pIoXfer->SgSeg.pvSeg = pEntry->pbData;
585 pIoXfer->SgSeg.cbSeg = pEntry->cbData;
586 pIoXfer->enmXferDir = PDMBLKCACHEXFERDIR_WRITE;
587 RTSgBufInit(&pIoXfer->SgBuf, &pIoXfer->SgSeg, 1);
588
589 return pdmBlkCacheEnqueue(pBlkCache, pEntry->Core.Key, pEntry->cbData, pIoXfer);
590}
591
592/**
593 * Passthrough a part of a request directly to the I/O manager
594 * handling the endpoint.
595 *
596 * @returns VBox status code.
597 * @param pEndpoint The endpoint.
598 * @param pTask The task.
599 * @param pIoMemCtx The I/O memory context to use.
600 * @param offStart Offset to start transfer from.
601 * @param cbData Amount of data to transfer.
602 * @param enmTransferType The transfer type (read/write)
603 */
604static int pdmBlkCacheRequestPassthrough(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEREQ pReq,
605 PRTSGBUF pSgBuf, uint64_t offStart, size_t cbData,
606 PDMBLKCACHEXFERDIR enmXferDir)
607{
608
609 PPDMBLKCACHEIOXFER pIoXfer = (PPDMBLKCACHEIOXFER)RTMemAllocZ(sizeof(PDMBLKCACHEIOXFER));
610 if (RT_UNLIKELY(!pIoXfer))
611 return VERR_NO_MEMORY;
612
613 ASMAtomicIncU32(&pReq->cXfersPending);
614 pIoXfer->fIoCache = false;
615 pIoXfer->pReq = pReq;
616 pIoXfer->enmXferDir = enmXferDir;
617 if (pSgBuf)
618 {
619 RTSgBufClone(&pIoXfer->SgBuf, pSgBuf);
620 RTSgBufAdvance(pSgBuf, cbData);
621 }
622
623 return pdmBlkCacheEnqueue(pBlkCache, offStart, cbData, pIoXfer);
624}
625
626/**
627 * Commit a single dirty entry to the endpoint
628 *
629 * @returns nothing
630 * @param pEntry The entry to commit.
631 */
632static void pdmBlkCacheEntryCommit(PPDMBLKCACHEENTRY pEntry)
633{
634 AssertMsg( (pEntry->fFlags & PDMBLKCACHE_ENTRY_IS_DIRTY)
635 && !(pEntry->fFlags & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS),
636 ("Invalid flags set for entry %#p\n", pEntry));
637
638 pdmBlkCacheEntryWriteToMedium(pEntry);
639}
640
641/**
642 * Commit all dirty entries for a single endpoint.
643 *
644 * @returns nothing.
645 * @param pBlkCache The endpoint cache to commit.
646 */
647static void pdmBlkCacheCommit(PPDMBLKCACHE pBlkCache)
648{
649 uint32_t cbCommitted = 0;
650
651 /* Return if the cache was suspended. */
652 if (pBlkCache->fSuspended)
653 return;
654
655 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
656
657 /* The list is moved to a new header to reduce locking overhead. */
658 RTLISTANCHOR ListDirtyNotCommitted;
659
660 RTListInit(&ListDirtyNotCommitted);
661 RTSpinlockAcquire(pBlkCache->LockList);
662 RTListMove(&ListDirtyNotCommitted, &pBlkCache->ListDirtyNotCommitted);
663 RTSpinlockRelease(pBlkCache->LockList);
664
665 if (!RTListIsEmpty(&ListDirtyNotCommitted))
666 {
667 PPDMBLKCACHEENTRY pEntry = RTListGetFirst(&ListDirtyNotCommitted, PDMBLKCACHEENTRY, NodeNotCommitted);
668
669 while (!RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted))
670 {
671 PPDMBLKCACHEENTRY pNext = RTListNodeGetNext(&pEntry->NodeNotCommitted, PDMBLKCACHEENTRY,
672 NodeNotCommitted);
673 pdmBlkCacheEntryCommit(pEntry);
674 cbCommitted += pEntry->cbData;
675 RTListNodeRemove(&pEntry->NodeNotCommitted);
676 pEntry = pNext;
677 }
678
679 /* Commit the last endpoint */
680 Assert(RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted));
681 pdmBlkCacheEntryCommit(pEntry);
682 cbCommitted += pEntry->cbData;
683 RTListNodeRemove(&pEntry->NodeNotCommitted);
684 AssertMsg(RTListIsEmpty(&ListDirtyNotCommitted),
685 ("Committed all entries but list is not empty\n"));
686 }
687
688 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
689 AssertMsg(pBlkCache->pCache->cbDirty >= cbCommitted,
690 ("Number of committed bytes exceeds number of dirty bytes\n"));
691 uint32_t cbDirtyOld = ASMAtomicSubU32(&pBlkCache->pCache->cbDirty, cbCommitted);
692
693 /* Reset the commit timer if we don't have any dirty bits. */
694 if ( !(cbDirtyOld - cbCommitted)
695 && pBlkCache->pCache->u32CommitTimeoutMs != 0)
696 TMTimerStop(pBlkCache->pCache->pTimerCommit);
697}
698
699/**
700 * Commit all dirty entries in the cache.
701 *
702 * @returns nothing.
703 * @param pCache The global cache instance.
704 */
705static void pdmBlkCacheCommitDirtyEntries(PPDMBLKCACHEGLOBAL pCache)
706{
707 bool fCommitInProgress = ASMAtomicXchgBool(&pCache->fCommitInProgress, true);
708
709 if (!fCommitInProgress)
710 {
711 pdmBlkCacheLockEnter(pCache);
712 Assert(!RTListIsEmpty(&pCache->ListUsers));
713
714 PPDMBLKCACHE pBlkCache = RTListGetFirst(&pCache->ListUsers, PDMBLKCACHE, NodeCacheUser);
715 AssertPtr(pBlkCache);
716
717 while (!RTListNodeIsLast(&pCache->ListUsers, &pBlkCache->NodeCacheUser))
718 {
719 pdmBlkCacheCommit(pBlkCache);
720
721 pBlkCache = RTListNodeGetNext(&pBlkCache->NodeCacheUser, PDMBLKCACHE,
722 NodeCacheUser);
723 }
724
725 /* Commit the last endpoint */
726 Assert(RTListNodeIsLast(&pCache->ListUsers, &pBlkCache->NodeCacheUser));
727 pdmBlkCacheCommit(pBlkCache);
728
729 pdmBlkCacheLockLeave(pCache);
730 ASMAtomicWriteBool(&pCache->fCommitInProgress, false);
731 }
732}
733
734/**
735 * Adds the given entry as a dirty to the cache.
736 *
737 * @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
738 * @param pBlkCache The endpoint cache the entry belongs to.
739 * @param pEntry The entry to add.
740 */
741static bool pdmBlkCacheAddDirtyEntry(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEENTRY pEntry)
742{
743 bool fDirtyBytesExceeded = false;
744 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
745
746 /* If the commit timer is disabled we commit right away. */
747 if (pCache->u32CommitTimeoutMs == 0)
748 {
749 pEntry->fFlags |= PDMBLKCACHE_ENTRY_IS_DIRTY;
750 pdmBlkCacheEntryCommit(pEntry);
751 }
752 else if (!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IS_DIRTY))
753 {
754 pEntry->fFlags |= PDMBLKCACHE_ENTRY_IS_DIRTY;
755
756 RTSpinlockAcquire(pBlkCache->LockList);
757 RTListAppend(&pBlkCache->ListDirtyNotCommitted, &pEntry->NodeNotCommitted);
758 RTSpinlockRelease(pBlkCache->LockList);
759
760 uint32_t cbDirty = ASMAtomicAddU32(&pCache->cbDirty, pEntry->cbData);
761
762 /* Prevent committing if the VM was suspended. */
763 if (RT_LIKELY(!ASMAtomicReadBool(&pCache->fIoErrorVmSuspended)))
764 fDirtyBytesExceeded = (cbDirty + pEntry->cbData >= pCache->cbCommitDirtyThreshold);
765 else if (!cbDirty && pCache->u32CommitTimeoutMs > 0)
766 {
767 /* Arm the commit timer. */
768 TMTimerSetMillies(pCache->pTimerCommit, pCache->u32CommitTimeoutMs);
769 }
770 }
771
772 return fDirtyBytesExceeded;
773}
774
775static PPDMBLKCACHE pdmR3BlkCacheFindById(PPDMBLKCACHEGLOBAL pBlkCacheGlobal, const char *pcszId)
776{
777 bool fFound = false;
778 PPDMBLKCACHE pBlkCache = NULL;
779
780 RTListForEach(&pBlkCacheGlobal->ListUsers, pBlkCache, PDMBLKCACHE, NodeCacheUser)
781 {
782 if (!RTStrCmp(pBlkCache->pszId, pcszId))
783 {
784 fFound = true;
785 break;
786 }
787 }
788
789 return fFound ? pBlkCache : NULL;
790}
791
792/**
793 * Commit timer callback.
794 */
795static DECLCALLBACK(void) pdmBlkCacheCommitTimerCallback(PVM pVM, PTMTIMER pTimer, void *pvUser)
796{
797 PPDMBLKCACHEGLOBAL pCache = (PPDMBLKCACHEGLOBAL)pvUser;
798 NOREF(pVM); NOREF(pTimer);
799
800 LogFlowFunc(("Commit interval expired, commiting dirty entries\n"));
801
802 if ( ASMAtomicReadU32(&pCache->cbDirty) > 0
803 && !ASMAtomicReadBool(&pCache->fIoErrorVmSuspended))
804 pdmBlkCacheCommitDirtyEntries(pCache);
805
806 LogFlowFunc(("Entries committed, going to sleep\n"));
807}
808
809static DECLCALLBACK(int) pdmR3BlkCacheSaveExec(PVM pVM, PSSMHANDLE pSSM)
810{
811 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
812
813 AssertPtr(pBlkCacheGlobal);
814
815 pdmBlkCacheLockEnter(pBlkCacheGlobal);
816
817 SSMR3PutU32(pSSM, pBlkCacheGlobal->cRefs);
818
819 /* Go through the list and save all dirty entries. */
820 PPDMBLKCACHE pBlkCache;
821 RTListForEach(&pBlkCacheGlobal->ListUsers, pBlkCache, PDMBLKCACHE, NodeCacheUser)
822 {
823 uint32_t cEntries = 0;
824 PPDMBLKCACHEENTRY pEntry;
825
826 RTSemRWRequestRead(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
827 SSMR3PutU32(pSSM, (uint32_t)strlen(pBlkCache->pszId));
828 SSMR3PutStrZ(pSSM, pBlkCache->pszId);
829
830 /* Count the number of entries to safe. */
831 RTListForEach(&pBlkCache->ListDirtyNotCommitted, pEntry, PDMBLKCACHEENTRY, NodeNotCommitted)
832 {
833 cEntries++;
834 }
835
836 SSMR3PutU32(pSSM, cEntries);
837
838 /* Walk the list of all dirty entries and save them. */
839 RTListForEach(&pBlkCache->ListDirtyNotCommitted, pEntry, PDMBLKCACHEENTRY, NodeNotCommitted)
840 {
841 /* A few sanity checks. */
842 AssertMsg(!pEntry->cRefs, ("The entry is still referenced\n"));
843 AssertMsg(pEntry->fFlags & PDMBLKCACHE_ENTRY_IS_DIRTY, ("Entry is not dirty\n"));
844 AssertMsg(!(pEntry->fFlags & ~PDMBLKCACHE_ENTRY_IS_DIRTY), ("Invalid flags set\n"));
845 AssertMsg(!pEntry->pWaitingHead && !pEntry->pWaitingTail, ("There are waiting requests\n"));
846 AssertMsg( pEntry->pList == &pBlkCacheGlobal->LruRecentlyUsedIn
847 || pEntry->pList == &pBlkCacheGlobal->LruFrequentlyUsed,
848 ("Invalid list\n"));
849 AssertMsg(pEntry->cbData == pEntry->Core.KeyLast - pEntry->Core.Key + 1,
850 ("Size and range do not match\n"));
851
852 /* Save */
853 SSMR3PutU64(pSSM, pEntry->Core.Key);
854 SSMR3PutU32(pSSM, pEntry->cbData);
855 SSMR3PutMem(pSSM, pEntry->pbData, pEntry->cbData);
856 }
857
858 RTSemRWReleaseRead(pBlkCache->SemRWEntries);
859 }
860
861 pdmBlkCacheLockLeave(pBlkCacheGlobal);
862
863 /* Terminator */
864 return SSMR3PutU32(pSSM, UINT32_MAX);
865}
866
867static DECLCALLBACK(int) pdmR3BlkCacheLoadExec(PVM pVM, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
868{
869 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
870 uint32_t cRefs;
871
872 NOREF(uPass);
873 AssertPtr(pBlkCacheGlobal);
874
875 pdmBlkCacheLockEnter(pBlkCacheGlobal);
876
877 if (uVersion != PDM_BLK_CACHE_SAVED_STATE_VERSION)
878 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
879
880 SSMR3GetU32(pSSM, &cRefs);
881
882 /*
883 * Fewer users in the saved state than in the current VM are allowed
884 * because that means that there are only new ones which don't have any saved state
885 * which can get lost.
886 * More saved state entries than registered cache users are only allowed if the
887 * missing users don't have any data saved in the cache.
888 */
889 int rc = VINF_SUCCESS;
890 char *pszId = NULL;
891
892 while ( cRefs > 0
893 && RT_SUCCESS(rc))
894 {
895 PPDMBLKCACHE pBlkCache = NULL;
896 uint32_t cbId = 0;
897
898 SSMR3GetU32(pSSM, &cbId);
899 Assert(cbId > 0);
900
901 cbId++; /* Include terminator */
902 pszId = (char *)RTMemAllocZ(cbId * sizeof(char));
903 if (!pszId)
904 {
905 rc = VERR_NO_MEMORY;
906 break;
907 }
908
909 rc = SSMR3GetStrZ(pSSM, pszId, cbId);
910 AssertRC(rc);
911
912 /* Search for the block cache with the provided id. */
913 pBlkCache = pdmR3BlkCacheFindById(pBlkCacheGlobal, pszId);
914
915 /* Get the entries */
916 uint32_t cEntries;
917 SSMR3GetU32(pSSM, &cEntries);
918
919 if (!pBlkCache && (cEntries > 0))
920 {
921 rc = SSMR3SetCfgError(pSSM, RT_SRC_POS,
922 N_("The VM is missing a block device and there is data in the cache. Please make sure the source and target VMs have compatible storage configurations"));
923 break;
924 }
925
926 RTStrFree(pszId);
927 pszId = NULL;
928
929 while (cEntries > 0)
930 {
931 PPDMBLKCACHEENTRY pEntry;
932 uint64_t off;
933 uint32_t cbEntry;
934
935 SSMR3GetU64(pSSM, &off);
936 SSMR3GetU32(pSSM, &cbEntry);
937
938 pEntry = pdmBlkCacheEntryAlloc(pBlkCache, off, cbEntry, NULL);
939 if (!pEntry)
940 {
941 rc = VERR_NO_MEMORY;
942 break;
943 }
944
945 rc = SSMR3GetMem(pSSM, pEntry->pbData, cbEntry);
946 if (RT_FAILURE(rc))
947 {
948 RTMemFree(pEntry->pbData);
949 RTMemFree(pEntry);
950 break;
951 }
952
953 /* Insert into the tree. */
954 bool fInserted = RTAvlrU64Insert(pBlkCache->pTree, &pEntry->Core);
955 Assert(fInserted); NOREF(fInserted);
956
957 /* Add to the dirty list. */
958 pdmBlkCacheAddDirtyEntry(pBlkCache, pEntry);
959 pdmBlkCacheEntryAddToList(&pBlkCacheGlobal->LruRecentlyUsedIn, pEntry);
960 pdmBlkCacheAdd(pBlkCacheGlobal, cbEntry);
961 pdmBlkCacheEntryRelease(pEntry);
962 cEntries--;
963 }
964
965 cRefs--;
966 }
967
968 if (pszId)
969 RTStrFree(pszId);
970
971 if (cRefs && RT_SUCCESS(rc))
972 rc = SSMR3SetCfgError(pSSM, RT_SRC_POS,
973 N_("Unexpected error while restoring state. Please make sure the source and target VMs have compatible storage configurations"));
974
975 pdmBlkCacheLockLeave(pBlkCacheGlobal);
976
977 if (RT_SUCCESS(rc))
978 {
979 uint32_t u32 = 0;
980 rc = SSMR3GetU32(pSSM, &u32);
981 if (RT_SUCCESS(rc))
982 AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
983 }
984
985 return rc;
986}
987
988int pdmR3BlkCacheInit(PVM pVM)
989{
990 int rc = VINF_SUCCESS;
991 PUVM pUVM = pVM->pUVM;
992 PPDMBLKCACHEGLOBAL pBlkCacheGlobal;
993
994 LogFlowFunc((": pVM=%p\n", pVM));
995
996 VM_ASSERT_EMT(pVM);
997
998 PCFGMNODE pCfgRoot = CFGMR3GetRoot(pVM);
999 PCFGMNODE pCfgBlkCache = CFGMR3GetChild(CFGMR3GetChild(pCfgRoot, "PDM"), "BlkCache");
1000
1001 pBlkCacheGlobal = (PPDMBLKCACHEGLOBAL)RTMemAllocZ(sizeof(PDMBLKCACHEGLOBAL));
1002 if (!pBlkCacheGlobal)
1003 return VERR_NO_MEMORY;
1004
1005 RTListInit(&pBlkCacheGlobal->ListUsers);
1006 pBlkCacheGlobal->pVM = pVM;
1007 pBlkCacheGlobal->cRefs = 0;
1008 pBlkCacheGlobal->cbCached = 0;
1009 pBlkCacheGlobal->fCommitInProgress = false;
1010
1011 /* Initialize members */
1012 pBlkCacheGlobal->LruRecentlyUsedIn.pHead = NULL;
1013 pBlkCacheGlobal->LruRecentlyUsedIn.pTail = NULL;
1014 pBlkCacheGlobal->LruRecentlyUsedIn.cbCached = 0;
1015
1016 pBlkCacheGlobal->LruRecentlyUsedOut.pHead = NULL;
1017 pBlkCacheGlobal->LruRecentlyUsedOut.pTail = NULL;
1018 pBlkCacheGlobal->LruRecentlyUsedOut.cbCached = 0;
1019
1020 pBlkCacheGlobal->LruFrequentlyUsed.pHead = NULL;
1021 pBlkCacheGlobal->LruFrequentlyUsed.pTail = NULL;
1022 pBlkCacheGlobal->LruFrequentlyUsed.cbCached = 0;
1023
1024 do
1025 {
1026 rc = CFGMR3QueryU32Def(pCfgBlkCache, "CacheSize", &pBlkCacheGlobal->cbMax, 5 * _1M);
1027 AssertLogRelRCBreak(rc);
1028 LogFlowFunc(("Maximum number of bytes cached %u\n", pBlkCacheGlobal->cbMax));
1029
1030 pBlkCacheGlobal->cbRecentlyUsedInMax = (pBlkCacheGlobal->cbMax / 100) * 25; /* 25% of the buffer size */
1031 pBlkCacheGlobal->cbRecentlyUsedOutMax = (pBlkCacheGlobal->cbMax / 100) * 50; /* 50% of the buffer size */
1032 LogFlowFunc(("cbRecentlyUsedInMax=%u cbRecentlyUsedOutMax=%u\n",
1033 pBlkCacheGlobal->cbRecentlyUsedInMax, pBlkCacheGlobal->cbRecentlyUsedOutMax));
1034
1035 /** @todo r=aeichner: Experiment to find optimal default values */
1036 rc = CFGMR3QueryU32Def(pCfgBlkCache, "CacheCommitIntervalMs", &pBlkCacheGlobal->u32CommitTimeoutMs, 10000 /* 10sec */);
1037 AssertLogRelRCBreak(rc);
1038 rc = CFGMR3QueryU32Def(pCfgBlkCache, "CacheCommitThreshold", &pBlkCacheGlobal->cbCommitDirtyThreshold, pBlkCacheGlobal->cbMax / 2);
1039 AssertLogRelRCBreak(rc);
1040 } while (0);
1041
1042 if (RT_SUCCESS(rc))
1043 {
1044 STAMR3Register(pVM, &pBlkCacheGlobal->cbMax,
1045 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
1046 "/PDM/BlkCache/cbMax",
1047 STAMUNIT_BYTES,
1048 "Maximum cache size");
1049 STAMR3Register(pVM, &pBlkCacheGlobal->cbCached,
1050 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
1051 "/PDM/BlkCache/cbCached",
1052 STAMUNIT_BYTES,
1053 "Currently used cache");
1054 STAMR3Register(pVM, &pBlkCacheGlobal->LruRecentlyUsedIn.cbCached,
1055 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
1056 "/PDM/BlkCache/cbCachedMruIn",
1057 STAMUNIT_BYTES,
1058 "Number of bytes cached in MRU list");
1059 STAMR3Register(pVM, &pBlkCacheGlobal->LruRecentlyUsedOut.cbCached,
1060 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
1061 "/PDM/BlkCache/cbCachedMruOut",
1062 STAMUNIT_BYTES,
1063 "Number of bytes cached in FRU list");
1064 STAMR3Register(pVM, &pBlkCacheGlobal->LruFrequentlyUsed.cbCached,
1065 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
1066 "/PDM/BlkCache/cbCachedFru",
1067 STAMUNIT_BYTES,
1068 "Number of bytes cached in FRU ghost list");
1069
1070#ifdef VBOX_WITH_STATISTICS
1071 STAMR3Register(pVM, &pBlkCacheGlobal->cHits,
1072 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1073 "/PDM/BlkCache/CacheHits",
1074 STAMUNIT_COUNT, "Number of hits in the cache");
1075 STAMR3Register(pVM, &pBlkCacheGlobal->cPartialHits,
1076 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1077 "/PDM/BlkCache/CachePartialHits",
1078 STAMUNIT_COUNT, "Number of partial hits in the cache");
1079 STAMR3Register(pVM, &pBlkCacheGlobal->cMisses,
1080 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1081 "/PDM/BlkCache/CacheMisses",
1082 STAMUNIT_COUNT, "Number of misses when accessing the cache");
1083 STAMR3Register(pVM, &pBlkCacheGlobal->StatRead,
1084 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1085 "/PDM/BlkCache/CacheRead",
1086 STAMUNIT_BYTES, "Number of bytes read from the cache");
1087 STAMR3Register(pVM, &pBlkCacheGlobal->StatWritten,
1088 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1089 "/PDM/BlkCache/CacheWritten",
1090 STAMUNIT_BYTES, "Number of bytes written to the cache");
1091 STAMR3Register(pVM, &pBlkCacheGlobal->StatTreeGet,
1092 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1093 "/PDM/BlkCache/CacheTreeGet",
1094 STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
1095 STAMR3Register(pVM, &pBlkCacheGlobal->StatTreeInsert,
1096 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1097 "/PDM/BlkCache/CacheTreeInsert",
1098 STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
1099 STAMR3Register(pVM, &pBlkCacheGlobal->StatTreeRemove,
1100 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1101 "/PDM/BlkCache/CacheTreeRemove",
1102 STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
1103 STAMR3Register(pVM, &pBlkCacheGlobal->StatBuffersReused,
1104 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1105 "/PDM/BlkCache/CacheBuffersReused",
1106 STAMUNIT_COUNT, "Number of times a buffer could be reused");
1107#endif
1108
1109 /* Initialize the critical section */
1110 rc = RTCritSectInit(&pBlkCacheGlobal->CritSect);
1111 }
1112
1113 if (RT_SUCCESS(rc))
1114 {
1115 /* Create the commit timer */
1116 if (pBlkCacheGlobal->u32CommitTimeoutMs > 0)
1117 rc = TMR3TimerCreateInternal(pVM, TMCLOCK_REAL,
1118 pdmBlkCacheCommitTimerCallback,
1119 pBlkCacheGlobal,
1120 "BlkCache-Commit",
1121 &pBlkCacheGlobal->pTimerCommit);
1122
1123 if (RT_SUCCESS(rc))
1124 {
1125 /* Register saved state handler. */
1126 rc = SSMR3RegisterInternal(pVM, "pdmblkcache", 0, PDM_BLK_CACHE_SAVED_STATE_VERSION, pBlkCacheGlobal->cbMax,
1127 NULL, NULL, NULL,
1128 NULL, pdmR3BlkCacheSaveExec, NULL,
1129 NULL, pdmR3BlkCacheLoadExec, NULL);
1130 if (RT_SUCCESS(rc))
1131 {
1132 LogRel(("BlkCache: Cache successfully initialised. Cache size is %u bytes\n", pBlkCacheGlobal->cbMax));
1133 LogRel(("BlkCache: Cache commit interval is %u ms\n", pBlkCacheGlobal->u32CommitTimeoutMs));
1134 LogRel(("BlkCache: Cache commit threshold is %u bytes\n", pBlkCacheGlobal->cbCommitDirtyThreshold));
1135 pUVM->pdm.s.pBlkCacheGlobal = pBlkCacheGlobal;
1136 return VINF_SUCCESS;
1137 }
1138 }
1139
1140 RTCritSectDelete(&pBlkCacheGlobal->CritSect);
1141 }
1142
1143 if (pBlkCacheGlobal)
1144 RTMemFree(pBlkCacheGlobal);
1145
1146 LogFlowFunc((": returns rc=%Rrc\n", pVM, rc));
1147 return rc;
1148}
1149
1150void pdmR3BlkCacheTerm(PVM pVM)
1151{
1152 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1153
1154 if (pBlkCacheGlobal)
1155 {
1156 /* Make sure no one else uses the cache now */
1157 pdmBlkCacheLockEnter(pBlkCacheGlobal);
1158
1159 /* Cleanup deleting all cache entries waiting for in progress entries to finish. */
1160 pdmBlkCacheDestroyList(&pBlkCacheGlobal->LruRecentlyUsedIn);
1161 pdmBlkCacheDestroyList(&pBlkCacheGlobal->LruRecentlyUsedOut);
1162 pdmBlkCacheDestroyList(&pBlkCacheGlobal->LruFrequentlyUsed);
1163
1164 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1165
1166 RTCritSectDelete(&pBlkCacheGlobal->CritSect);
1167 RTMemFree(pBlkCacheGlobal);
1168 pVM->pUVM->pdm.s.pBlkCacheGlobal = NULL;
1169 }
1170}
1171
1172int pdmR3BlkCacheResume(PVM pVM)
1173{
1174 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1175
1176 LogFlowFunc(("pVM=%#p\n", pVM));
1177
1178 if ( pBlkCacheGlobal
1179 && ASMAtomicXchgBool(&pBlkCacheGlobal->fIoErrorVmSuspended, false))
1180 {
1181 /* The VM was suspended because of an I/O error, commit all dirty entries. */
1182 pdmBlkCacheCommitDirtyEntries(pBlkCacheGlobal);
1183 }
1184
1185 return VINF_SUCCESS;
1186}
1187
1188static int pdmR3BlkCacheRetain(PVM pVM, PPPDMBLKCACHE ppBlkCache, const char *pcszId)
1189{
1190 int rc = VINF_SUCCESS;
1191 PPDMBLKCACHE pBlkCache = NULL;
1192 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1193
1194 if (!pBlkCacheGlobal)
1195 return VERR_NOT_SUPPORTED;
1196
1197 /*
1198 * Check that no other user cache has the same id first,
1199 * Unique id's are necessary in case the state is saved.
1200 */
1201 pdmBlkCacheLockEnter(pBlkCacheGlobal);
1202
1203 pBlkCache = pdmR3BlkCacheFindById(pBlkCacheGlobal, pcszId);
1204
1205 if (!pBlkCache)
1206 {
1207 pBlkCache = (PPDMBLKCACHE)RTMemAllocZ(sizeof(PDMBLKCACHE));
1208
1209 if (pBlkCache)
1210 pBlkCache->pszId = RTStrDup(pcszId);
1211
1212 if ( pBlkCache
1213 && pBlkCache->pszId)
1214 {
1215 pBlkCache->fSuspended = false;
1216 pBlkCache->pCache = pBlkCacheGlobal;
1217 RTListInit(&pBlkCache->ListDirtyNotCommitted);
1218
1219 rc = RTSpinlockCreate(&pBlkCache->LockList, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "pdmR3BlkCacheRetain");
1220 if (RT_SUCCESS(rc))
1221 {
1222 rc = RTSemRWCreate(&pBlkCache->SemRWEntries);
1223 if (RT_SUCCESS(rc))
1224 {
1225 pBlkCache->pTree = (PAVLRU64TREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
1226 if (pBlkCache->pTree)
1227 {
1228#ifdef VBOX_WITH_STATISTICS
1229 STAMR3RegisterF(pBlkCacheGlobal->pVM, &pBlkCache->StatWriteDeferred,
1230 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1231 STAMUNIT_COUNT, "Number of deferred writes",
1232 "/PDM/BlkCache/%s/Cache/DeferredWrites", pBlkCache->pszId);
1233#endif
1234
1235 /* Add to the list of users. */
1236 pBlkCacheGlobal->cRefs++;
1237 RTListAppend(&pBlkCacheGlobal->ListUsers, &pBlkCache->NodeCacheUser);
1238 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1239
1240 *ppBlkCache = pBlkCache;
1241 LogFlowFunc(("returns success\n"));
1242 return VINF_SUCCESS;
1243 }
1244
1245 rc = VERR_NO_MEMORY;
1246 RTSemRWDestroy(pBlkCache->SemRWEntries);
1247 }
1248
1249 RTSpinlockDestroy(pBlkCache->LockList);
1250 }
1251
1252 RTStrFree(pBlkCache->pszId);
1253 }
1254 else
1255 rc = VERR_NO_MEMORY;
1256
1257 if (pBlkCache)
1258 RTMemFree(pBlkCache);
1259 }
1260 else
1261 rc = VERR_ALREADY_EXISTS;
1262
1263 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1264
1265 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1266 return rc;
1267}
1268
1269VMMR3DECL(int) PDMR3BlkCacheRetainDriver(PVM pVM, PPDMDRVINS pDrvIns, PPPDMBLKCACHE ppBlkCache,
1270 PFNPDMBLKCACHEXFERCOMPLETEDRV pfnXferComplete,
1271 PFNPDMBLKCACHEXFERENQUEUEDRV pfnXferEnqueue,
1272 PFNPDMBLKCACHEXFERENQUEUEDISCARDDRV pfnXferEnqueueDiscard,
1273 const char *pcszId)
1274{
1275 int rc = VINF_SUCCESS;
1276 PPDMBLKCACHE pBlkCache;
1277
1278 rc = pdmR3BlkCacheRetain(pVM, &pBlkCache, pcszId);
1279 if (RT_SUCCESS(rc))
1280 {
1281 pBlkCache->enmType = PDMBLKCACHETYPE_DRV;
1282 pBlkCache->u.Drv.pfnXferComplete = pfnXferComplete;
1283 pBlkCache->u.Drv.pfnXferEnqueue = pfnXferEnqueue;
1284 pBlkCache->u.Drv.pfnXferEnqueueDiscard = pfnXferEnqueueDiscard;
1285 pBlkCache->u.Drv.pDrvIns = pDrvIns;
1286 *ppBlkCache = pBlkCache;
1287 }
1288
1289 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1290 return rc;
1291}
1292
1293VMMR3DECL(int) PDMR3BlkCacheRetainDevice(PVM pVM, PPDMDEVINS pDevIns, PPPDMBLKCACHE ppBlkCache,
1294 PFNPDMBLKCACHEXFERCOMPLETEDEV pfnXferComplete,
1295 PFNPDMBLKCACHEXFERENQUEUEDEV pfnXferEnqueue,
1296 PFNPDMBLKCACHEXFERENQUEUEDISCARDDEV pfnXferEnqueueDiscard,
1297 const char *pcszId)
1298{
1299 int rc = VINF_SUCCESS;
1300 PPDMBLKCACHE pBlkCache;
1301
1302 rc = pdmR3BlkCacheRetain(pVM, &pBlkCache, pcszId);
1303 if (RT_SUCCESS(rc))
1304 {
1305 pBlkCache->enmType = PDMBLKCACHETYPE_DEV;
1306 pBlkCache->u.Dev.pfnXferComplete = pfnXferComplete;
1307 pBlkCache->u.Dev.pfnXferEnqueue = pfnXferEnqueue;
1308 pBlkCache->u.Dev.pfnXferEnqueueDiscard = pfnXferEnqueueDiscard;
1309 pBlkCache->u.Dev.pDevIns = pDevIns;
1310 *ppBlkCache = pBlkCache;
1311 }
1312
1313 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1314 return rc;
1315
1316}
1317
1318VMMR3DECL(int) PDMR3BlkCacheRetainUsb(PVM pVM, PPDMUSBINS pUsbIns, PPPDMBLKCACHE ppBlkCache,
1319 PFNPDMBLKCACHEXFERCOMPLETEUSB pfnXferComplete,
1320 PFNPDMBLKCACHEXFERENQUEUEUSB pfnXferEnqueue,
1321 PFNPDMBLKCACHEXFERENQUEUEDISCARDUSB pfnXferEnqueueDiscard,
1322 const char *pcszId)
1323{
1324 int rc = VINF_SUCCESS;
1325 PPDMBLKCACHE pBlkCache;
1326
1327 rc = pdmR3BlkCacheRetain(pVM, &pBlkCache, pcszId);
1328 if (RT_SUCCESS(rc))
1329 {
1330 pBlkCache->enmType = PDMBLKCACHETYPE_USB;
1331 pBlkCache->u.Usb.pfnXferComplete = pfnXferComplete;
1332 pBlkCache->u.Usb.pfnXferEnqueue = pfnXferEnqueue;
1333 pBlkCache->u.Usb.pfnXferEnqueueDiscard = pfnXferEnqueueDiscard;
1334 pBlkCache->u.Usb.pUsbIns = pUsbIns;
1335 *ppBlkCache = pBlkCache;
1336 }
1337
1338 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1339 return rc;
1340
1341}
1342
1343VMMR3DECL(int) PDMR3BlkCacheRetainInt(PVM pVM, void *pvUser, PPPDMBLKCACHE ppBlkCache,
1344 PFNPDMBLKCACHEXFERCOMPLETEINT pfnXferComplete,
1345 PFNPDMBLKCACHEXFERENQUEUEINT pfnXferEnqueue,
1346 PFNPDMBLKCACHEXFERENQUEUEDISCARDINT pfnXferEnqueueDiscard,
1347 const char *pcszId)
1348{
1349 int rc = VINF_SUCCESS;
1350 PPDMBLKCACHE pBlkCache;
1351
1352 rc = pdmR3BlkCacheRetain(pVM, &pBlkCache, pcszId);
1353 if (RT_SUCCESS(rc))
1354 {
1355 pBlkCache->enmType = PDMBLKCACHETYPE_INTERNAL;
1356 pBlkCache->u.Int.pfnXferComplete = pfnXferComplete;
1357 pBlkCache->u.Int.pfnXferEnqueue = pfnXferEnqueue;
1358 pBlkCache->u.Int.pfnXferEnqueueDiscard = pfnXferEnqueueDiscard;
1359 pBlkCache->u.Int.pvUser = pvUser;
1360 *ppBlkCache = pBlkCache;
1361 }
1362
1363 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1364 return rc;
1365
1366}
1367
1368/**
1369 * Callback for the AVL destroy routine. Frees a cache entry for this endpoint.
1370 *
1371 * @returns IPRT status code.
1372 * @param pNode The node to destroy.
1373 * @param pvUser Opaque user data.
1374 */
1375static int pdmBlkCacheEntryDestroy(PAVLRU64NODECORE pNode, void *pvUser)
1376{
1377 PPDMBLKCACHEENTRY pEntry = (PPDMBLKCACHEENTRY)pNode;
1378 PPDMBLKCACHEGLOBAL pCache = (PPDMBLKCACHEGLOBAL)pvUser;
1379 PPDMBLKCACHE pBlkCache = pEntry->pBlkCache;
1380
1381 while (ASMAtomicReadU32(&pEntry->fFlags) & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS)
1382 {
1383 /* Leave the locks to let the I/O thread make progress but reference the entry to prevent eviction. */
1384 pdmBlkCacheEntryRef(pEntry);
1385 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
1386 pdmBlkCacheLockLeave(pCache);
1387
1388 RTThreadSleep(250);
1389
1390 /* Re-enter all locks */
1391 pdmBlkCacheLockEnter(pCache);
1392 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1393 pdmBlkCacheEntryRelease(pEntry);
1394 }
1395
1396 AssertMsg(!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS),
1397 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
1398
1399 bool fUpdateCache = pEntry->pList == &pCache->LruFrequentlyUsed
1400 || pEntry->pList == &pCache->LruRecentlyUsedIn;
1401
1402 pdmBlkCacheEntryRemoveFromList(pEntry);
1403
1404 if (fUpdateCache)
1405 pdmBlkCacheSub(pCache, pEntry->cbData);
1406
1407 RTMemPageFree(pEntry->pbData, pEntry->cbData);
1408 RTMemFree(pEntry);
1409
1410 return VINF_SUCCESS;
1411}
1412
1413/**
1414 * Destroys all cache resources used by the given endpoint.
1415 *
1416 * @returns nothing.
1417 * @param pEndpoint The endpoint to the destroy.
1418 */
1419VMMR3DECL(void) PDMR3BlkCacheRelease(PPDMBLKCACHE pBlkCache)
1420{
1421 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
1422
1423 /*
1424 * Commit all dirty entries now (they are waited on for completion during the
1425 * destruction of the AVL tree below).
1426 * The exception is if the VM was paused because of an I/O error before.
1427 */
1428 if (!ASMAtomicReadBool(&pCache->fIoErrorVmSuspended))
1429 pdmBlkCacheCommit(pBlkCache);
1430
1431 /* Make sure nobody is accessing the cache while we delete the tree. */
1432 pdmBlkCacheLockEnter(pCache);
1433 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1434 RTAvlrU64Destroy(pBlkCache->pTree, pdmBlkCacheEntryDestroy, pCache);
1435 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
1436
1437 RTSpinlockDestroy(pBlkCache->LockList);
1438
1439 pCache->cRefs--;
1440 RTListNodeRemove(&pBlkCache->NodeCacheUser);
1441
1442 pdmBlkCacheLockLeave(pCache);
1443
1444 RTSemRWDestroy(pBlkCache->SemRWEntries);
1445
1446#ifdef VBOX_WITH_STATISTICS
1447 STAMR3DeregisterF(pCache->pVM->pUVM, "/PDM/BlkCache/%s/Cache/DeferredWrites", pBlkCache->pszId);
1448#endif
1449
1450 RTStrFree(pBlkCache->pszId);
1451 RTMemFree(pBlkCache);
1452}
1453
1454VMMR3DECL(void) PDMR3BlkCacheReleaseDevice(PVM pVM, PPDMDEVINS pDevIns)
1455{
1456 LogFlow(("%s: pDevIns=%p\n", __FUNCTION__, pDevIns));
1457
1458 /*
1459 * Validate input.
1460 */
1461 if (!pDevIns)
1462 return;
1463 VM_ASSERT_EMT(pVM);
1464
1465 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1466 PPDMBLKCACHE pBlkCache, pBlkCacheNext;
1467
1468 /* Return silently if not supported. */
1469 if (!pBlkCacheGlobal)
1470 return;
1471
1472 pdmBlkCacheLockEnter(pBlkCacheGlobal);
1473
1474 RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
1475 {
1476 if ( pBlkCache->enmType == PDMBLKCACHETYPE_DEV
1477 && pBlkCache->u.Dev.pDevIns == pDevIns)
1478 PDMR3BlkCacheRelease(pBlkCache);
1479 }
1480
1481 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1482}
1483
1484VMMR3DECL(void) PDMR3BlkCacheReleaseDriver(PVM pVM, PPDMDRVINS pDrvIns)
1485{
1486 LogFlow(("%s: pDrvIns=%p\n", __FUNCTION__, pDrvIns));
1487
1488 /*
1489 * Validate input.
1490 */
1491 if (!pDrvIns)
1492 return;
1493 VM_ASSERT_EMT(pVM);
1494
1495 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1496 PPDMBLKCACHE pBlkCache, pBlkCacheNext;
1497
1498 /* Return silently if not supported. */
1499 if (!pBlkCacheGlobal)
1500 return;
1501
1502 pdmBlkCacheLockEnter(pBlkCacheGlobal);
1503
1504 RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
1505 {
1506 if ( pBlkCache->enmType == PDMBLKCACHETYPE_DRV
1507 && pBlkCache->u.Drv.pDrvIns == pDrvIns)
1508 PDMR3BlkCacheRelease(pBlkCache);
1509 }
1510
1511 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1512}
1513
1514VMMR3DECL(void) PDMR3BlkCacheReleaseUsb(PVM pVM, PPDMUSBINS pUsbIns)
1515{
1516 LogFlow(("%s: pUsbIns=%p\n", __FUNCTION__, pUsbIns));
1517
1518 /*
1519 * Validate input.
1520 */
1521 if (!pUsbIns)
1522 return;
1523 VM_ASSERT_EMT(pVM);
1524
1525 PPDMBLKCACHEGLOBAL pBlkCacheGlobal = pVM->pUVM->pdm.s.pBlkCacheGlobal;
1526 PPDMBLKCACHE pBlkCache, pBlkCacheNext;
1527
1528 /* Return silently if not supported. */
1529 if (!pBlkCacheGlobal)
1530 return;
1531
1532 pdmBlkCacheLockEnter(pBlkCacheGlobal);
1533
1534 RTListForEachSafe(&pBlkCacheGlobal->ListUsers, pBlkCache, pBlkCacheNext, PDMBLKCACHE, NodeCacheUser)
1535 {
1536 if ( pBlkCache->enmType == PDMBLKCACHETYPE_USB
1537 && pBlkCache->u.Usb.pUsbIns == pUsbIns)
1538 PDMR3BlkCacheRelease(pBlkCache);
1539 }
1540
1541 pdmBlkCacheLockLeave(pBlkCacheGlobal);
1542}
1543
1544static PPDMBLKCACHEENTRY pdmBlkCacheGetCacheEntryByOffset(PPDMBLKCACHE pBlkCache, uint64_t off)
1545{
1546 STAM_PROFILE_ADV_START(&pBlkCache->pCache->StatTreeGet, Cache);
1547
1548 RTSemRWRequestRead(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1549 PPDMBLKCACHEENTRY pEntry = (PPDMBLKCACHEENTRY)RTAvlrU64RangeGet(pBlkCache->pTree, off);
1550 if (pEntry)
1551 pdmBlkCacheEntryRef(pEntry);
1552 RTSemRWReleaseRead(pBlkCache->SemRWEntries);
1553
1554 STAM_PROFILE_ADV_STOP(&pBlkCache->pCache->StatTreeGet, Cache);
1555
1556 return pEntry;
1557}
1558
1559/**
1560 * Return the best fit cache entries for the given offset.
1561 *
1562 * @returns nothing.
1563 * @param pBlkCache The endpoint cache.
1564 * @param off The offset.
1565 * @param pEntryAbove Where to store the pointer to the best fit entry above the
1566 * the given offset. NULL if not required.
1567 */
1568static void pdmBlkCacheGetCacheBestFitEntryByOffset(PPDMBLKCACHE pBlkCache, uint64_t off,
1569 PPDMBLKCACHEENTRY *ppEntryAbove)
1570{
1571 STAM_PROFILE_ADV_START(&pBlkCache->pCache->StatTreeGet, Cache);
1572
1573 RTSemRWRequestRead(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1574 if (ppEntryAbove)
1575 {
1576 *ppEntryAbove = (PPDMBLKCACHEENTRY)RTAvlrU64GetBestFit(pBlkCache->pTree, off, true /*fAbove*/);
1577 if (*ppEntryAbove)
1578 pdmBlkCacheEntryRef(*ppEntryAbove);
1579 }
1580
1581 RTSemRWReleaseRead(pBlkCache->SemRWEntries);
1582
1583 STAM_PROFILE_ADV_STOP(&pBlkCache->pCache->StatTreeGet, Cache);
1584}
1585
1586static void pdmBlkCacheInsertEntry(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEENTRY pEntry)
1587{
1588 STAM_PROFILE_ADV_START(&pBlkCache->pCache->StatTreeInsert, Cache);
1589 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1590 bool fInserted = RTAvlrU64Insert(pBlkCache->pTree, &pEntry->Core);
1591 AssertMsg(fInserted, ("Node was not inserted into tree\n")); NOREF(fInserted);
1592 STAM_PROFILE_ADV_STOP(&pBlkCache->pCache->StatTreeInsert, Cache);
1593 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
1594}
1595
1596/**
1597 * Allocates and initializes a new entry for the cache.
1598 * The entry has a reference count of 1.
1599 *
1600 * @returns Pointer to the new cache entry or NULL if out of memory.
1601 * @param pBlkCache The cache the entry belongs to.
1602 * @param off Start offset.
1603 * @param cbData Size of the cache entry.
1604 * @param pbBuffer Pointer to the buffer to use.
1605 * NULL if a new buffer should be allocated.
1606 * The buffer needs to have the same size of the entry.
1607 */
1608static PPDMBLKCACHEENTRY pdmBlkCacheEntryAlloc(PPDMBLKCACHE pBlkCache,
1609 uint64_t off, size_t cbData, uint8_t *pbBuffer)
1610{
1611 AssertReturn(cbData <= UINT32_MAX, NULL);
1612 PPDMBLKCACHEENTRY pEntryNew = (PPDMBLKCACHEENTRY)RTMemAllocZ(sizeof(PDMBLKCACHEENTRY));
1613
1614 if (RT_UNLIKELY(!pEntryNew))
1615 return NULL;
1616
1617 pEntryNew->Core.Key = off;
1618 pEntryNew->Core.KeyLast = off + cbData - 1;
1619 pEntryNew->pBlkCache = pBlkCache;
1620 pEntryNew->fFlags = 0;
1621 pEntryNew->cRefs = 1; /* We are using it now. */
1622 pEntryNew->pList = NULL;
1623 pEntryNew->cbData = (uint32_t)cbData;
1624 pEntryNew->pWaitingHead = NULL;
1625 pEntryNew->pWaitingTail = NULL;
1626 if (pbBuffer)
1627 pEntryNew->pbData = pbBuffer;
1628 else
1629 pEntryNew->pbData = (uint8_t *)RTMemPageAlloc(cbData);
1630
1631 if (RT_UNLIKELY(!pEntryNew->pbData))
1632 {
1633 RTMemFree(pEntryNew);
1634 return NULL;
1635 }
1636
1637 return pEntryNew;
1638}
1639
1640/**
1641 * Checks that a set of flags is set/clear acquiring the R/W semaphore
1642 * in exclusive mode.
1643 *
1644 * @returns true if the flag in fSet is set and the one in fClear is clear.
1645 * false otherwise.
1646 * The R/W semaphore is only held if true is returned.
1647 *
1648 * @param pBlkCache The endpoint cache instance data.
1649 * @param pEntry The entry to check the flags for.
1650 * @param fSet The flag which is tested to be set.
1651 * @param fClear The flag which is tested to be clear.
1652 */
1653DECLINLINE(bool) pdmBlkCacheEntryFlagIsSetClearAcquireLock(PPDMBLKCACHE pBlkCache,
1654 PPDMBLKCACHEENTRY pEntry,
1655 uint32_t fSet, uint32_t fClear)
1656{
1657 uint32_t fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1658 bool fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1659
1660 if (fPassed)
1661 {
1662 /* Acquire the lock and check again because the completion callback might have raced us. */
1663 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
1664
1665 fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1666 fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1667
1668 /* Drop the lock if we didn't passed the test. */
1669 if (!fPassed)
1670 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
1671 }
1672
1673 return fPassed;
1674}
1675
1676/**
1677 * Adds a segment to the waiting list for a cache entry
1678 * which is currently in progress.
1679 *
1680 * @returns nothing.
1681 * @param pEntry The cache entry to add the segment to.
1682 * @param pSeg The segment to add.
1683 */
1684DECLINLINE(void) pdmBlkCacheEntryAddWaiter(PPDMBLKCACHEENTRY pEntry,
1685 PPDMBLKCACHEWAITER pWaiter)
1686{
1687 pWaiter->pNext = NULL;
1688
1689 if (pEntry->pWaitingHead)
1690 {
1691 AssertPtr(pEntry->pWaitingTail);
1692
1693 pEntry->pWaitingTail->pNext = pWaiter;
1694 pEntry->pWaitingTail = pWaiter;
1695 }
1696 else
1697 {
1698 Assert(!pEntry->pWaitingTail);
1699
1700 pEntry->pWaitingHead = pWaiter;
1701 pEntry->pWaitingTail = pWaiter;
1702 }
1703}
1704
1705/**
1706 * Add a buffer described by the I/O memory context
1707 * to the entry waiting for completion.
1708 *
1709 * @returns VBox status code.
1710 * @param pEntry The entry to add the buffer to.
1711 * @param pTask Task associated with the buffer.
1712 * @param pIoMemCtx The memory context to use.
1713 * @param offDiff Offset from the start of the buffer
1714 * in the entry.
1715 * @param cbData Amount of data to wait for onthis entry.
1716 * @param fWrite Flag whether the task waits because it wants to write
1717 * to the cache entry.
1718 */
1719static int pdmBlkCacheEntryWaitersAdd(PPDMBLKCACHEENTRY pEntry,
1720 PPDMBLKCACHEREQ pReq,
1721 PRTSGBUF pSgBuf, uint64_t offDiff,
1722 size_t cbData, bool fWrite)
1723{
1724 PPDMBLKCACHEWAITER pWaiter = (PPDMBLKCACHEWAITER)RTMemAllocZ(sizeof(PDMBLKCACHEWAITER));
1725 if (!pWaiter)
1726 return VERR_NO_MEMORY;
1727
1728 ASMAtomicIncU32(&pReq->cXfersPending);
1729 pWaiter->pReq = pReq;
1730 pWaiter->offCacheEntry = offDiff;
1731 pWaiter->cbTransfer = cbData;
1732 pWaiter->fWrite = fWrite;
1733 RTSgBufClone(&pWaiter->SgBuf, pSgBuf);
1734 RTSgBufAdvance(pSgBuf, cbData);
1735
1736 pdmBlkCacheEntryAddWaiter(pEntry, pWaiter);
1737
1738 return VINF_SUCCESS;
1739}
1740
1741/**
1742 * Calculate aligned offset and size for a new cache entry which do not
1743 * intersect with an already existing entry and the file end.
1744 *
1745 * @returns The number of bytes the entry can hold of the requested amount
1746 * of bytes.
1747 * @param pEndpoint The endpoint.
1748 * @param pBlkCache The endpoint cache.
1749 * @param off The start offset.
1750 * @param cb The number of bytes the entry needs to hold at
1751 * least.
1752 * @param pcbEntry Where to store the number of bytes the entry can hold.
1753 * Can be less than given because of other entries.
1754 */
1755static uint32_t pdmBlkCacheEntryBoundariesCalc(PPDMBLKCACHE pBlkCache,
1756 uint64_t off, uint32_t cb,
1757 uint32_t *pcbEntry)
1758{
1759 /* Get the best fit entries around the offset */
1760 PPDMBLKCACHEENTRY pEntryAbove = NULL;
1761 pdmBlkCacheGetCacheBestFitEntryByOffset(pBlkCache, off, &pEntryAbove);
1762
1763 /* Log the info */
1764 LogFlow(("%sest fit entry above off=%llu (BestFit=%llu BestFitEnd=%llu BestFitSize=%u)\n",
1765 pEntryAbove ? "B" : "No b",
1766 off,
1767 pEntryAbove ? pEntryAbove->Core.Key : 0,
1768 pEntryAbove ? pEntryAbove->Core.KeyLast : 0,
1769 pEntryAbove ? pEntryAbove->cbData : 0));
1770
1771 uint32_t cbNext;
1772 uint32_t cbInEntry;
1773 if ( pEntryAbove
1774 && off + cb > pEntryAbove->Core.Key)
1775 {
1776 cbInEntry = (uint32_t)(pEntryAbove->Core.Key - off);
1777 cbNext = (uint32_t)(pEntryAbove->Core.Key - off);
1778 }
1779 else
1780 {
1781 cbInEntry = cb;
1782 cbNext = cb;
1783 }
1784
1785 /* A few sanity checks */
1786 AssertMsg(!pEntryAbove || off + cbNext <= pEntryAbove->Core.Key,
1787 ("Aligned size intersects with another cache entry\n"));
1788 Assert(cbInEntry <= cbNext);
1789
1790 if (pEntryAbove)
1791 pdmBlkCacheEntryRelease(pEntryAbove);
1792
1793 LogFlow(("off=%llu cbNext=%u\n", off, cbNext));
1794
1795 *pcbEntry = cbNext;
1796
1797 return cbInEntry;
1798}
1799
1800/**
1801 * Create a new cache entry evicting data from the cache if required.
1802 *
1803 * @returns Pointer to the new cache entry or NULL
1804 * if not enough bytes could be evicted from the cache.
1805 * @param pEndpoint The endpoint.
1806 * @param pBlkCache The endpoint cache.
1807 * @param off The offset.
1808 * @param cb Number of bytes the cache entry should have.
1809 * @param pcbData Where to store the number of bytes the new
1810 * entry can hold. May be lower than actually requested
1811 * due to another entry intersecting the access range.
1812 */
1813static PPDMBLKCACHEENTRY pdmBlkCacheEntryCreate(PPDMBLKCACHE pBlkCache,
1814 uint64_t off, size_t cb,
1815 size_t *pcbData)
1816{
1817 uint32_t cbEntry = 0;
1818
1819 *pcbData = pdmBlkCacheEntryBoundariesCalc(pBlkCache, off, (uint32_t)cb, &cbEntry);
1820 AssertReturn(cb <= UINT32_MAX, NULL);
1821
1822 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
1823 pdmBlkCacheLockEnter(pCache);
1824
1825 PPDMBLKCACHEENTRY pEntryNew = NULL;
1826 uint8_t *pbBuffer = NULL;
1827 bool fEnough = pdmBlkCacheReclaim(pCache, cbEntry, true, &pbBuffer);
1828 if (fEnough)
1829 {
1830 LogFlow(("Evicted enough bytes (%u requested). Creating new cache entry\n", cbEntry));
1831
1832 pEntryNew = pdmBlkCacheEntryAlloc(pBlkCache, off, cbEntry, pbBuffer);
1833 if (RT_LIKELY(pEntryNew))
1834 {
1835 pdmBlkCacheEntryAddToList(&pCache->LruRecentlyUsedIn, pEntryNew);
1836 pdmBlkCacheAdd(pCache, cbEntry);
1837 pdmBlkCacheLockLeave(pCache);
1838
1839 pdmBlkCacheInsertEntry(pBlkCache, pEntryNew);
1840
1841 AssertMsg( (off >= pEntryNew->Core.Key)
1842 && (off + *pcbData <= pEntryNew->Core.KeyLast + 1),
1843 ("Overflow in calculation off=%llu\n", off));
1844 }
1845 else
1846 pdmBlkCacheLockLeave(pCache);
1847 }
1848 else
1849 pdmBlkCacheLockLeave(pCache);
1850
1851 return pEntryNew;
1852}
1853
1854static PPDMBLKCACHEREQ pdmBlkCacheReqAlloc(void *pvUser)
1855{
1856 PPDMBLKCACHEREQ pReq = (PPDMBLKCACHEREQ)RTMemAlloc(sizeof(PDMBLKCACHEREQ));
1857
1858 if (RT_LIKELY(pReq))
1859 {
1860 pReq->pvUser = pvUser;
1861 pReq->rcReq = VINF_SUCCESS;
1862 pReq->cXfersPending = 0;
1863 }
1864
1865 return pReq;
1866}
1867
1868static void pdmBlkCacheReqComplete(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEREQ pReq)
1869{
1870 switch (pBlkCache->enmType)
1871 {
1872 case PDMBLKCACHETYPE_DEV:
1873 {
1874 pBlkCache->u.Dev.pfnXferComplete(pBlkCache->u.Dev.pDevIns,
1875 pReq->pvUser, pReq->rcReq);
1876 break;
1877 }
1878 case PDMBLKCACHETYPE_DRV:
1879 {
1880 pBlkCache->u.Drv.pfnXferComplete(pBlkCache->u.Drv.pDrvIns,
1881 pReq->pvUser, pReq->rcReq);
1882 break;
1883 }
1884 case PDMBLKCACHETYPE_USB:
1885 {
1886 pBlkCache->u.Usb.pfnXferComplete(pBlkCache->u.Usb.pUsbIns,
1887 pReq->pvUser, pReq->rcReq);
1888 break;
1889 }
1890 case PDMBLKCACHETYPE_INTERNAL:
1891 {
1892 pBlkCache->u.Int.pfnXferComplete(pBlkCache->u.Int.pvUser,
1893 pReq->pvUser, pReq->rcReq);
1894 break;
1895 }
1896 default:
1897 AssertMsgFailed(("Unknown block cache type!\n"));
1898 }
1899
1900 RTMemFree(pReq);
1901}
1902
1903static bool pdmBlkCacheReqUpdate(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEREQ pReq,
1904 int rcReq, bool fCallHandler)
1905{
1906 if (RT_FAILURE(rcReq))
1907 ASMAtomicCmpXchgS32(&pReq->rcReq, rcReq, VINF_SUCCESS);
1908
1909 AssertMsg(pReq->cXfersPending > 0, ("No transfers are pending for this request\n"));
1910 uint32_t cXfersPending = ASMAtomicDecU32(&pReq->cXfersPending);
1911
1912 if (!cXfersPending)
1913 {
1914 if (fCallHandler)
1915 pdmBlkCacheReqComplete(pBlkCache, pReq);
1916 else
1917 RTMemFree(pReq);
1918 return true;
1919 }
1920
1921 LogFlowFunc(("pReq=%#p cXfersPending=%u\n", pReq, cXfersPending));
1922 return false;
1923}
1924
1925VMMR3DECL(int) PDMR3BlkCacheRead(PPDMBLKCACHE pBlkCache, uint64_t off,
1926 PCRTSGBUF pcSgBuf, size_t cbRead, void *pvUser)
1927{
1928 int rc = VINF_SUCCESS;
1929 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
1930 PPDMBLKCACHEENTRY pEntry;
1931 PPDMBLKCACHEREQ pReq;
1932
1933 LogFlowFunc((": pBlkCache=%#p{%s} off=%llu pcSgBuf=%#p cbRead=%u pvUser=%#p\n",
1934 pBlkCache, pBlkCache->pszId, off, pcSgBuf, cbRead, pvUser));
1935
1936 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
1937 AssertReturn(!pBlkCache->fSuspended, VERR_INVALID_STATE);
1938
1939 RTSGBUF SgBuf;
1940 RTSgBufClone(&SgBuf, pcSgBuf);
1941
1942 /* Allocate new request structure. */
1943 pReq = pdmBlkCacheReqAlloc(pvUser);
1944 if (RT_UNLIKELY(!pReq))
1945 return VERR_NO_MEMORY;
1946
1947 /* Increment data transfer counter to keep the request valid while we access it. */
1948 ASMAtomicIncU32(&pReq->cXfersPending);
1949
1950 while (cbRead)
1951 {
1952 size_t cbToRead;
1953
1954 pEntry = pdmBlkCacheGetCacheEntryByOffset(pBlkCache, off);
1955
1956 /*
1957 * If there is no entry we try to create a new one eviciting unused pages
1958 * if the cache is full. If this is not possible we will pass the request through
1959 * and skip the caching (all entries may be still in progress so they can't
1960 * be evicted)
1961 * If we have an entry it can be in one of the LRU lists where the entry
1962 * contains data (recently used or frequently used LRU) so we can just read
1963 * the data we need and put the entry at the head of the frequently used LRU list.
1964 * In case the entry is in one of the ghost lists it doesn't contain any data.
1965 * We have to fetch it again evicting pages from either T1 or T2 to make room.
1966 */
1967 if (pEntry)
1968 {
1969 uint64_t offDiff = off - pEntry->Core.Key;
1970
1971 AssertMsg(off >= pEntry->Core.Key,
1972 ("Overflow in calculation off=%llu OffsetAligned=%llu\n",
1973 off, pEntry->Core.Key));
1974
1975 AssertPtr(pEntry->pList);
1976
1977 cbToRead = RT_MIN(pEntry->cbData - offDiff, cbRead);
1978
1979 AssertMsg(off + cbToRead <= pEntry->Core.Key + pEntry->Core.KeyLast + 1,
1980 ("Buffer of cache entry exceeded off=%llu cbToRead=%d\n",
1981 off, cbToRead));
1982
1983 cbRead -= cbToRead;
1984
1985 if (!cbRead)
1986 STAM_COUNTER_INC(&pCache->cHits);
1987 else
1988 STAM_COUNTER_INC(&pCache->cPartialHits);
1989
1990 STAM_COUNTER_ADD(&pCache->StatRead, cbToRead);
1991
1992 /* Ghost lists contain no data. */
1993 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1994 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1995 {
1996 if (pdmBlkCacheEntryFlagIsSetClearAcquireLock(pBlkCache, pEntry,
1997 PDMBLKCACHE_ENTRY_IO_IN_PROGRESS,
1998 PDMBLKCACHE_ENTRY_IS_DIRTY))
1999 {
2000 /* Entry didn't completed yet. Append to the list */
2001 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2002 &SgBuf, offDiff, cbToRead,
2003 false /* fWrite */);
2004 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2005 }
2006 else
2007 {
2008 /* Read as much as we can from the entry. */
2009 RTSgBufCopyFromBuf(&SgBuf, pEntry->pbData + offDiff, cbToRead);
2010 }
2011
2012 /* Move this entry to the top position */
2013 if (pEntry->pList == &pCache->LruFrequentlyUsed)
2014 {
2015 pdmBlkCacheLockEnter(pCache);
2016 pdmBlkCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2017 pdmBlkCacheLockLeave(pCache);
2018 }
2019 /* Release the entry */
2020 pdmBlkCacheEntryRelease(pEntry);
2021 }
2022 else
2023 {
2024 uint8_t *pbBuffer = NULL;
2025
2026 LogFlow(("Fetching data for ghost entry %#p from file\n", pEntry));
2027
2028 pdmBlkCacheLockEnter(pCache);
2029 pdmBlkCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
2030 bool fEnough = pdmBlkCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
2031
2032 /* Move the entry to Am and fetch it to the cache. */
2033 if (fEnough)
2034 {
2035 pdmBlkCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2036 pdmBlkCacheAdd(pCache, pEntry->cbData);
2037 pdmBlkCacheLockLeave(pCache);
2038
2039 if (pbBuffer)
2040 pEntry->pbData = pbBuffer;
2041 else
2042 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
2043 AssertPtr(pEntry->pbData);
2044
2045 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2046 &SgBuf, offDiff, cbToRead,
2047 false /* fWrite */);
2048 pdmBlkCacheEntryReadFromMedium(pEntry);
2049 /* Release the entry */
2050 pdmBlkCacheEntryRelease(pEntry);
2051 }
2052 else
2053 {
2054 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2055 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2056 RTAvlrU64Remove(pBlkCache->pTree, pEntry->Core.Key);
2057 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2058 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2059
2060 pdmBlkCacheLockLeave(pCache);
2061
2062 RTMemFree(pEntry);
2063
2064 pdmBlkCacheRequestPassthrough(pBlkCache, pReq,
2065 &SgBuf, off, cbToRead,
2066 PDMBLKCACHEXFERDIR_READ);
2067 }
2068 }
2069 }
2070 else
2071 {
2072#ifdef VBOX_WITH_IO_READ_CACHE
2073 /* No entry found for this offset. Create a new entry and fetch the data to the cache. */
2074 PPDMBLKCACHEENTRY pEntryNew = pdmBlkCacheEntryCreate(pBlkCache,
2075 off, cbRead,
2076 &cbToRead);
2077
2078 cbRead -= cbToRead;
2079
2080 if (pEntryNew)
2081 {
2082 if (!cbRead)
2083 STAM_COUNTER_INC(&pCache->cMisses);
2084 else
2085 STAM_COUNTER_INC(&pCache->cPartialHits);
2086
2087 pdmBlkCacheEntryWaitersAdd(pEntryNew, pReq,
2088 &SgBuf,
2089 off - pEntryNew->Core.Key,
2090 cbToRead,
2091 false /* fWrite */);
2092 pdmBlkCacheEntryReadFromMedium(pEntryNew);
2093 pdmBlkCacheEntryRelease(pEntryNew); /* it is protected by the I/O in progress flag now. */
2094 }
2095 else
2096 {
2097 /*
2098 * There is not enough free space in the cache.
2099 * Pass the request directly to the I/O manager.
2100 */
2101 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToRead));
2102
2103 pdmBlkCacheRequestPassthrough(pBlkCache, pReq,
2104 &SgBuf, off, cbToRead,
2105 PDMBLKCACHEXFERDIR_READ);
2106 }
2107#else
2108 /* Clip read size if necessary. */
2109 PPDMBLKCACHEENTRY pEntryAbove;
2110 pdmBlkCacheGetCacheBestFitEntryByOffset(pBlkCache, off, &pEntryAbove);
2111
2112 if (pEntryAbove)
2113 {
2114 if (off + cbRead > pEntryAbove->Core.Key)
2115 cbToRead = pEntryAbove->Core.Key - off;
2116 else
2117 cbToRead = cbRead;
2118
2119 pdmBlkCacheEntryRelease(pEntryAbove);
2120 }
2121 else
2122 cbToRead = cbRead;
2123
2124 cbRead -= cbToRead;
2125 pdmBlkCacheRequestPassthrough(pBlkCache, pReq,
2126 &SgBuf, off, cbToRead,
2127 PDMBLKCACHEXFERDIR_READ);
2128#endif
2129 }
2130 off += cbToRead;
2131 }
2132
2133 if (!pdmBlkCacheReqUpdate(pBlkCache, pReq, rc, false))
2134 rc = VINF_AIO_TASK_PENDING;
2135
2136 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2137
2138 return rc;
2139}
2140
2141VMMR3DECL(int) PDMR3BlkCacheWrite(PPDMBLKCACHE pBlkCache, uint64_t off,
2142 PCRTSGBUF pcSgBuf, size_t cbWrite, void *pvUser)
2143{
2144 int rc = VINF_SUCCESS;
2145 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
2146 PPDMBLKCACHEENTRY pEntry;
2147 PPDMBLKCACHEREQ pReq;
2148
2149 LogFlowFunc((": pBlkCache=%#p{%s} off=%llu pcSgBuf=%#p cbWrite=%u pvUser=%#p\n",
2150 pBlkCache, pBlkCache->pszId, off, pcSgBuf, cbWrite, pvUser));
2151
2152 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
2153 AssertReturn(!pBlkCache->fSuspended, VERR_INVALID_STATE);
2154
2155 RTSGBUF SgBuf;
2156 RTSgBufClone(&SgBuf, pcSgBuf);
2157
2158 /* Allocate new request structure. */
2159 pReq = pdmBlkCacheReqAlloc(pvUser);
2160 if (RT_UNLIKELY(!pReq))
2161 return VERR_NO_MEMORY;
2162
2163 /* Increment data transfer counter to keep the request valid while we access it. */
2164 ASMAtomicIncU32(&pReq->cXfersPending);
2165
2166 while (cbWrite)
2167 {
2168 size_t cbToWrite;
2169
2170 pEntry = pdmBlkCacheGetCacheEntryByOffset(pBlkCache, off);
2171 if (pEntry)
2172 {
2173 /* Write the data into the entry and mark it as dirty */
2174 AssertPtr(pEntry->pList);
2175
2176 uint64_t offDiff = off - pEntry->Core.Key;
2177
2178 AssertMsg(off >= pEntry->Core.Key,
2179 ("Overflow in calculation off=%llu OffsetAligned=%llu\n",
2180 off, pEntry->Core.Key));
2181
2182 cbToWrite = RT_MIN(pEntry->cbData - offDiff, cbWrite);
2183 cbWrite -= cbToWrite;
2184
2185 if (!cbWrite)
2186 STAM_COUNTER_INC(&pCache->cHits);
2187 else
2188 STAM_COUNTER_INC(&pCache->cPartialHits);
2189
2190 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
2191
2192 /* Ghost lists contain no data. */
2193 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
2194 || (pEntry->pList == &pCache->LruFrequentlyUsed))
2195 {
2196 /* Check if the entry is dirty. */
2197 if (pdmBlkCacheEntryFlagIsSetClearAcquireLock(pBlkCache, pEntry,
2198 PDMBLKCACHE_ENTRY_IS_DIRTY,
2199 0))
2200 {
2201 /* If it is already dirty but not in progress just update the data. */
2202 if (!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS))
2203 RTSgBufCopyToBuf(&SgBuf, pEntry->pbData + offDiff, cbToWrite);
2204 else
2205 {
2206 /* The data isn't written to the file yet */
2207 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2208 &SgBuf, offDiff, cbToWrite,
2209 true /* fWrite */);
2210 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2211 }
2212
2213 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2214 }
2215 else /* Dirty bit not set */
2216 {
2217 /*
2218 * Check if a read is in progress for this entry.
2219 * We have to defer processing in that case.
2220 */
2221 if (pdmBlkCacheEntryFlagIsSetClearAcquireLock(pBlkCache, pEntry,
2222 PDMBLKCACHE_ENTRY_IO_IN_PROGRESS,
2223 0))
2224 {
2225 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2226 &SgBuf, offDiff, cbToWrite,
2227 true /* fWrite */);
2228 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2229 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2230 }
2231 else /* I/O in progress flag not set */
2232 {
2233 /* Write as much as we can into the entry and update the file. */
2234 RTSgBufCopyToBuf(&SgBuf, pEntry->pbData + offDiff, cbToWrite);
2235
2236 bool fCommit = pdmBlkCacheAddDirtyEntry(pBlkCache, pEntry);
2237 if (fCommit)
2238 pdmBlkCacheCommitDirtyEntries(pCache);
2239 }
2240 } /* Dirty bit not set */
2241
2242 /* Move this entry to the top position */
2243 if (pEntry->pList == &pCache->LruFrequentlyUsed)
2244 {
2245 pdmBlkCacheLockEnter(pCache);
2246 pdmBlkCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2247 pdmBlkCacheLockLeave(pCache);
2248 }
2249
2250 pdmBlkCacheEntryRelease(pEntry);
2251 }
2252 else /* Entry is on the ghost list */
2253 {
2254 uint8_t *pbBuffer = NULL;
2255
2256 pdmBlkCacheLockEnter(pCache);
2257 pdmBlkCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
2258 bool fEnough = pdmBlkCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
2259
2260 if (fEnough)
2261 {
2262 /* Move the entry to Am and fetch it to the cache. */
2263 pdmBlkCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2264 pdmBlkCacheAdd(pCache, pEntry->cbData);
2265 pdmBlkCacheLockLeave(pCache);
2266
2267 if (pbBuffer)
2268 pEntry->pbData = pbBuffer;
2269 else
2270 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
2271 AssertPtr(pEntry->pbData);
2272
2273 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2274 &SgBuf, offDiff, cbToWrite,
2275 true /* fWrite */);
2276 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2277 pdmBlkCacheEntryReadFromMedium(pEntry);
2278
2279 /* Release the reference. If it is still needed the I/O in progress flag should protect it now. */
2280 pdmBlkCacheEntryRelease(pEntry);
2281 }
2282 else
2283 {
2284 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2285 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2286 RTAvlrU64Remove(pBlkCache->pTree, pEntry->Core.Key);
2287 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2288 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2289
2290 pdmBlkCacheLockLeave(pCache);
2291
2292 RTMemFree(pEntry);
2293 pdmBlkCacheRequestPassthrough(pBlkCache, pReq,
2294 &SgBuf, off, cbToWrite,
2295 PDMBLKCACHEXFERDIR_WRITE);
2296 }
2297 }
2298 }
2299 else /* No entry found */
2300 {
2301 /*
2302 * No entry found. Try to create a new cache entry to store the data in and if that fails
2303 * write directly to the file.
2304 */
2305 PPDMBLKCACHEENTRY pEntryNew = pdmBlkCacheEntryCreate(pBlkCache,
2306 off, cbWrite,
2307 &cbToWrite);
2308
2309 cbWrite -= cbToWrite;
2310
2311 if (pEntryNew)
2312 {
2313 uint64_t offDiff = off - pEntryNew->Core.Key;
2314
2315 STAM_COUNTER_INC(&pCache->cHits);
2316
2317 /*
2318 * Check if it is possible to just write the data without waiting
2319 * for it to get fetched first.
2320 */
2321 if (!offDiff && pEntryNew->cbData == cbToWrite)
2322 {
2323 RTSgBufCopyToBuf(&SgBuf, pEntryNew->pbData, cbToWrite);
2324
2325 bool fCommit = pdmBlkCacheAddDirtyEntry(pBlkCache, pEntryNew);
2326 if (fCommit)
2327 pdmBlkCacheCommitDirtyEntries(pCache);
2328 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
2329 }
2330 else
2331 {
2332 /* Defer the write and fetch the data from the endpoint. */
2333 pdmBlkCacheEntryWaitersAdd(pEntryNew, pReq,
2334 &SgBuf, offDiff, cbToWrite,
2335 true /* fWrite */);
2336 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2337 pdmBlkCacheEntryReadFromMedium(pEntryNew);
2338 }
2339
2340 pdmBlkCacheEntryRelease(pEntryNew);
2341 }
2342 else
2343 {
2344 /*
2345 * There is not enough free space in the cache.
2346 * Pass the request directly to the I/O manager.
2347 */
2348 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToWrite));
2349
2350 STAM_COUNTER_INC(&pCache->cMisses);
2351
2352 pdmBlkCacheRequestPassthrough(pBlkCache, pReq,
2353 &SgBuf, off, cbToWrite,
2354 PDMBLKCACHEXFERDIR_WRITE);
2355 }
2356 }
2357
2358 off += cbToWrite;
2359 }
2360
2361 if (!pdmBlkCacheReqUpdate(pBlkCache, pReq, rc, false))
2362 rc = VINF_AIO_TASK_PENDING;
2363
2364 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2365
2366 return rc;
2367}
2368
2369VMMR3DECL(int) PDMR3BlkCacheFlush(PPDMBLKCACHE pBlkCache, void *pvUser)
2370{
2371 int rc = VINF_SUCCESS;
2372 PPDMBLKCACHEREQ pReq;
2373
2374 LogFlowFunc((": pBlkCache=%#p{%s}\n", pBlkCache, pBlkCache->pszId));
2375
2376 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
2377 AssertReturn(!pBlkCache->fSuspended, VERR_INVALID_STATE);
2378
2379 /* Commit dirty entries in the cache. */
2380 pdmBlkCacheCommit(pBlkCache);
2381
2382 /* Allocate new request structure. */
2383 pReq = pdmBlkCacheReqAlloc(pvUser);
2384 if (RT_UNLIKELY(!pReq))
2385 return VERR_NO_MEMORY;
2386
2387 rc = pdmBlkCacheRequestPassthrough(pBlkCache, pReq, NULL, 0, 0,
2388 PDMBLKCACHEXFERDIR_FLUSH);
2389 AssertRC(rc);
2390
2391 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2392 return VINF_AIO_TASK_PENDING;
2393}
2394
2395VMMR3DECL(int) PDMR3BlkCacheDiscard(PPDMBLKCACHE pBlkCache, PCRTRANGE paRanges,
2396 unsigned cRanges, void *pvUser)
2397{
2398 int rc = VINF_SUCCESS;
2399 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
2400 PPDMBLKCACHEENTRY pEntry;
2401 PPDMBLKCACHEREQ pReq;
2402
2403 LogFlowFunc((": pBlkCache=%#p{%s} paRanges=%#p cRanges=%u pvUser=%#p\n",
2404 pBlkCache, pBlkCache->pszId, paRanges, cRanges, pvUser));
2405
2406 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
2407 AssertReturn(!pBlkCache->fSuspended, VERR_INVALID_STATE);
2408
2409 /* Allocate new request structure. */
2410 pReq = pdmBlkCacheReqAlloc(pvUser);
2411 if (RT_UNLIKELY(!pReq))
2412 return VERR_NO_MEMORY;
2413
2414 /* Increment data transfer counter to keep the request valid while we access it. */
2415 ASMAtomicIncU32(&pReq->cXfersPending);
2416
2417 for (unsigned i = 0; i < cRanges; i++)
2418 {
2419 uint64_t offCur = paRanges[i].offStart;
2420 size_t cbLeft = paRanges[i].cbRange;
2421
2422 while (cbLeft)
2423 {
2424 size_t cbThisDiscard = 0;
2425
2426 pEntry = pdmBlkCacheGetCacheEntryByOffset(pBlkCache, offCur);
2427
2428 if (pEntry)
2429 {
2430 /* Write the data into the entry and mark it as dirty */
2431 AssertPtr(pEntry->pList);
2432
2433 uint64_t offDiff = offCur - pEntry->Core.Key;
2434
2435 AssertMsg(offCur >= pEntry->Core.Key,
2436 ("Overflow in calculation offCur=%llu OffsetAligned=%llu\n",
2437 offCur, pEntry->Core.Key));
2438
2439 cbThisDiscard = RT_MIN(pEntry->cbData - offDiff, cbLeft);
2440
2441 /* Ghost lists contain no data. */
2442 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
2443 || (pEntry->pList == &pCache->LruFrequentlyUsed))
2444 {
2445 /* Check if the entry is dirty. */
2446 if (pdmBlkCacheEntryFlagIsSetClearAcquireLock(pBlkCache, pEntry,
2447 PDMBLKCACHE_ENTRY_IS_DIRTY,
2448 0))
2449 {
2450 /* If it is dirty but not yet in progress remove it. */
2451 if (!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS))
2452 {
2453 pdmBlkCacheLockEnter(pCache);
2454 pdmBlkCacheEntryRemoveFromList(pEntry);
2455
2456 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2457 RTAvlrU64Remove(pBlkCache->pTree, pEntry->Core.Key);
2458 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2459
2460 pdmBlkCacheLockLeave(pCache);
2461
2462 RTMemFree(pEntry);
2463 }
2464 else
2465 {
2466#if 0
2467 /* The data isn't written to the file yet */
2468 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2469 &SgBuf, offDiff, cbToWrite,
2470 true /* fWrite */);
2471 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2472#endif
2473 }
2474
2475 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2476 pdmBlkCacheEntryRelease(pEntry);
2477 }
2478 else /* Dirty bit not set */
2479 {
2480 /*
2481 * Check if a read is in progress for this entry.
2482 * We have to defer processing in that case.
2483 */
2484 if(pdmBlkCacheEntryFlagIsSetClearAcquireLock(pBlkCache, pEntry,
2485 PDMBLKCACHE_ENTRY_IO_IN_PROGRESS,
2486 0))
2487 {
2488#if 0
2489 pdmBlkCacheEntryWaitersAdd(pEntry, pReq,
2490 &SgBuf, offDiff, cbToWrite,
2491 true /* fWrite */);
2492#endif
2493 STAM_COUNTER_INC(&pBlkCache->StatWriteDeferred);
2494 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2495 pdmBlkCacheEntryRelease(pEntry);
2496 }
2497 else /* I/O in progress flag not set */
2498 {
2499 pdmBlkCacheLockEnter(pCache);
2500 pdmBlkCacheEntryRemoveFromList(pEntry);
2501
2502 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2503 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2504 RTAvlrU64Remove(pBlkCache->pTree, pEntry->Core.Key);
2505 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2506 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2507
2508 pdmBlkCacheLockLeave(pCache);
2509
2510 RTMemFree(pEntry);
2511 }
2512 } /* Dirty bit not set */
2513 }
2514 else /* Entry is on the ghost list just remove cache entry. */
2515 {
2516 pdmBlkCacheLockEnter(pCache);
2517 pdmBlkCacheEntryRemoveFromList(pEntry);
2518
2519 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2520 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2521 RTAvlrU64Remove(pBlkCache->pTree, pEntry->Core.Key);
2522 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2523 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2524
2525 pdmBlkCacheLockLeave(pCache);
2526
2527 RTMemFree(pEntry);
2528 }
2529 }
2530 /* else: no entry found. */
2531
2532 offCur += cbThisDiscard;
2533 cbLeft -= cbThisDiscard;
2534 }
2535 }
2536
2537 if (!pdmBlkCacheReqUpdate(pBlkCache, pReq, rc, false))
2538 rc = VINF_AIO_TASK_PENDING;
2539
2540 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2541
2542 return rc;
2543}
2544
2545/**
2546 * Completes a task segment freeing all resources and completes the task handle
2547 * if everything was transferred.
2548 *
2549 * @returns Next task segment handle.
2550 * @param pTaskSeg Task segment to complete.
2551 * @param rc Status code to set.
2552 */
2553static PPDMBLKCACHEWAITER pdmBlkCacheWaiterComplete(PPDMBLKCACHE pBlkCache,
2554 PPDMBLKCACHEWAITER pWaiter,
2555 int rc)
2556{
2557 PPDMBLKCACHEWAITER pNext = pWaiter->pNext;
2558 PPDMBLKCACHEREQ pReq = pWaiter->pReq;
2559
2560 pdmBlkCacheReqUpdate(pBlkCache, pReq, rc, true);
2561
2562 RTMemFree(pWaiter);
2563
2564 return pNext;
2565}
2566
2567static void pdmBlkCacheIoXferCompleteEntry(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEIOXFER hIoXfer, int rcIoXfer)
2568{
2569 PPDMBLKCACHEENTRY pEntry = hIoXfer->pEntry;
2570 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
2571
2572 /* Reference the entry now as we are clearing the I/O in progress flag
2573 * which protected the entry till now. */
2574 pdmBlkCacheEntryRef(pEntry);
2575
2576 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2577 pEntry->fFlags &= ~PDMBLKCACHE_ENTRY_IO_IN_PROGRESS;
2578
2579 /* Process waiting segment list. The data in entry might have changed in-between. */
2580 bool fDirty = false;
2581 PPDMBLKCACHEWAITER pComplete = pEntry->pWaitingHead;
2582 PPDMBLKCACHEWAITER pCurr = pComplete;
2583
2584 AssertMsg((pCurr && pEntry->pWaitingTail) || (!pCurr && !pEntry->pWaitingTail),
2585 ("The list tail was not updated correctly\n"));
2586 pEntry->pWaitingTail = NULL;
2587 pEntry->pWaitingHead = NULL;
2588
2589 if (hIoXfer->enmXferDir == PDMBLKCACHEXFERDIR_WRITE)
2590 {
2591 /*
2592 * An error here is difficult to handle as the original request completed already.
2593 * The error is logged for now and the VM is paused.
2594 * If the user continues the entry is written again in the hope
2595 * the user fixed the problem and the next write succeeds.
2596 */
2597 if (RT_FAILURE(rcIoXfer))
2598 {
2599 LogRel(("I/O cache: Error while writing entry at offset %llu (%u bytes) to medium \"%s\" (rc=%Rrc)\n",
2600 pEntry->Core.Key, pEntry->cbData, pBlkCache->pszId, rcIoXfer));
2601
2602 if (!ASMAtomicXchgBool(&pCache->fIoErrorVmSuspended, true))
2603 {
2604 int rc = VMSetRuntimeError(pCache->pVM, VMSETRTERR_FLAGS_SUSPEND | VMSETRTERR_FLAGS_NO_WAIT, "BLKCACHE_IOERR",
2605 N_("The I/O cache encountered an error while updating data in medium \"%s\" (rc=%Rrc). "
2606 "Make sure there is enough free space on the disk and that the disk is working properly. "
2607 "Operation can be resumed afterwards"),
2608 pBlkCache->pszId, rcIoXfer);
2609 AssertRC(rc);
2610 }
2611
2612 /* Mark the entry as dirty again to get it added to the list later on. */
2613 fDirty = true;
2614 }
2615
2616 pEntry->fFlags &= ~PDMBLKCACHE_ENTRY_IS_DIRTY;
2617
2618 while (pCurr)
2619 {
2620 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
2621
2622 RTSgBufCopyToBuf(&pCurr->SgBuf, pEntry->pbData + pCurr->offCacheEntry, pCurr->cbTransfer);
2623 fDirty = true;
2624 pCurr = pCurr->pNext;
2625 }
2626 }
2627 else
2628 {
2629 AssertMsg(hIoXfer->enmXferDir == PDMBLKCACHEXFERDIR_READ, ("Invalid transfer type\n"));
2630 AssertMsg(!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IS_DIRTY),
2631 ("Invalid flags set\n"));
2632
2633 while (pCurr)
2634 {
2635 if (pCurr->fWrite)
2636 {
2637 RTSgBufCopyToBuf(&pCurr->SgBuf, pEntry->pbData + pCurr->offCacheEntry, pCurr->cbTransfer);
2638 fDirty = true;
2639 }
2640 else
2641 RTSgBufCopyFromBuf(&pCurr->SgBuf, pEntry->pbData + pCurr->offCacheEntry, pCurr->cbTransfer);
2642
2643 pCurr = pCurr->pNext;
2644 }
2645 }
2646
2647 bool fCommit = false;
2648 if (fDirty)
2649 fCommit = pdmBlkCacheAddDirtyEntry(pBlkCache, pEntry);
2650
2651 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2652
2653 /* Dereference so that it isn't protected anymore except we issued anyother write for it. */
2654 pdmBlkCacheEntryRelease(pEntry);
2655
2656 if (fCommit)
2657 pdmBlkCacheCommitDirtyEntries(pCache);
2658
2659 /* Complete waiters now. */
2660 while (pComplete)
2661 pComplete = pdmBlkCacheWaiterComplete(pBlkCache, pComplete, rcIoXfer);
2662}
2663
2664VMMR3DECL(void) PDMR3BlkCacheIoXferComplete(PPDMBLKCACHE pBlkCache, PPDMBLKCACHEIOXFER hIoXfer, int rcIoXfer)
2665{
2666 LogFlowFunc(("pBlkCache=%#p hIoXfer=%#p rcIoXfer=%Rrc\n", pBlkCache, hIoXfer, rcIoXfer));
2667
2668 if (hIoXfer->fIoCache)
2669 pdmBlkCacheIoXferCompleteEntry(pBlkCache, hIoXfer, rcIoXfer);
2670 else
2671 pdmBlkCacheReqUpdate(pBlkCache, hIoXfer->pReq, rcIoXfer, true);
2672 RTMemFree(hIoXfer);
2673}
2674
2675/**
2676 * Callback for the AVL do with all routine. Waits for a cachen entry to finish any pending I/O.
2677 *
2678 * @returns IPRT status code.
2679 * @param pNode The node to destroy.
2680 * @param pvUser Opaque user data.
2681 */
2682static int pdmBlkCacheEntryQuiesce(PAVLRU64NODECORE pNode, void *pvUser)
2683{
2684 PPDMBLKCACHEENTRY pEntry = (PPDMBLKCACHEENTRY)pNode;
2685 PPDMBLKCACHE pBlkCache = pEntry->pBlkCache;
2686 NOREF(pvUser);
2687
2688 while (ASMAtomicReadU32(&pEntry->fFlags) & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS)
2689 {
2690 /* Leave the locks to let the I/O thread make progress but reference the entry to prevent eviction. */
2691 pdmBlkCacheEntryRef(pEntry);
2692 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2693
2694 RTThreadSleep(1);
2695
2696 /* Re-enter all locks and drop the reference. */
2697 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2698 pdmBlkCacheEntryRelease(pEntry);
2699 }
2700
2701 AssertMsg(!(pEntry->fFlags & PDMBLKCACHE_ENTRY_IO_IN_PROGRESS),
2702 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
2703
2704 return VINF_SUCCESS;
2705}
2706
2707VMMR3DECL(int) PDMR3BlkCacheSuspend(PPDMBLKCACHE pBlkCache)
2708{
2709 int rc = VINF_SUCCESS;
2710 LogFlowFunc(("pBlkCache=%#p\n", pBlkCache));
2711
2712 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
2713
2714 if (!ASMAtomicReadBool(&pBlkCache->pCache->fIoErrorVmSuspended))
2715 pdmBlkCacheCommit(pBlkCache); /* Can issue new I/O requests. */
2716 ASMAtomicXchgBool(&pBlkCache->fSuspended, true);
2717
2718 /* Wait for all I/O to complete. */
2719 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2720 rc = RTAvlrU64DoWithAll(pBlkCache->pTree, true, pdmBlkCacheEntryQuiesce, NULL);
2721 AssertRC(rc);
2722 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2723
2724 return rc;
2725}
2726
2727VMMR3DECL(int) PDMR3BlkCacheResume(PPDMBLKCACHE pBlkCache)
2728{
2729 LogFlowFunc(("pBlkCache=%#p\n", pBlkCache));
2730
2731 AssertPtrReturn(pBlkCache, VERR_INVALID_POINTER);
2732
2733 ASMAtomicXchgBool(&pBlkCache->fSuspended, false);
2734
2735 return VINF_SUCCESS;
2736}
2737
2738VMMR3DECL(int) PDMR3BlkCacheClear(PPDMBLKCACHE pBlkCache)
2739{
2740 int rc = VINF_SUCCESS;
2741 PPDMBLKCACHEGLOBAL pCache = pBlkCache->pCache;
2742
2743 /*
2744 * Commit all dirty entries now (they are waited on for completion during the
2745 * destruction of the AVL tree below).
2746 * The exception is if the VM was paused because of an I/O error before.
2747 */
2748 if (!ASMAtomicReadBool(&pCache->fIoErrorVmSuspended))
2749 pdmBlkCacheCommit(pBlkCache);
2750
2751 /* Make sure nobody is accessing the cache while we delete the tree. */
2752 pdmBlkCacheLockEnter(pCache);
2753 RTSemRWRequestWrite(pBlkCache->SemRWEntries, RT_INDEFINITE_WAIT);
2754 RTAvlrU64Destroy(pBlkCache->pTree, pdmBlkCacheEntryDestroy, pCache);
2755 RTSemRWReleaseWrite(pBlkCache->SemRWEntries);
2756
2757 pdmBlkCacheLockLeave(pCache);
2758 return rc;
2759}
2760
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