VirtualBox

source: vbox/trunk/src/VBox/VMM/PDMAsyncCompletionFileCache.cpp@ 27948

Last change on this file since 27948 was 27921, checked in by vboxsync, 15 years ago

Reset status code

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 84.3 KB
Line 
1/* $Id: PDMAsyncCompletionFileCache.cpp 27921 2010-03-31 19:24:05Z vboxsync $ */
2/** @file
3 * PDM Async I/O - Transport data asynchronous in R3 using EMT.
4 * File data cache.
5 */
6
7/*
8 * Copyright (C) 2006-2008 Sun Microsystems, Inc.
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 *
18 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
19 * Clara, CA 95054 USA or visit http://www.sun.com if you need
20 * additional information or have any questions.
21 */
22
23/** @page pg_pdm_async_completion_cache PDM Async Completion Cache - The file I/O cache
24 * This component implements an I/O cache for file endpoints based on the 2Q cache algorithm.
25 */
26
27/*******************************************************************************
28* Header Files *
29*******************************************************************************/
30#define LOG_GROUP LOG_GROUP_PDM_ASYNC_COMPLETION
31#include <iprt/types.h>
32#include <iprt/mem.h>
33#include <iprt/path.h>
34#include <VBox/log.h>
35#include <VBox/stam.h>
36
37#include "PDMAsyncCompletionFileInternal.h"
38
39/**
40 * A I/O memory context.
41 */
42typedef struct PDMIOMEMCTX
43{
44 /** Pointer to the scatter/gather list. */
45 PCPDMDATASEG paDataSeg;
46 /** Number of segments. */
47 size_t cSegments;
48 /** Current segment we are in. */
49 unsigned iSegIdx;
50 /** Pointer to the current buffer. */
51 uint8_t *pbBuf;
52 /** Number of bytes left in the current buffer. */
53 size_t cbBufLeft;
54} PDMIOMEMCTX, *PPDMIOMEMCTX;
55
56#ifdef VBOX_STRICT
57# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) \
58 do \
59 { \
60 AssertMsg(RTCritSectIsOwner(&Cache->CritSect), \
61 ("Thread does not own critical section\n"));\
62 } while(0)
63
64# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) \
65 do \
66 { \
67 AssertMsg(RTSemRWIsWriteOwner(pEpCache->SemRWEntries), \
68 ("Thread is not exclusive owner of the per endpoint RW semaphore\n")); \
69 } while(0)
70
71# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) \
72 do \
73 { \
74 AssertMsg(RTSemRWIsReadOwner(pEpCache->SemRWEntries), \
75 ("Thread is not read owner of the per endpoint RW semaphore\n")); \
76 } while(0)
77
78#else
79# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) do { } while(0)
80# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) do { } while(0)
81# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) do { } while(0)
82#endif
83
84/*******************************************************************************
85* Internal Functions *
86*******************************************************************************/
87static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc);
88
89/**
90 * Decrement the reference counter of the given cache entry.
91 *
92 * @returns nothing.
93 * @param pEntry The entry to release.
94 */
95DECLINLINE(void) pdmacFileEpCacheEntryRelease(PPDMACFILECACHEENTRY pEntry)
96{
97 AssertMsg(pEntry->cRefs > 0, ("Trying to release a not referenced entry\n"));
98 ASMAtomicDecU32(&pEntry->cRefs);
99}
100
101/**
102 * Increment the reference counter of the given cache entry.
103 *
104 * @returns nothing.
105 * @param pEntry The entry to reference.
106 */
107DECLINLINE(void) pdmacFileEpCacheEntryRef(PPDMACFILECACHEENTRY pEntry)
108{
109 ASMAtomicIncU32(&pEntry->cRefs);
110}
111
112/**
113 * Initialize a I/O memory context.
114 *
115 * @returns nothing
116 * @param pIoMemCtx Pointer to a unitialized I/O memory context.
117 * @param paDataSeg Pointer to the S/G list.
118 * @param cSegments Number of segments in the S/G list.
119 */
120DECLINLINE(void) pdmIoMemCtxInit(PPDMIOMEMCTX pIoMemCtx, PCPDMDATASEG paDataSeg, size_t cSegments)
121{
122 AssertMsg((cSegments > 0) && paDataSeg, ("Trying to initialize a I/O memory context without a S/G list\n"));
123
124 pIoMemCtx->paDataSeg = paDataSeg;
125 pIoMemCtx->cSegments = cSegments;
126 pIoMemCtx->iSegIdx = 0;
127 pIoMemCtx->pbBuf = (uint8_t *)paDataSeg[0].pvSeg;
128 pIoMemCtx->cbBufLeft = paDataSeg[0].cbSeg;
129}
130
131/**
132 * Return a buffer from the I/O memory context.
133 *
134 * @returns Pointer to the buffer
135 * @param pIoMemCtx Pointer to the I/O memory context.
136 * @param pcbData Pointer to the amount of byte requested.
137 * If the current buffer doesn't have enough bytes left
138 * the amount is returned in the variable.
139 */
140DECLINLINE(uint8_t *) pdmIoMemCtxGetBuffer(PPDMIOMEMCTX pIoMemCtx, size_t *pcbData)
141{
142 size_t cbData = RT_MIN(*pcbData, pIoMemCtx->cbBufLeft);
143 uint8_t *pbBuf = pIoMemCtx->pbBuf;
144
145 pIoMemCtx->cbBufLeft -= cbData;
146
147 /* Advance to the next segment if required. */
148 if (!pIoMemCtx->cbBufLeft)
149 {
150 pIoMemCtx->iSegIdx++;
151
152 if (RT_UNLIKELY(pIoMemCtx->iSegIdx == pIoMemCtx->cSegments))
153 {
154 pIoMemCtx->cbBufLeft = 0;
155 pIoMemCtx->pbBuf = NULL;
156 }
157 else
158 {
159 pIoMemCtx->pbBuf = (uint8_t *)pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].pvSeg;
160 pIoMemCtx->cbBufLeft = pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].cbSeg;
161 }
162
163 *pcbData = cbData;
164 }
165 else
166 pIoMemCtx->pbBuf += cbData;
167
168 return pbBuf;
169}
170
171#ifdef DEBUG
172static void pdmacFileCacheValidate(PPDMACFILECACHEGLOBAL pCache)
173{
174 /* Amount of cached data should never exceed the maximum amount. */
175 AssertMsg(pCache->cbCached <= pCache->cbMax,
176 ("Current amount of cached data exceeds maximum\n"));
177
178 /* The amount of cached data in the LRU and FRU list should match cbCached */
179 AssertMsg(pCache->LruRecentlyUsedIn.cbCached + pCache->LruFrequentlyUsed.cbCached == pCache->cbCached,
180 ("Amount of cached data doesn't match\n"));
181
182 AssertMsg(pCache->LruRecentlyUsedOut.cbCached <= pCache->cbRecentlyUsedOutMax,
183 ("Paged out list exceeds maximum\n"));
184}
185#endif
186
187DECLINLINE(void) pdmacFileCacheLockEnter(PPDMACFILECACHEGLOBAL pCache)
188{
189 RTCritSectEnter(&pCache->CritSect);
190#ifdef DEBUG
191 pdmacFileCacheValidate(pCache);
192#endif
193}
194
195DECLINLINE(void) pdmacFileCacheLockLeave(PPDMACFILECACHEGLOBAL pCache)
196{
197#ifdef DEBUG
198 pdmacFileCacheValidate(pCache);
199#endif
200 RTCritSectLeave(&pCache->CritSect);
201}
202
203DECLINLINE(void) pdmacFileCacheSub(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
204{
205 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
206 pCache->cbCached -= cbAmount;
207}
208
209DECLINLINE(void) pdmacFileCacheAdd(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
210{
211 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
212 pCache->cbCached += cbAmount;
213}
214
215DECLINLINE(void) pdmacFileCacheListAdd(PPDMACFILELRULIST pList, uint32_t cbAmount)
216{
217 pList->cbCached += cbAmount;
218}
219
220DECLINLINE(void) pdmacFileCacheListSub(PPDMACFILELRULIST pList, uint32_t cbAmount)
221{
222 pList->cbCached -= cbAmount;
223}
224
225#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
226/**
227 * Checks consistency of a LRU list.
228 *
229 * @returns nothing
230 * @param pList The LRU list to check.
231 * @param pNotInList Element which is not allowed to occur in the list.
232 */
233static void pdmacFileCacheCheckList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pNotInList)
234{
235 PPDMACFILECACHEENTRY pCurr = pList->pHead;
236
237 /* Check that there are no double entries and no cycles in the list. */
238 while (pCurr)
239 {
240 PPDMACFILECACHEENTRY pNext = pCurr->pNext;
241
242 while (pNext)
243 {
244 AssertMsg(pCurr != pNext,
245 ("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
246 pCurr, pList));
247 pNext = pNext->pNext;
248 }
249
250 AssertMsg(pCurr != pNotInList, ("Not allowed entry %#p is in list\n", pCurr));
251
252 if (!pCurr->pNext)
253 AssertMsg(pCurr == pList->pTail, ("End of list reached but last element is not list tail\n"));
254
255 pCurr = pCurr->pNext;
256 }
257}
258#endif
259
260/**
261 * Unlinks a cache entry from the LRU list it is assigned to.
262 *
263 * @returns nothing.
264 * @param pEntry The entry to unlink.
265 */
266static void pdmacFileCacheEntryRemoveFromList(PPDMACFILECACHEENTRY pEntry)
267{
268 PPDMACFILELRULIST pList = pEntry->pList;
269 PPDMACFILECACHEENTRY pPrev, pNext;
270
271 LogFlowFunc((": Deleting entry %#p from list %#p\n", pEntry, pList));
272
273 AssertPtr(pList);
274
275#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
276 pdmacFileCacheCheckList(pList, NULL);
277#endif
278
279 pPrev = pEntry->pPrev;
280 pNext = pEntry->pNext;
281
282 AssertMsg(pEntry != pPrev, ("Entry links to itself as previous element\n"));
283 AssertMsg(pEntry != pNext, ("Entry links to itself as next element\n"));
284
285 if (pPrev)
286 pPrev->pNext = pNext;
287 else
288 {
289 pList->pHead = pNext;
290
291 if (pNext)
292 pNext->pPrev = NULL;
293 }
294
295 if (pNext)
296 pNext->pPrev = pPrev;
297 else
298 {
299 pList->pTail = pPrev;
300
301 if (pPrev)
302 pPrev->pNext = NULL;
303 }
304
305 pEntry->pList = NULL;
306 pEntry->pPrev = NULL;
307 pEntry->pNext = NULL;
308 pdmacFileCacheListSub(pList, pEntry->cbData);
309#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
310 pdmacFileCacheCheckList(pList, pEntry);
311#endif
312}
313
314/**
315 * Adds a cache entry to the given LRU list unlinking it from the currently
316 * assigned list if needed.
317 *
318 * @returns nothing.
319 * @param pList List to the add entry to.
320 * @param pEntry Entry to add.
321 */
322static void pdmacFileCacheEntryAddToList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pEntry)
323{
324 LogFlowFunc((": Adding entry %#p to list %#p\n", pEntry, pList));
325#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
326 pdmacFileCacheCheckList(pList, NULL);
327#endif
328
329 /* Remove from old list if needed */
330 if (pEntry->pList)
331 pdmacFileCacheEntryRemoveFromList(pEntry);
332
333 pEntry->pNext = pList->pHead;
334 if (pList->pHead)
335 pList->pHead->pPrev = pEntry;
336 else
337 {
338 Assert(!pList->pTail);
339 pList->pTail = pEntry;
340 }
341
342 pEntry->pPrev = NULL;
343 pList->pHead = pEntry;
344 pdmacFileCacheListAdd(pList, pEntry->cbData);
345 pEntry->pList = pList;
346#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
347 pdmacFileCacheCheckList(pList, NULL);
348#endif
349}
350
351/**
352 * Destroys a LRU list freeing all entries.
353 *
354 * @returns nothing
355 * @param pList Pointer to the LRU list to destroy.
356 *
357 * @note The caller must own the critical section of the cache.
358 */
359static void pdmacFileCacheDestroyList(PPDMACFILELRULIST pList)
360{
361 while (pList->pHead)
362 {
363 PPDMACFILECACHEENTRY pEntry = pList->pHead;
364
365 pList->pHead = pEntry->pNext;
366
367 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
368 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
369
370 RTMemPageFree(pEntry->pbData);
371 RTMemFree(pEntry);
372 }
373}
374
375/**
376 * Tries to remove the given amount of bytes from a given list in the cache
377 * moving the entries to one of the given ghosts lists
378 *
379 * @returns Amount of data which could be freed.
380 * @param pCache Pointer to the global cache data.
381 * @param cbData The amount of the data to free.
382 * @param pListSrc The source list to evict data from.
383 * @param pGhostListSrc The ghost list removed entries should be moved to
384 * NULL if the entry should be freed.
385 * @param fReuseBuffer Flag whether a buffer should be reused if it has the same size
386 * @param ppbBuf Where to store the address of the buffer if an entry with the
387 * same size was found and fReuseBuffer is true.
388 *
389 * @note This function may return fewer bytes than requested because entries
390 * may be marked as non evictable if they are used for I/O at the
391 * moment.
392 */
393static size_t pdmacFileCacheEvictPagesFrom(PPDMACFILECACHEGLOBAL pCache, size_t cbData,
394 PPDMACFILELRULIST pListSrc, PPDMACFILELRULIST pGhostListDst,
395 bool fReuseBuffer, uint8_t **ppbBuffer)
396{
397 size_t cbEvicted = 0;
398
399 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
400
401 AssertMsg(cbData > 0, ("Evicting 0 bytes not possible\n"));
402 AssertMsg( !pGhostListDst
403 || (pGhostListDst == &pCache->LruRecentlyUsedOut),
404 ("Destination list must be NULL or the recently used but paged out list\n"));
405
406 if (fReuseBuffer)
407 {
408 AssertPtr(ppbBuffer);
409 *ppbBuffer = NULL;
410 }
411
412 /* Start deleting from the tail. */
413 PPDMACFILECACHEENTRY pEntry = pListSrc->pTail;
414
415 while ((cbEvicted < cbData) && pEntry)
416 {
417 PPDMACFILECACHEENTRY pCurr = pEntry;
418
419 pEntry = pEntry->pPrev;
420
421 /* We can't evict pages which are currently in progress or dirty but not in progress */
422 if ( !(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
423 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
424 {
425 /* Ok eviction candidate. Grab the endpoint semaphore and check again
426 * because somebody else might have raced us. */
427 PPDMACFILEENDPOINTCACHE pEndpointCache = &pCurr->pEndpoint->DataCache;
428 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
429
430 if (!(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
431 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
432 {
433 LogFlow(("Evicting entry %#p (%u bytes)\n", pCurr, pCurr->cbData));
434
435 if (fReuseBuffer && (pCurr->cbData == cbData))
436 {
437 STAM_COUNTER_INC(&pCache->StatBuffersReused);
438 *ppbBuffer = pCurr->pbData;
439 }
440 else if (pCurr->pbData)
441 RTMemPageFree(pCurr->pbData);
442
443 pCurr->pbData = NULL;
444 cbEvicted += pCurr->cbData;
445
446 pdmacFileCacheEntryRemoveFromList(pCurr);
447 pdmacFileCacheSub(pCache, pCurr->cbData);
448
449 if (pGhostListDst)
450 {
451 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
452
453 PPDMACFILECACHEENTRY pGhostEntFree = pGhostListDst->pTail;
454
455 /* We have to remove the last entries from the paged out list. */
456 while ( ((pGhostListDst->cbCached + pCurr->cbData) > pCache->cbRecentlyUsedOutMax)
457 && pGhostEntFree)
458 {
459 PPDMACFILECACHEENTRY pFree = pGhostEntFree;
460 PPDMACFILEENDPOINTCACHE pEndpointCacheFree = &pFree->pEndpoint->DataCache;
461
462 pGhostEntFree = pGhostEntFree->pPrev;
463
464 RTSemRWRequestWrite(pEndpointCacheFree->SemRWEntries, RT_INDEFINITE_WAIT);
465
466 if (ASMAtomicReadU32(&pFree->cRefs) == 0)
467 {
468 pdmacFileCacheEntryRemoveFromList(pFree);
469
470 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
471 RTAvlrFileOffsetRemove(pEndpointCacheFree->pTree, pFree->Core.Key);
472 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
473
474 RTMemFree(pFree);
475 }
476
477 RTSemRWReleaseWrite(pEndpointCacheFree->SemRWEntries);
478 }
479
480 if (pGhostListDst->cbCached + pCurr->cbData > pCache->cbRecentlyUsedOutMax)
481 {
482 /* Couldn't remove enough entries. Delete */
483 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
484 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
485 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
486
487 RTMemFree(pCurr);
488 }
489 else
490 pdmacFileCacheEntryAddToList(pGhostListDst, pCurr);
491 }
492 else
493 {
494 /* Delete the entry from the AVL tree it is assigned to. */
495 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
496 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
497 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
498
499 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
500 RTMemFree(pCurr);
501 }
502 }
503
504 }
505 else
506 LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
507 }
508
509 return cbEvicted;
510}
511
512static bool pdmacFileCacheReclaim(PPDMACFILECACHEGLOBAL pCache, size_t cbData, bool fReuseBuffer, uint8_t **ppbBuffer)
513{
514 size_t cbRemoved = 0;
515
516 if ((pCache->cbCached + cbData) < pCache->cbMax)
517 return true;
518 else if ((pCache->LruRecentlyUsedIn.cbCached + cbData) > pCache->cbRecentlyUsedInMax)
519 {
520 /* Try to evict as many bytes as possible from A1in */
521 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruRecentlyUsedIn,
522 &pCache->LruRecentlyUsedOut, fReuseBuffer, ppbBuffer);
523
524 /*
525 * If it was not possible to remove enough entries
526 * try the frequently accessed cache.
527 */
528 if (cbRemoved < cbData)
529 {
530 Assert(!fReuseBuffer || !*ppbBuffer); /* It is not possible that we got a buffer with the correct size but we didn't freed enough data. */
531
532 /*
533 * If we removed something we can't pass the reuse buffer flag anymore because
534 * we don't need to evict that much data
535 */
536 if (!cbRemoved)
537 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
538 NULL, fReuseBuffer, ppbBuffer);
539 else
540 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData - cbRemoved, &pCache->LruFrequentlyUsed,
541 NULL, false, NULL);
542 }
543 }
544 else
545 {
546 /* We have to remove entries from frequently access list. */
547 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
548 NULL, fReuseBuffer, ppbBuffer);
549 }
550
551 LogFlowFunc((": removed %u bytes, requested %u\n", cbRemoved, cbData));
552 return (cbRemoved >= cbData);
553}
554
555/**
556 * Initiates a read I/O task for the given entry.
557 *
558 * @returns nothing.
559 * @param pEntry The entry to fetch the data to.
560 */
561static void pdmacFileCacheReadFromEndpoint(PPDMACFILECACHEENTRY pEntry)
562{
563 LogFlowFunc((": Reading data into cache entry %#p\n", pEntry));
564
565 /* Make sure no one evicts the entry while it is accessed. */
566 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
567
568 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
569 AssertPtr(pIoTask);
570
571 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
572
573 pIoTask->pEndpoint = pEntry->pEndpoint;
574 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_READ;
575 pIoTask->Off = pEntry->Core.Key;
576 pIoTask->DataSeg.cbSeg = pEntry->cbData;
577 pIoTask->DataSeg.pvSeg = pEntry->pbData;
578 pIoTask->pvUser = pEntry;
579 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
580
581 /* Send it off to the I/O manager. */
582 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
583}
584
585/**
586 * Initiates a write I/O task for the given entry.
587 *
588 * @returns nothing.
589 * @param pEntry The entry to read the data from.
590 */
591static void pdmacFileCacheWriteToEndpoint(PPDMACFILECACHEENTRY pEntry)
592{
593 LogFlowFunc((": Writing data from cache entry %#p\n", pEntry));
594
595 /* Make sure no one evicts the entry while it is accessed. */
596 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
597
598 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
599 AssertPtr(pIoTask);
600
601 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
602
603 pIoTask->pEndpoint = pEntry->pEndpoint;
604 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_WRITE;
605 pIoTask->Off = pEntry->Core.Key;
606 pIoTask->DataSeg.cbSeg = pEntry->cbData;
607 pIoTask->DataSeg.pvSeg = pEntry->pbData;
608 pIoTask->pvUser = pEntry;
609 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
610 ASMAtomicIncU32(&pEntry->pEndpoint->DataCache.cWritesOutstanding);
611
612 /* Send it off to the I/O manager. */
613 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
614}
615
616/**
617 * Commit a single dirty entry to the endpoint
618 *
619 * @returns nothing
620 * @param pEntry The entry to commit.
621 */
622static void pdmacFileCacheEntryCommit(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
623{
624 NOREF(pEndpointCache);
625 AssertMsg( (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
626 && !(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS),
627 ("Invalid flags set for entry %#p\n", pEntry));
628
629 pdmacFileCacheWriteToEndpoint(pEntry);
630}
631
632/**
633 * Commit all dirty entries for a single endpoint.
634 *
635 * @returns nothing.
636 * @param pEndpointCache The endpoint cache to commit.
637 */
638static void pdmacFileCacheEndpointCommit(PPDMACFILEENDPOINTCACHE pEndpointCache)
639{
640 uint32_t cbCommitted = 0;
641 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
642
643 /* The list is moved to a new header to reduce locking overhead. */
644 RTLISTNODE ListDirtyNotCommitted;
645 RTSPINLOCKTMP Tmp;
646
647 RTListInit(&ListDirtyNotCommitted);
648 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
649 RTListMove(&ListDirtyNotCommitted, &pEndpointCache->ListDirtyNotCommitted);
650 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
651
652 if (!RTListIsEmpty(&ListDirtyNotCommitted))
653 {
654 PPDMACFILECACHEENTRY pEntry = RTListNodeGetFirst(&ListDirtyNotCommitted,
655 PDMACFILECACHEENTRY,
656 NodeNotCommitted);
657
658 while (!RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted))
659 {
660 PPDMACFILECACHEENTRY pNext = RTListNodeGetNext(&pEntry->NodeNotCommitted, PDMACFILECACHEENTRY,
661 NodeNotCommitted);
662 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
663 cbCommitted += pEntry->cbData;
664 RTListNodeRemove(&pEntry->NodeNotCommitted);
665 pEntry = pNext;
666 }
667
668 /* Commit the last endpoint */
669 Assert(RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted));
670 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
671 RTListNodeRemove(&pEntry->NodeNotCommitted);
672 AssertMsg(RTListIsEmpty(&ListDirtyNotCommitted),
673 ("Committed all entries but list is not empty\n"));
674 }
675
676 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
677 AssertMsg(pEndpointCache->pCache->cbDirty >= cbCommitted,
678 ("Number of committed bytes exceeds number of dirty bytes\n"));
679 ASMAtomicSubU32(&pEndpointCache->pCache->cbDirty, cbCommitted);
680}
681
682/**
683 * Commit all dirty entries in the cache.
684 *
685 * @returns nothing.
686 * @param pCache The global cache instance.
687 */
688static void pdmacFileCacheCommitDirtyEntries(PPDMACFILECACHEGLOBAL pCache)
689{
690 bool fCommitInProgress = ASMAtomicXchgBool(&pCache->fCommitInProgress, true);
691
692 if (!fCommitInProgress)
693 {
694 pdmacFileCacheLockEnter(pCache);
695 Assert(!RTListIsEmpty(&pCache->ListEndpoints));
696
697 PPDMACFILEENDPOINTCACHE pEndpointCache = RTListNodeGetFirst(&pCache->ListEndpoints,
698 PDMACFILEENDPOINTCACHE,
699 NodeCacheEndpoint);
700 AssertPtr(pEndpointCache);
701
702 while (!RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint))
703 {
704 pdmacFileCacheEndpointCommit(pEndpointCache);
705
706 pEndpointCache = RTListNodeGetNext(&pEndpointCache->NodeCacheEndpoint, PDMACFILEENDPOINTCACHE,
707 NodeCacheEndpoint);
708 }
709
710 /* Commit the last endpoint */
711 Assert(RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint));
712 pdmacFileCacheEndpointCommit(pEndpointCache);
713
714 pdmacFileCacheLockLeave(pCache);
715 ASMAtomicWriteBool(&pCache->fCommitInProgress, false);
716 }
717}
718
719/**
720 * Adds the given entry as a dirty to the cache.
721 *
722 * @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
723 * @param pEndpointCache The endpoint cache the entry belongs to.
724 * @param pEntry The entry to add.
725 */
726static bool pdmacFileCacheAddDirtyEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
727{
728 bool fDirtyBytesExceeded = false;
729 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
730
731 /* If the commit timer is disabled we commit right away. */
732 if (pCache->u32CommitTimeoutMs == 0)
733 {
734 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
735 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
736 }
737 else if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY))
738 {
739 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
740
741 RTSPINLOCKTMP Tmp;
742 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
743 RTListAppend(&pEndpointCache->ListDirtyNotCommitted, &pEntry->NodeNotCommitted);
744 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
745
746 uint32_t cbDirty = ASMAtomicAddU32(&pCache->cbDirty, pEntry->cbData);
747
748 fDirtyBytesExceeded = (cbDirty >= pCache->cbCommitDirtyThreshold);
749 }
750
751 return fDirtyBytesExceeded;
752}
753
754
755/**
756 * Completes a task segment freeing all ressources and completes the task handle
757 * if everything was transfered.
758 *
759 * @returns Next task segment handle.
760 * @param pEndpointCache The endpoint cache.
761 * @param pTaskSeg Task segment to complete.
762 * @param rc Status code to set.
763 */
764static PPDMACFILETASKSEG pdmacFileCacheTaskComplete(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILETASKSEG pTaskSeg, int rc)
765{
766 PPDMACFILETASKSEG pNext = pTaskSeg->pNext;
767 PPDMASYNCCOMPLETIONTASKFILE pTaskFile = pTaskSeg->pTask;
768
769 if (RT_FAILURE(rc))
770 ASMAtomicCmpXchgS32(&pTaskFile->rc, rc, VINF_SUCCESS);
771
772 uint32_t uOld = ASMAtomicSubS32(&pTaskFile->cbTransferLeft, pTaskSeg->cbTransfer);
773 AssertMsg(uOld >= pTaskSeg->cbTransfer, ("New value would overflow\n"));
774 if (!(uOld - pTaskSeg->cbTransfer)
775 && !ASMAtomicXchgBool(&pTaskFile->fCompleted, true))
776 pdmR3AsyncCompletionCompleteTask(&pTaskFile->Core, pTaskFile->rc, true);
777
778 RTMemFree(pTaskSeg);
779
780 return pNext;
781}
782
783/**
784 * Completion callback for I/O tasks.
785 *
786 * @returns nothing.
787 * @param pTask The completed task.
788 * @param pvUser Opaque user data.
789 * @param rc Status code of the completed request.
790 */
791static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc)
792{
793 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pvUser;
794 PPDMACFILECACHEGLOBAL pCache = pEntry->pCache;
795 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint = pEntry->pEndpoint;
796 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
797
798 /* Reference the entry now as we are clearing the I/O in progres flag
799 * which protects the entry till now. */
800 pdmacFileEpCacheEntryRef(pEntry);
801
802 RTSemRWRequestWrite(pEndpoint->DataCache.SemRWEntries, RT_INDEFINITE_WAIT);
803 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
804
805 /* Process waiting segment list. The data in entry might have changed inbetween. */
806 bool fDirty = false;
807 PPDMACFILETASKSEG pCurr = pEntry->pWaitingHead;
808
809 AssertMsg((pCurr && pEntry->pWaitingTail) || (!pCurr && !pEntry->pWaitingTail),
810 ("The list tail was not updated correctly\n"));
811 pEntry->pWaitingTail = NULL;
812 pEntry->pWaitingHead = NULL;
813
814 if (pTask->enmTransferType == PDMACTASKFILETRANSFER_WRITE)
815 {
816 AssertMsg(pEndpointCache->cWritesOutstanding > 0, ("Completed write request but outstanding task count is 0\n"));
817 ASMAtomicDecU32(&pEndpointCache->cWritesOutstanding);
818
819 /*
820 * An error here is difficult to handle as the original request completed already.
821 * The error is logged for now and the VM is paused.
822 * If the user continues the entry is written again in the hope
823 * the user fixed the problem and the next write succeeds.
824 */
825 /** @todo r=aeichner: This solution doesn't work
826 * The user will get the message but the VM will hang afterwards
827 * VMR3Suspend() returns when the VM is suspended but suspending
828 * the VM will reopen the images readonly in DrvVD. They are closed first
829 * which will close the endpoints. This will block EMT while the
830 * I/O manager processes the close request but the IO manager is stuck
831 * in the VMR3Suspend call and can't process the request.
832 * Another problem is that closing the VM means flushing the cache
833 * but the entry failed and will probably fail again.
834 * No idea so far how to solve this problem... but the user gets informed
835 * at least.
836 */
837 if (RT_FAILURE(rc))
838 {
839 LogRel(("I/O cache: Error while writing entry at offset %RTfoff (%u bytes) to file \"%s\"\n",
840 pEntry->Core.Key, pEntry->cbData, pEndpoint->Core.pszUri));
841
842 rc = VMSetRuntimeError(pEndpoint->Core.pEpClass->pVM, 0, "CACHE_IOERR",
843 N_("The I/O cache encountered an error while updating data in file \"%s\" (rc=%Rrc). Make sure there is enough free space on the disk and that the disk is working properly. Operation can be resumed afterwards."), pEndpoint->Core.pszUri, rc);
844 AssertRC(rc);
845 rc = VMR3Suspend(pEndpoint->Core.pEpClass->pVM);
846 }
847 else
848 {
849 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IS_DIRTY;
850
851 while (pCurr)
852 {
853 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
854
855 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
856 fDirty = true;
857
858 pCurr = pdmacFileCacheTaskComplete(pEndpointCache, pCurr, VINF_SUCCESS);
859 }
860 }
861 }
862 else
863 {
864 AssertMsg(pTask->enmTransferType == PDMACTASKFILETRANSFER_READ, ("Invalid transfer type\n"));
865 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY),
866 ("Invalid flags set\n"));
867
868 while (pCurr)
869 {
870 if (pCurr->fWrite)
871 {
872 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
873 fDirty = true;
874 }
875 else
876 memcpy(pCurr->pvBuf, pEntry->pbData + pCurr->uBufOffset, pCurr->cbTransfer);
877
878 pCurr = pdmacFileCacheTaskComplete(pEndpointCache, pCurr, rc);
879 }
880 }
881
882 bool fCommit = false;
883 if (fDirty)
884 fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
885
886 /* Complete a pending flush if all writes have completed */
887 if (!ASMAtomicReadU32(&pEndpointCache->cWritesOutstanding))
888 {
889 PPDMASYNCCOMPLETIONTASKFILE pTaskFlush = (PPDMASYNCCOMPLETIONTASKFILE)ASMAtomicXchgPtr((void * volatile *)&pEndpointCache->pTaskFlush, NULL);
890 if (pTaskFlush)
891 pdmR3AsyncCompletionCompleteTask(&pTaskFlush->Core, VINF_SUCCESS, true);
892 }
893
894 RTSemRWReleaseWrite(pEndpoint->DataCache.SemRWEntries);
895
896 /* Dereference so that it isn't protected anymore except we issued anyother write for it. */
897 pdmacFileEpCacheEntryRelease(pEntry);
898
899 if (fCommit)
900 pdmacFileCacheCommitDirtyEntries(pCache);
901}
902
903/**
904 * Commit timer callback.
905 */
906static void pdmacFileCacheCommitTimerCallback(PVM pVM, PTMTIMER pTimer, void *pvUser)
907{
908 PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pvUser;
909 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
910
911 LogFlowFunc(("Commit interval expired, commiting dirty entries\n"));
912
913 if (ASMAtomicReadU32(&pCache->cbDirty) > 0)
914 pdmacFileCacheCommitDirtyEntries(pCache);
915
916 TMTimerSetMillies(pTimer, pCache->u32CommitTimeoutMs);
917 LogFlowFunc(("Entries committed, going to sleep\n"));
918}
919
920/**
921 * Initializies the I/O cache.
922 *
923 * returns VBox status code.
924 * @param pClassFile The global class data for file endpoints.
925 * @param pCfgNode CFGM node to query configuration data from.
926 */
927int pdmacFileCacheInit(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile, PCFGMNODE pCfgNode)
928{
929 int rc = VINF_SUCCESS;
930 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
931
932 rc = CFGMR3QueryU32Def(pCfgNode, "CacheSize", &pCache->cbMax, 5 * _1M);
933 AssertLogRelRCReturn(rc, rc);
934
935 RTListInit(&pCache->ListEndpoints);
936 pCache->cRefs = 0;
937 pCache->cbCached = 0;
938 pCache->fCommitInProgress = 0;
939 LogFlowFunc((": Maximum number of bytes cached %u\n", pCache->cbMax));
940
941 /* Initialize members */
942 pCache->LruRecentlyUsedIn.pHead = NULL;
943 pCache->LruRecentlyUsedIn.pTail = NULL;
944 pCache->LruRecentlyUsedIn.cbCached = 0;
945
946 pCache->LruRecentlyUsedOut.pHead = NULL;
947 pCache->LruRecentlyUsedOut.pTail = NULL;
948 pCache->LruRecentlyUsedOut.cbCached = 0;
949
950 pCache->LruFrequentlyUsed.pHead = NULL;
951 pCache->LruFrequentlyUsed.pTail = NULL;
952 pCache->LruFrequentlyUsed.cbCached = 0;
953
954 pCache->cbRecentlyUsedInMax = (pCache->cbMax / 100) * 25; /* 25% of the buffer size */
955 pCache->cbRecentlyUsedOutMax = (pCache->cbMax / 100) * 50; /* 50% of the buffer size */
956 LogFlowFunc((": cbRecentlyUsedInMax=%u cbRecentlyUsedOutMax=%u\n", pCache->cbRecentlyUsedInMax, pCache->cbRecentlyUsedOutMax));
957
958 /** @todo r=aeichner: Experiment to find optimal default values */
959 rc = CFGMR3QueryU32Def(pCfgNode, "CacheCommitIntervalMs", &pCache->u32CommitTimeoutMs, 10000 /* 10sec */);
960 AssertLogRelRCReturn(rc, rc);
961 rc = CFGMR3QueryU32(pCfgNode, "CacheCommitThreshold", &pCache->cbCommitDirtyThreshold);
962 if ( rc == VERR_CFGM_VALUE_NOT_FOUND
963 || rc == VERR_CFGM_NO_PARENT)
964 {
965 /* Start committing after 50% of the cache are dirty */
966 pCache->cbCommitDirtyThreshold = pCache->cbMax / 2;
967 }
968 else
969 return rc;
970
971 STAMR3Register(pClassFile->Core.pVM, &pCache->cbMax,
972 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
973 "/PDM/AsyncCompletion/File/cbMax",
974 STAMUNIT_BYTES,
975 "Maximum cache size");
976 STAMR3Register(pClassFile->Core.pVM, &pCache->cbCached,
977 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
978 "/PDM/AsyncCompletion/File/cbCached",
979 STAMUNIT_BYTES,
980 "Currently used cache");
981 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedIn.cbCached,
982 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
983 "/PDM/AsyncCompletion/File/cbCachedMruIn",
984 STAMUNIT_BYTES,
985 "Number of bytes cached in MRU list");
986 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedOut.cbCached,
987 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
988 "/PDM/AsyncCompletion/File/cbCachedMruOut",
989 STAMUNIT_BYTES,
990 "Number of bytes cached in FRU list");
991 STAMR3Register(pClassFile->Core.pVM, &pCache->LruFrequentlyUsed.cbCached,
992 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
993 "/PDM/AsyncCompletion/File/cbCachedFru",
994 STAMUNIT_BYTES,
995 "Number of bytes cached in FRU ghost list");
996
997#ifdef VBOX_WITH_STATISTICS
998 STAMR3Register(pClassFile->Core.pVM, &pCache->cHits,
999 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1000 "/PDM/AsyncCompletion/File/CacheHits",
1001 STAMUNIT_COUNT, "Number of hits in the cache");
1002 STAMR3Register(pClassFile->Core.pVM, &pCache->cPartialHits,
1003 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1004 "/PDM/AsyncCompletion/File/CachePartialHits",
1005 STAMUNIT_COUNT, "Number of partial hits in the cache");
1006 STAMR3Register(pClassFile->Core.pVM, &pCache->cMisses,
1007 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1008 "/PDM/AsyncCompletion/File/CacheMisses",
1009 STAMUNIT_COUNT, "Number of misses when accessing the cache");
1010 STAMR3Register(pClassFile->Core.pVM, &pCache->StatRead,
1011 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1012 "/PDM/AsyncCompletion/File/CacheRead",
1013 STAMUNIT_BYTES, "Number of bytes read from the cache");
1014 STAMR3Register(pClassFile->Core.pVM, &pCache->StatWritten,
1015 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1016 "/PDM/AsyncCompletion/File/CacheWritten",
1017 STAMUNIT_BYTES, "Number of bytes written to the cache");
1018 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeGet,
1019 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1020 "/PDM/AsyncCompletion/File/CacheTreeGet",
1021 STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
1022 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeInsert,
1023 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1024 "/PDM/AsyncCompletion/File/CacheTreeInsert",
1025 STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
1026 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeRemove,
1027 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1028 "/PDM/AsyncCompletion/File/CacheTreeRemove",
1029 STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
1030 STAMR3Register(pClassFile->Core.pVM, &pCache->StatBuffersReused,
1031 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1032 "/PDM/AsyncCompletion/File/CacheBuffersReused",
1033 STAMUNIT_COUNT, "Number of times a buffer could be reused");
1034#endif
1035
1036 /* Initialize the critical section */
1037 rc = RTCritSectInit(&pCache->CritSect);
1038
1039 if (RT_SUCCESS(rc))
1040 {
1041 /* Create the commit timer */
1042 if (pCache->u32CommitTimeoutMs > 0)
1043 rc = TMR3TimerCreateInternal(pClassFile->Core.pVM, TMCLOCK_REAL,
1044 pdmacFileCacheCommitTimerCallback,
1045 pClassFile,
1046 "Cache-Commit",
1047 &pClassFile->Cache.pTimerCommit);
1048
1049 if (RT_SUCCESS(rc))
1050 {
1051 LogRel(("AIOMgr: Cache successfully initialised. Cache size is %u bytes\n", pCache->cbMax));
1052 LogRel(("AIOMgr: Cache commit interval is %u ms\n", pCache->u32CommitTimeoutMs));
1053 LogRel(("AIOMgr: Cache commit threshold is %u bytes\n", pCache->cbCommitDirtyThreshold));
1054 return VINF_SUCCESS;
1055 }
1056
1057 RTCritSectDelete(&pCache->CritSect);
1058 }
1059
1060 return rc;
1061}
1062
1063/**
1064 * Destroysthe cache freeing all data.
1065 *
1066 * returns nothing.
1067 * @param pClassFile The global class data for file endpoints.
1068 */
1069void pdmacFileCacheDestroy(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1070{
1071 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
1072
1073 /* Make sure no one else uses the cache now */
1074 pdmacFileCacheLockEnter(pCache);
1075
1076 /* Cleanup deleting all cache entries waiting for in progress entries to finish. */
1077 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedIn);
1078 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedOut);
1079 pdmacFileCacheDestroyList(&pCache->LruFrequentlyUsed);
1080
1081 pdmacFileCacheLockLeave(pCache);
1082
1083 RTCritSectDelete(&pCache->CritSect);
1084}
1085
1086/**
1087 * Initializes per endpoint cache data
1088 * like the AVL tree used to access cached entries.
1089 *
1090 * @returns VBox status code.
1091 * @param pEndpoint The endpoint to init the cache for,
1092 * @param pClassFile The global class data for file endpoints.
1093 */
1094int pdmacFileEpCacheInit(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1095{
1096 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1097
1098 pEndpointCache->pCache = &pClassFile->Cache;
1099 RTListInit(&pEndpointCache->ListDirtyNotCommitted);
1100 int rc = RTSpinlockCreate(&pEndpointCache->LockList);
1101
1102 if (RT_SUCCESS(rc))
1103 {
1104 rc = RTSemRWCreate(&pEndpointCache->SemRWEntries);
1105 if (RT_SUCCESS(rc))
1106 {
1107 pEndpointCache->pTree = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
1108 if (pEndpointCache->pTree)
1109 {
1110 pClassFile->Cache.cRefs++;
1111 RTListAppend(&pClassFile->Cache.ListEndpoints, &pEndpointCache->NodeCacheEndpoint);
1112
1113 /* Arm the timer if this is the first endpoint. */
1114 if ( pClassFile->Cache.cRefs == 1
1115 && pClassFile->Cache.u32CommitTimeoutMs > 0)
1116 rc = TMTimerSetMillies(pClassFile->Cache.pTimerCommit, pClassFile->Cache.u32CommitTimeoutMs);
1117 }
1118 else
1119 rc = VERR_NO_MEMORY;
1120
1121 if (RT_FAILURE(rc))
1122 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1123 }
1124
1125 if (RT_FAILURE(rc))
1126 RTSpinlockDestroy(pEndpointCache->LockList);
1127 }
1128
1129#ifdef VBOX_WITH_STATISTICS
1130 if (RT_SUCCESS(rc))
1131 {
1132 STAMR3RegisterF(pClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred,
1133 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1134 STAMUNIT_COUNT, "Number of deferred writes",
1135 "/PDM/AsyncCompletion/File/%s/Cache/DeferredWrites", RTPathFilename(pEndpoint->Core.pszUri));
1136 }
1137#endif
1138
1139 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1140 return rc;
1141}
1142
1143/**
1144 * Callback for the AVL destroy routine. Frees a cache entry for this endpoint.
1145 *
1146 * @returns IPRT status code.
1147 * @param pNode The node to destroy.
1148 * @param pvUser Opaque user data.
1149 */
1150static int pdmacFileEpCacheEntryDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
1151{
1152 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pNode;
1153 PPDMACFILECACHEGLOBAL pCache = (PPDMACFILECACHEGLOBAL)pvUser;
1154 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEntry->pEndpoint->DataCache;
1155
1156 while (ASMAtomicReadU32(&pEntry->fFlags) & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY))
1157 {
1158 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1159 RTThreadSleep(250);
1160 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1161 }
1162
1163 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
1164 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
1165
1166 bool fUpdateCache = pEntry->pList == &pCache->LruFrequentlyUsed
1167 || pEntry->pList == &pCache->LruRecentlyUsedIn;
1168
1169 pdmacFileCacheEntryRemoveFromList(pEntry);
1170
1171 if (fUpdateCache)
1172 pdmacFileCacheSub(pCache, pEntry->cbData);
1173
1174 RTMemPageFree(pEntry->pbData);
1175 RTMemFree(pEntry);
1176
1177 return VINF_SUCCESS;
1178}
1179
1180/**
1181 * Destroys all cache ressources used by the given endpoint.
1182 *
1183 * @returns nothing.
1184 * @param pEndpoint The endpoint to the destroy.
1185 */
1186void pdmacFileEpCacheDestroy(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint)
1187{
1188 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1189 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1190
1191 /* Make sure nobody is accessing the cache while we delete the tree. */
1192 pdmacFileCacheLockEnter(pCache);
1193 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1194 RTAvlrFileOffsetDestroy(pEndpointCache->pTree, pdmacFileEpCacheEntryDestroy, pCache);
1195 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1196
1197 RTSpinlockDestroy(pEndpointCache->LockList);
1198
1199 pCache->cRefs--;
1200 RTListNodeRemove(&pEndpointCache->NodeCacheEndpoint);
1201
1202 if ( !pCache->cRefs
1203 && pCache->u32CommitTimeoutMs > 0)
1204 TMTimerStop(pCache->pTimerCommit);
1205
1206 pdmacFileCacheLockLeave(pCache);
1207
1208 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1209
1210#ifdef VBOX_WITH_STATISTICS
1211 PPDMASYNCCOMPLETIONEPCLASSFILE pEpClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pEndpoint->Core.pEpClass;
1212
1213 STAMR3Deregister(pEpClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred);
1214#endif
1215}
1216
1217static PPDMACFILECACHEENTRY pdmacFileEpCacheGetCacheEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off)
1218{
1219 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1220 PPDMACFILECACHEENTRY pEntry = NULL;
1221
1222 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1223
1224 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1225 pEntry = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetRangeGet(pEndpointCache->pTree, off);
1226 if (pEntry)
1227 pdmacFileEpCacheEntryRef(pEntry);
1228 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1229
1230 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1231
1232 return pEntry;
1233}
1234
1235/**
1236 * Return the best fit cache entries for the given offset.
1237 *
1238 * @returns nothing.
1239 * @param pEndpointCache The endpoint cache.
1240 * @param off The offset.
1241 * @param pEntryAbove Where to store the pointer to the best fit entry above the
1242 * the given offset. NULL if not required.
1243 * @param pEntryBelow Where to store the pointer to the best fit entry below the
1244 * the given offset. NULL if not required.
1245 */
1246static void pdmacFileEpCacheGetCacheBestFitEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off,
1247 PPDMACFILECACHEENTRY *ppEntryAbove,
1248 PPDMACFILECACHEENTRY *ppEntryBelow)
1249{
1250 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1251
1252 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1253
1254 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1255 if (ppEntryAbove)
1256 {
1257 *ppEntryAbove = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, true /*fAbove*/);
1258 if (*ppEntryAbove)
1259 pdmacFileEpCacheEntryRef(*ppEntryAbove);
1260 }
1261
1262 if (ppEntryBelow)
1263 {
1264 *ppEntryBelow = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, false /*fAbove*/);
1265 if (*ppEntryBelow)
1266 pdmacFileEpCacheEntryRef(*ppEntryBelow);
1267 }
1268 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1269
1270 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1271}
1272
1273static void pdmacFileEpCacheInsertEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
1274{
1275 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1276
1277 STAM_PROFILE_ADV_START(&pCache->StatTreeInsert, Cache);
1278 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1279 bool fInserted = RTAvlrFileOffsetInsert(pEndpointCache->pTree, &pEntry->Core);
1280 AssertMsg(fInserted, ("Node was not inserted into tree\n"));
1281 STAM_PROFILE_ADV_STOP(&pCache->StatTreeInsert, Cache);
1282 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1283}
1284
1285/**
1286 * Allocates and initializes a new entry for the cache.
1287 * The entry has a reference count of 1.
1288 *
1289 * @returns Pointer to the new cache entry or NULL if out of memory.
1290 * @param pCache The cache the entry belongs to.
1291 * @param pEndoint The endpoint the entry holds data for.
1292 * @param off Start offset.
1293 * @param cbData Size of the cache entry.
1294 * @param pbBuffer Pointer to the buffer to use.
1295 * NULL if a new buffer should be allocated.
1296 * The buffer needs to have the same size of the entry.
1297 */
1298static PPDMACFILECACHEENTRY pdmacFileCacheEntryAlloc(PPDMACFILECACHEGLOBAL pCache,
1299 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1300 RTFOFF off, size_t cbData, uint8_t *pbBuffer)
1301{
1302 PPDMACFILECACHEENTRY pEntryNew = (PPDMACFILECACHEENTRY)RTMemAllocZ(sizeof(PDMACFILECACHEENTRY));
1303
1304 if (RT_UNLIKELY(!pEntryNew))
1305 return NULL;
1306
1307 pEntryNew->Core.Key = off;
1308 pEntryNew->Core.KeyLast = off + cbData - 1;
1309 pEntryNew->pEndpoint = pEndpoint;
1310 pEntryNew->pCache = pCache;
1311 pEntryNew->fFlags = 0;
1312 pEntryNew->cRefs = 1; /* We are using it now. */
1313 pEntryNew->pList = NULL;
1314 pEntryNew->cbData = cbData;
1315 pEntryNew->pWaitingHead = NULL;
1316 pEntryNew->pWaitingTail = NULL;
1317 if (pbBuffer)
1318 pEntryNew->pbData = pbBuffer;
1319 else
1320 pEntryNew->pbData = (uint8_t *)RTMemPageAlloc(cbData);
1321
1322 if (RT_UNLIKELY(!pEntryNew->pbData))
1323 {
1324 RTMemFree(pEntryNew);
1325 return NULL;
1326 }
1327
1328 return pEntryNew;
1329}
1330
1331/**
1332 * Adds a segment to the waiting list for a cache entry
1333 * which is currently in progress.
1334 *
1335 * @returns nothing.
1336 * @param pEntry The cache entry to add the segment to.
1337 * @param pSeg The segment to add.
1338 */
1339DECLINLINE(void) pdmacFileEpCacheEntryAddWaitingSegment(PPDMACFILECACHEENTRY pEntry, PPDMACFILETASKSEG pSeg)
1340{
1341 pSeg->pNext = NULL;
1342
1343 if (pEntry->pWaitingHead)
1344 {
1345 AssertPtr(pEntry->pWaitingTail);
1346
1347 pEntry->pWaitingTail->pNext = pSeg;
1348 pEntry->pWaitingTail = pSeg;
1349 }
1350 else
1351 {
1352 Assert(!pEntry->pWaitingTail);
1353
1354 pEntry->pWaitingHead = pSeg;
1355 pEntry->pWaitingTail = pSeg;
1356 }
1357}
1358
1359/**
1360 * Checks that a set of flags is set/clear acquiring the R/W semaphore
1361 * in exclusive mode.
1362 *
1363 * @returns true if the flag in fSet is set and the one in fClear is clear.
1364 * false othwerise.
1365 * The R/W semaphore is only held if true is returned.
1366 *
1367 * @param pEndpointCache The endpoint cache instance data.
1368 * @param pEntry The entry to check the flags for.
1369 * @param fSet The flag which is tested to be set.
1370 * @param fClear The flag which is tested to be clear.
1371 */
1372DECLINLINE(bool) pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(PPDMACFILEENDPOINTCACHE pEndpointCache,
1373 PPDMACFILECACHEENTRY pEntry,
1374 uint32_t fSet, uint32_t fClear)
1375{
1376 uint32_t fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1377 bool fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1378
1379 if (fPassed)
1380 {
1381 /* Acquire the lock and check again becuase the completion callback might have raced us. */
1382 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1383
1384 fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1385 fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1386
1387 /* Drop the lock if we didn't passed the test. */
1388 if (!fPassed)
1389 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1390 }
1391
1392 return fPassed;
1393}
1394
1395/**
1396 * Copies data to a buffer described by a I/O memory context.
1397 *
1398 * @returns nothing.
1399 * @param pIoMemCtx The I/O memory context to copy the data into.
1400 * @param pbData Pointer to the data data to copy.
1401 * @param cbData Amount of data to copy.
1402 */
1403static void pdmacFileEpCacheCopyToIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1404 uint8_t *pbData,
1405 size_t cbData)
1406{
1407 while (cbData)
1408 {
1409 size_t cbCopy = cbData;
1410 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1411
1412 AssertPtr(pbBuf);
1413
1414 memcpy(pbBuf, pbData, cbCopy);
1415
1416 cbData -= cbCopy;
1417 pbData += cbCopy;
1418 }
1419}
1420
1421/**
1422 * Copies data from a buffer described by a I/O memory context.
1423 *
1424 * @returns nothing.
1425 * @param pIoMemCtx The I/O memory context to copy the data from.
1426 * @param pbData Pointer to the destination buffer.
1427 * @param cbData Amount of data to copy.
1428 */
1429static void pdmacFileEpCacheCopyFromIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1430 uint8_t *pbData,
1431 size_t cbData)
1432{
1433 while (cbData)
1434 {
1435 size_t cbCopy = cbData;
1436 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1437
1438 AssertPtr(pbBuf);
1439
1440 memcpy(pbData, pbBuf, cbCopy);
1441
1442 cbData -= cbCopy;
1443 pbData += cbCopy;
1444 }
1445}
1446
1447/**
1448 * Add a buffer described by the I/O memory context
1449 * to the entry waiting for completion.
1450 *
1451 * @returns nothing.
1452 * @param pEntry The entry to add the buffer to.
1453 * @param pTask Task associated with the buffer.
1454 * @param pIoMemCtx The memory context to use.
1455 * @param OffDiff Offset from the start of the buffer
1456 * in the entry.
1457 * @param cbData Amount of data to wait for onthis entry.
1458 * @param fWrite Flag whether the task waits because it wants to write
1459 * to the cache entry.
1460 */
1461static void pdmacFileEpCacheEntryWaitersAdd(PPDMACFILECACHEENTRY pEntry,
1462 PPDMASYNCCOMPLETIONTASKFILE pTask,
1463 PPDMIOMEMCTX pIoMemCtx,
1464 RTFOFF OffDiff,
1465 size_t cbData,
1466 bool fWrite)
1467{
1468 while (cbData)
1469 {
1470 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
1471 size_t cbSeg = cbData;
1472 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1473
1474 pSeg->pTask = pTask;
1475 pSeg->uBufOffset = OffDiff;
1476 pSeg->cbTransfer = cbSeg;
1477 pSeg->pvBuf = pbBuf;
1478 pSeg->fWrite = fWrite;
1479
1480 pdmacFileEpCacheEntryAddWaitingSegment(pEntry, pSeg);
1481
1482 cbData -= cbSeg;
1483 OffDiff += cbSeg;
1484 }
1485}
1486
1487/**
1488 * Passthrough a part of a request directly to the I/O manager
1489 * handling the endpoint.
1490 *
1491 * @returns nothing.
1492 * @param pEndpoint The endpoint.
1493 * @param pTask The task.
1494 * @param pIoMemCtx The I/O memory context to use.
1495 * @param offStart Offset to start transfer from.
1496 * @param cbData Amount of data to transfer.
1497 * @param enmTransferType The transfer type (read/write)
1498 */
1499static void pdmacFileEpCacheRequestPassthrough(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1500 PPDMASYNCCOMPLETIONTASKFILE pTask,
1501 PPDMIOMEMCTX pIoMemCtx,
1502 RTFOFF offStart, size_t cbData,
1503 PDMACTASKFILETRANSFER enmTransferType)
1504{
1505 while (cbData)
1506 {
1507 size_t cbSeg = cbData;
1508 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1509 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEndpoint);
1510 AssertPtr(pIoTask);
1511
1512 pIoTask->pEndpoint = pEndpoint;
1513 pIoTask->enmTransferType = enmTransferType;
1514 pIoTask->Off = offStart;
1515 pIoTask->DataSeg.cbSeg = cbSeg;
1516 pIoTask->DataSeg.pvSeg = pbBuf;
1517 pIoTask->pvUser = pTask;
1518 pIoTask->pfnCompleted = pdmacFileEpTaskCompleted;
1519
1520 offStart += cbSeg;
1521 cbData -= cbSeg;
1522
1523 /* Send it off to the I/O manager. */
1524 pdmacFileEpAddTask(pEndpoint, pIoTask);
1525 }
1526}
1527
1528/**
1529 * Calculate aligned offset and size for a new cache entry
1530 * which do not intersect with an already existing entry and the
1531 * file end.
1532 *
1533 * @returns The number of bytes the entry can hold of the requested amount
1534 * of byte.
1535 * @param pEndpoint The endpoint.
1536 * @param pEndpointCache The endpoint cache.
1537 * @param off The start offset.
1538 * @param cb The number of bytes the entry needs to hold at least.
1539 * @param uAlignment Alignment of the boundary sizes.
1540 * @param poffAligned Where to store the aligned offset.
1541 * @param pcbAligned Where to store the aligned size of the entry.
1542 */
1543static size_t pdmacFileEpCacheEntryBoundariesCalc(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1544 PPDMACFILEENDPOINTCACHE pEndpointCache,
1545 RTFOFF off, size_t cb,
1546 unsigned uAlignment,
1547 RTFOFF *poffAligned, size_t *pcbAligned)
1548{
1549 size_t cbAligned;
1550 size_t cbInEntry = 0;
1551 RTFOFF offAligned;
1552 PPDMACFILECACHEENTRY pEntryAbove = NULL;
1553 PPDMACFILECACHEENTRY pEntryBelow = NULL;
1554
1555 /* Get the best fit entries around the offset */
1556 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1557 &pEntryAbove, &pEntryBelow);
1558
1559 /* Log the info */
1560 LogFlow(("%sest fit entry below off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1561 pEntryBelow ? "B" : "No b",
1562 off,
1563 pEntryBelow ? pEntryBelow->Core.Key : 0,
1564 pEntryBelow ? pEntryBelow->Core.KeyLast : 0,
1565 pEntryBelow ? pEntryBelow->cbData : 0));
1566
1567 LogFlow(("%sest fit entry above off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1568 pEntryAbove ? "B" : "No b",
1569 off,
1570 pEntryAbove ? pEntryAbove->Core.Key : 0,
1571 pEntryAbove ? pEntryAbove->Core.KeyLast : 0,
1572 pEntryAbove ? pEntryAbove->cbData : 0));
1573
1574 /* Align the offset first. */
1575 offAligned = off & ~(RTFOFF)(512-1);
1576 if ( pEntryBelow
1577 && offAligned <= pEntryBelow->Core.KeyLast)
1578 offAligned = pEntryBelow->Core.KeyLast;
1579
1580 if ( pEntryAbove
1581 && off + (RTFOFF)cb > pEntryAbove->Core.Key)
1582 {
1583 cbInEntry = pEntryAbove->Core.Key - off;
1584 cbAligned = pEntryAbove->Core.Key - offAligned;
1585 }
1586 else
1587 {
1588 /*
1589 * Align the size to a 4KB boundary.
1590 * Memory size is aligned to a page boundary
1591 * and memory is wasted if the size is rather small.
1592 * (For example reads with a size of 512 bytes).
1593 */
1594 cbInEntry = cb;
1595 cbAligned = RT_ALIGN_Z(cb + (off - offAligned), uAlignment);
1596
1597 /*
1598 * Clip to file size if the original request doesn't
1599 * exceed the file (not an appending write)
1600 */
1601 uint64_t cbReq = off + (RTFOFF)cb;
1602 if (cbReq >= pEndpoint->cbFile)
1603 cbAligned = cbReq - offAligned;
1604 else
1605 cbAligned = RT_MIN(pEndpoint->cbFile - offAligned, cbAligned);
1606 if (pEntryAbove)
1607 {
1608 Assert(pEntryAbove->Core.Key >= off);
1609 cbAligned = RT_MIN(cbAligned, (uint64_t)pEntryAbove->Core.Key - offAligned);
1610 }
1611 }
1612
1613 /* A few sanity checks */
1614 AssertMsg(!pEntryBelow || pEntryBelow->Core.KeyLast < offAligned,
1615 ("Aligned start offset intersects with another cache entry\n"));
1616 AssertMsg(!pEntryAbove || (offAligned + (RTFOFF)cbAligned) <= pEntryAbove->Core.Key,
1617 ("Aligned size intersects with another cache entry\n"));
1618 Assert(cbInEntry <= cbAligned);
1619 AssertMsg( ( offAligned + (RTFOFF)cbAligned <= (RTFOFF)pEndpoint->cbFile
1620 && off + (RTFOFF)cb <= (RTFOFF)pEndpoint->cbFile)
1621 || (offAligned + (RTFOFF)cbAligned <= off + (RTFOFF)cb),
1622 ("Unwanted file size increase\n"));
1623
1624 if (pEntryBelow)
1625 pdmacFileEpCacheEntryRelease(pEntryBelow);
1626 if (pEntryAbove)
1627 pdmacFileEpCacheEntryRelease(pEntryAbove);
1628
1629 LogFlow(("offAligned=%RTfoff cbAligned=%u\n", offAligned, cbAligned));
1630
1631 *poffAligned = offAligned;
1632 *pcbAligned = cbAligned;
1633
1634 return cbInEntry;
1635}
1636
1637/**
1638 * Create a new cache entry evicting data from the cache if required.
1639 *
1640 * @returns Pointer to the new cache entry or NULL
1641 * if not enough bytes could be evicted from the cache.
1642 * @param pEndpoint The endpoint.
1643 * @param pEndpointCache The endpoint cache.
1644 * @param off The offset.
1645 * @param cb Number of bytes the cache entry should have.
1646 * @param uAlignment Alignment the size of the entry should have.
1647 * @param pcbData Where to store the number of bytes the new
1648 * entry can hold. May be lower than actually requested
1649 * due to another entry intersecting the access range.
1650 */
1651static PPDMACFILECACHEENTRY pdmacFileEpCacheEntryCreate(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1652 PPDMACFILEENDPOINTCACHE pEndpointCache,
1653 RTFOFF off, size_t cb,
1654 unsigned uAlignment,
1655 size_t *pcbData)
1656{
1657 RTFOFF offStart = 0;
1658 size_t cbEntry = 0;
1659 PPDMACFILECACHEENTRY pEntryNew = NULL;
1660 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1661 uint8_t *pbBuffer = NULL;
1662
1663 *pcbData = pdmacFileEpCacheEntryBoundariesCalc(pEndpoint,
1664 pEndpointCache,
1665 off, cb,
1666 uAlignment,
1667 &offStart, &cbEntry);
1668
1669 pdmacFileCacheLockEnter(pCache);
1670 bool fEnough = pdmacFileCacheReclaim(pCache, cbEntry, true, &pbBuffer);
1671
1672 if (fEnough)
1673 {
1674 LogFlow(("Evicted enough bytes (%u requested). Creating new cache entry\n", cbEntry));
1675
1676 pEntryNew = pdmacFileCacheEntryAlloc(pCache, pEndpoint,
1677 offStart, cbEntry,
1678 pbBuffer);
1679 if (RT_LIKELY(pEntryNew))
1680 {
1681 pdmacFileCacheEntryAddToList(&pCache->LruRecentlyUsedIn, pEntryNew);
1682 pdmacFileCacheAdd(pCache, cbEntry);
1683 pdmacFileCacheLockLeave(pCache);
1684
1685 pdmacFileEpCacheInsertEntry(pEndpointCache, pEntryNew);
1686
1687 AssertMsg( (off >= pEntryNew->Core.Key)
1688 && (off + (RTFOFF)*pcbData <= pEntryNew->Core.KeyLast + 1),
1689 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1690 off, pEntryNew->Core.Key));
1691 }
1692 else
1693 pdmacFileCacheLockLeave(pCache);
1694 }
1695 else
1696 pdmacFileCacheLockLeave(pCache);
1697
1698 return pEntryNew;
1699}
1700
1701/**
1702 * Reads the specified data from the endpoint using the cache if possible.
1703 *
1704 * @returns VBox status code.
1705 * @param pEndpoint The endpoint to read from.
1706 * @param pTask The task structure used as identifier for this request.
1707 * @param off The offset to start reading from.
1708 * @param paSegments Pointer to the array holding the destination buffers.
1709 * @param cSegments Number of segments in the array.
1710 * @param cbRead Number of bytes to read.
1711 */
1712int pdmacFileEpCacheRead(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1713 RTFOFF off, PCPDMDATASEG paSegments, size_t cSegments,
1714 size_t cbRead)
1715{
1716 int rc = VINF_SUCCESS;
1717 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1718 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1719 PPDMACFILECACHEENTRY pEntry;
1720
1721 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbRead=%u\n",
1722 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbRead));
1723
1724 pTask->cbTransferLeft = cbRead;
1725 pTask->rc = VINF_SUCCESS;
1726 /* Set to completed to make sure that the task is valid while we access it. */
1727 ASMAtomicWriteBool(&pTask->fCompleted, true);
1728
1729 /* Init the I/O memory context */
1730 PDMIOMEMCTX IoMemCtx;
1731 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1732
1733 while (cbRead)
1734 {
1735 size_t cbToRead;
1736
1737 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1738
1739 /*
1740 * If there is no entry we try to create a new one eviciting unused pages
1741 * if the cache is full. If this is not possible we will pass the request through
1742 * and skip the caching (all entries may be still in progress so they can't
1743 * be evicted)
1744 * If we have an entry it can be in one of the LRU lists where the entry
1745 * contains data (recently used or frequently used LRU) so we can just read
1746 * the data we need and put the entry at the head of the frequently used LRU list.
1747 * In case the entry is in one of the ghost lists it doesn't contain any data.
1748 * We have to fetch it again evicting pages from either T1 or T2 to make room.
1749 */
1750 if (pEntry)
1751 {
1752 RTFOFF OffDiff = off - pEntry->Core.Key;
1753
1754 AssertMsg(off >= pEntry->Core.Key,
1755 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1756 off, pEntry->Core.Key));
1757
1758 AssertPtr(pEntry->pList);
1759
1760 cbToRead = RT_MIN(pEntry->cbData - OffDiff, cbRead);
1761
1762 AssertMsg(off + (RTFOFF)cbToRead <= pEntry->Core.Key + pEntry->Core.KeyLast + 1,
1763 ("Buffer of cache entry exceeded off=%RTfoff cbToRead=%d\n",
1764 off, cbToRead));
1765
1766 cbRead -= cbToRead;
1767
1768 if (!cbRead)
1769 STAM_COUNTER_INC(&pCache->cHits);
1770 else
1771 STAM_COUNTER_INC(&pCache->cPartialHits);
1772
1773 STAM_COUNTER_ADD(&pCache->StatRead, cbToRead);
1774
1775 /* Ghost lists contain no data. */
1776 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1777 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1778 {
1779 if (pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
1780 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
1781 PDMACFILECACHE_ENTRY_IS_DIRTY))
1782 {
1783 /* Entry didn't completed yet. Append to the list */
1784 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1785 &IoMemCtx,
1786 OffDiff, cbToRead,
1787 false /* fWrite */);
1788 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1789 }
1790 else
1791 {
1792 /* Read as much as we can from the entry. */
1793 pdmacFileEpCacheCopyToIoMemCtx(&IoMemCtx, pEntry->pbData + OffDiff, cbToRead);
1794 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToRead);
1795 }
1796
1797 /* Move this entry to the top position */
1798 if (pEntry->pList == &pCache->LruFrequentlyUsed)
1799 {
1800 pdmacFileCacheLockEnter(pCache);
1801 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1802 pdmacFileCacheLockLeave(pCache);
1803 }
1804 /* Release the entry */
1805 pdmacFileEpCacheEntryRelease(pEntry);
1806 }
1807 else
1808 {
1809 uint8_t *pbBuffer = NULL;
1810
1811 LogFlow(("Fetching data for ghost entry %#p from file\n", pEntry));
1812
1813 pdmacFileCacheLockEnter(pCache);
1814 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
1815 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
1816
1817 /* Move the entry to Am and fetch it to the cache. */
1818 if (fEnough)
1819 {
1820 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1821 pdmacFileCacheAdd(pCache, pEntry->cbData);
1822 pdmacFileCacheLockLeave(pCache);
1823
1824 if (pbBuffer)
1825 pEntry->pbData = pbBuffer;
1826 else
1827 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
1828 AssertPtr(pEntry->pbData);
1829
1830 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1831 &IoMemCtx,
1832 OffDiff, cbToRead,
1833 false /* fWrite */);
1834 pdmacFileCacheReadFromEndpoint(pEntry);
1835 /* Release the entry */
1836 pdmacFileEpCacheEntryRelease(pEntry);
1837 }
1838 else
1839 {
1840 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1841 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
1842 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
1843 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
1844 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1845
1846 pdmacFileCacheLockLeave(pCache);
1847
1848 RTMemFree(pEntry);
1849
1850 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1851 &IoMemCtx, off, cbToRead,
1852 PDMACTASKFILETRANSFER_READ);
1853 }
1854 }
1855 }
1856 else
1857 {
1858#ifdef VBOX_WITH_IO_READ_CACHE
1859 /* No entry found for this offset. Create a new entry and fetch the data to the cache. */
1860 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
1861 pEndpointCache,
1862 off, cbRead,
1863 PAGE_SIZE,
1864 &cbToRead);
1865
1866 cbRead -= cbToRead;
1867
1868 if (pEntryNew)
1869 {
1870 if (!cbRead)
1871 STAM_COUNTER_INC(&pCache->cMisses);
1872 else
1873 STAM_COUNTER_INC(&pCache->cPartialHits);
1874
1875 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
1876 &IoMemCtx,
1877 off - pEntryNew->Core.Key,
1878 cbToRead,
1879 false /* fWrite */);
1880 pdmacFileCacheReadFromEndpoint(pEntryNew);
1881 pdmacFileEpCacheEntryRelease(pEntryNew); /* it is protected by the I/O in progress flag now. */
1882 }
1883 else
1884 {
1885 /*
1886 * There is not enough free space in the cache.
1887 * Pass the request directly to the I/O manager.
1888 */
1889 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToRead));
1890
1891 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1892 &IoMemCtx, off, cbToRead,
1893 PDMACTASKFILETRANSFER_READ);
1894 }
1895#else
1896 /* Clip read size if neccessary. */
1897 PPDMACFILECACHEENTRY pEntryAbove;
1898 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1899 &pEntryAbove, NULL);
1900
1901 if (pEntryAbove)
1902 {
1903 if (off + (RTFOFF)cbRead > pEntryAbove->Core.Key)
1904 cbToRead = pEntryAbove->Core.Key - off;
1905 else
1906 cbToRead = cbRead;
1907
1908 pdmacFileEpCacheEntryRelease(pEntryAbove);
1909 }
1910 else
1911 cbToRead = cbRead;
1912
1913 cbRead -= cbToRead;
1914 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1915 &IoMemCtx, off, cbToRead,
1916 PDMACTASKFILETRANSFER_READ);
1917#endif
1918 }
1919 off += cbToRead;
1920 }
1921
1922 ASMAtomicWriteBool(&pTask->fCompleted, false);
1923
1924 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
1925 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
1926 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
1927 else
1928 rc = VINF_AIO_TASK_PENDING;
1929
1930 LogFlowFunc((": Leave rc=%Rrc\n", rc));
1931
1932 return rc;
1933}
1934
1935/**
1936 * Writes the given data to the endpoint using the cache if possible.
1937 *
1938 * @returns VBox status code.
1939 * @param pEndpoint The endpoint to write to.
1940 * @param pTask The task structure used as identifier for this request.
1941 * @param off The offset to start writing to
1942 * @param paSegments Pointer to the array holding the source buffers.
1943 * @param cSegments Number of segments in the array.
1944 * @param cbWrite Number of bytes to write.
1945 */
1946int pdmacFileEpCacheWrite(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1947 RTFOFF off, PCPDMDATASEG paSegments, size_t cSegments,
1948 size_t cbWrite)
1949{
1950 int rc = VINF_SUCCESS;
1951 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1952 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1953 PPDMACFILECACHEENTRY pEntry;
1954
1955 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbWrite=%u\n",
1956 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbWrite));
1957
1958 pTask->cbTransferLeft = cbWrite;
1959 pTask->rc = VINF_SUCCESS;
1960 /* Set to completed to make sure that the task is valid while we access it. */
1961 ASMAtomicWriteBool(&pTask->fCompleted, true);
1962
1963 /* Init the I/O memory context */
1964 PDMIOMEMCTX IoMemCtx;
1965 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1966
1967 while (cbWrite)
1968 {
1969 size_t cbToWrite;
1970
1971 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1972
1973 if (pEntry)
1974 {
1975 /* Write the data into the entry and mark it as dirty */
1976 AssertPtr(pEntry->pList);
1977
1978 RTFOFF OffDiff = off - pEntry->Core.Key;
1979
1980 AssertMsg(off >= pEntry->Core.Key,
1981 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1982 off, pEntry->Core.Key));
1983
1984 cbToWrite = RT_MIN(pEntry->cbData - OffDiff, cbWrite);
1985 cbWrite -= cbToWrite;
1986
1987 if (!cbWrite)
1988 STAM_COUNTER_INC(&pCache->cHits);
1989 else
1990 STAM_COUNTER_INC(&pCache->cPartialHits);
1991
1992 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
1993
1994 /* Ghost lists contain no data. */
1995 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1996 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1997 {
1998 /* Check if the entry is dirty. */
1999 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
2000 PDMACFILECACHE_ENTRY_IS_DIRTY,
2001 0))
2002 {
2003 /* If it is dirty but not in progrss just update the data. */
2004 if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS))
2005 {
2006 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2007 pEntry->pbData + OffDiff,
2008 cbToWrite);
2009 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2010 }
2011 else
2012 {
2013 /* The data isn't written to the file yet */
2014 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2015 &IoMemCtx,
2016 OffDiff, cbToWrite,
2017 true /* fWrite */);
2018 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2019 }
2020
2021 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2022 }
2023 else /* Dirty bit not set */
2024 {
2025 /*
2026 * Check if a read is in progress for this entry.
2027 * We have to defer processing in that case.
2028 */
2029 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
2030 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
2031 0))
2032 {
2033 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2034 &IoMemCtx,
2035 OffDiff, cbToWrite,
2036 true /* fWrite */);
2037 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2038 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2039 }
2040 else /* I/O in progress flag not set */
2041 {
2042 /* Write as much as we can into the entry and update the file. */
2043 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2044 pEntry->pbData + OffDiff,
2045 cbToWrite);
2046 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2047
2048 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
2049 if (fCommit)
2050 pdmacFileCacheCommitDirtyEntries(pCache);
2051 }
2052 } /* Dirty bit not set */
2053
2054 /* Move this entry to the top position */
2055 if (pEntry->pList == &pCache->LruFrequentlyUsed)
2056 {
2057 pdmacFileCacheLockEnter(pCache);
2058 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2059 pdmacFileCacheLockLeave(pCache);
2060 }
2061
2062 pdmacFileEpCacheEntryRelease(pEntry);
2063 }
2064 else /* Entry is on the ghost list */
2065 {
2066 uint8_t *pbBuffer = NULL;
2067
2068 pdmacFileCacheLockEnter(pCache);
2069 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
2070 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
2071
2072 if (fEnough)
2073 {
2074 /* Move the entry to Am and fetch it to the cache. */
2075 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2076 pdmacFileCacheAdd(pCache, pEntry->cbData);
2077 pdmacFileCacheLockLeave(pCache);
2078
2079 if (pbBuffer)
2080 pEntry->pbData = pbBuffer;
2081 else
2082 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
2083 AssertPtr(pEntry->pbData);
2084
2085 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2086 &IoMemCtx,
2087 OffDiff, cbToWrite,
2088 true /* fWrite */);
2089 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2090 pdmacFileCacheReadFromEndpoint(pEntry);
2091
2092 /* Release the reference. If it is still needed the I/O in progress flag should protect it now. */
2093 pdmacFileEpCacheEntryRelease(pEntry);
2094 }
2095 else
2096 {
2097 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
2098 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2099 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
2100 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2101 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2102
2103 pdmacFileCacheLockLeave(pCache);
2104
2105 RTMemFree(pEntry);
2106 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2107 &IoMemCtx, off, cbToWrite,
2108 PDMACTASKFILETRANSFER_WRITE);
2109 }
2110 }
2111 }
2112 else /* No entry found */
2113 {
2114 /*
2115 * No entry found. Try to create a new cache entry to store the data in and if that fails
2116 * write directly to the file.
2117 */
2118 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
2119 pEndpointCache,
2120 off, cbWrite,
2121 512,
2122 &cbToWrite);
2123
2124 cbWrite -= cbToWrite;
2125
2126 if (pEntryNew)
2127 {
2128 RTFOFF offDiff = off - pEntryNew->Core.Key;
2129
2130 STAM_COUNTER_INC(&pCache->cHits);
2131
2132 /*
2133 * Check if it is possible to just write the data without waiting
2134 * for it to get fetched first.
2135 */
2136 if (!offDiff && pEntryNew->cbData == cbToWrite)
2137 {
2138 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2139 pEntryNew->pbData,
2140 cbToWrite);
2141 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2142
2143 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntryNew);
2144 if (fCommit)
2145 pdmacFileCacheCommitDirtyEntries(pCache);
2146 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
2147 }
2148 else
2149 {
2150 /* Defer the write and fetch the data from the endpoint. */
2151 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
2152 &IoMemCtx,
2153 offDiff, cbToWrite,
2154 true /* fWrite */);
2155 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2156 pdmacFileCacheReadFromEndpoint(pEntryNew);
2157 }
2158
2159 pdmacFileEpCacheEntryRelease(pEntryNew);
2160 }
2161 else
2162 {
2163 /*
2164 * There is not enough free space in the cache.
2165 * Pass the request directly to the I/O manager.
2166 */
2167 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToWrite));
2168
2169 STAM_COUNTER_INC(&pCache->cMisses);
2170
2171 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2172 &IoMemCtx, off, cbToWrite,
2173 PDMACTASKFILETRANSFER_WRITE);
2174 }
2175 }
2176
2177 off += cbToWrite;
2178 }
2179
2180 ASMAtomicWriteBool(&pTask->fCompleted, false);
2181
2182 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
2183 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
2184 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
2185 else
2186 rc = VINF_AIO_TASK_PENDING;
2187
2188 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2189
2190 return rc;
2191}
2192
2193int pdmacFileEpCacheFlush(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask)
2194{
2195 int rc = VINF_SUCCESS;
2196
2197 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p\n",
2198 pEndpoint, pEndpoint->Core.pszUri, pTask));
2199
2200 pTask->rc = VINF_SUCCESS;
2201
2202 if (ASMAtomicReadPtr((void * volatile *)&pEndpoint->DataCache.pTaskFlush))
2203 rc = VERR_RESOURCE_BUSY;
2204 else
2205 {
2206 /* Check for dirty entries in the cache. */
2207 pdmacFileCacheEndpointCommit(&pEndpoint->DataCache);
2208 if (ASMAtomicReadU32(&pEndpoint->DataCache.cWritesOutstanding) > 0)
2209 {
2210 ASMAtomicWritePtr((void * volatile *)&pEndpoint->DataCache.pTaskFlush, pTask);
2211 rc = VINF_AIO_TASK_PENDING;
2212 }
2213 else
2214 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
2215 }
2216
2217 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2218 return rc;
2219}
2220
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