VirtualBox

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

Last change on this file since 69405 was 69111, checked in by vboxsync, 7 years ago

(C) year

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