VirtualBox

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

Last change on this file since 28800 was 28800, checked in by vboxsync, 14 years ago

Automated rebranding to Oracle copyright/license strings via filemuncher

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 84.2 KB
Line 
1/* $Id: PDMAsyncCompletionFileCache.cpp 28800 2010-04-27 08:22:32Z 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 Oracle Corporation
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
19/** @page pg_pdm_async_completion_cache PDM Async Completion Cache - The file I/O cache
20 * This component implements an I/O cache for file endpoints based on the 2Q cache algorithm.
21 */
22
23/*******************************************************************************
24* Header Files *
25*******************************************************************************/
26#define LOG_GROUP LOG_GROUP_PDM_ASYNC_COMPLETION
27#include <iprt/types.h>
28#include <iprt/mem.h>
29#include <iprt/path.h>
30#include <VBox/log.h>
31#include <VBox/stam.h>
32
33#include "PDMAsyncCompletionFileInternal.h"
34
35/**
36 * A I/O memory context.
37 */
38typedef struct PDMIOMEMCTX
39{
40 /** Pointer to the scatter/gather list. */
41 PCRTSGSEG paDataSeg;
42 /** Number of segments. */
43 size_t cSegments;
44 /** Current segment we are in. */
45 unsigned iSegIdx;
46 /** Pointer to the current buffer. */
47 uint8_t *pbBuf;
48 /** Number of bytes left in the current buffer. */
49 size_t cbBufLeft;
50} PDMIOMEMCTX, *PPDMIOMEMCTX;
51
52#ifdef VBOX_STRICT
53# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) \
54 do \
55 { \
56 AssertMsg(RTCritSectIsOwner(&Cache->CritSect), \
57 ("Thread does not own critical section\n"));\
58 } while(0)
59
60# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) \
61 do \
62 { \
63 AssertMsg(RTSemRWIsWriteOwner(pEpCache->SemRWEntries), \
64 ("Thread is not exclusive owner of the per endpoint RW semaphore\n")); \
65 } while(0)
66
67# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) \
68 do \
69 { \
70 AssertMsg(RTSemRWIsReadOwner(pEpCache->SemRWEntries), \
71 ("Thread is not read owner of the per endpoint RW semaphore\n")); \
72 } while(0)
73
74#else
75# define PDMACFILECACHE_IS_CRITSECT_OWNER(Cache) do { } while(0)
76# define PDMACFILECACHE_EP_IS_SEMRW_WRITE_OWNER(pEpCache) do { } while(0)
77# define PDMACFILECACHE_EP_IS_SEMRW_READ_OWNER(pEpCache) do { } while(0)
78#endif
79
80/*******************************************************************************
81* Internal Functions *
82*******************************************************************************/
83static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc);
84
85/**
86 * Decrement the reference counter of the given cache entry.
87 *
88 * @returns nothing.
89 * @param pEntry The entry to release.
90 */
91DECLINLINE(void) pdmacFileEpCacheEntryRelease(PPDMACFILECACHEENTRY pEntry)
92{
93 AssertMsg(pEntry->cRefs > 0, ("Trying to release a not referenced entry\n"));
94 ASMAtomicDecU32(&pEntry->cRefs);
95}
96
97/**
98 * Increment the reference counter of the given cache entry.
99 *
100 * @returns nothing.
101 * @param pEntry The entry to reference.
102 */
103DECLINLINE(void) pdmacFileEpCacheEntryRef(PPDMACFILECACHEENTRY pEntry)
104{
105 ASMAtomicIncU32(&pEntry->cRefs);
106}
107
108/**
109 * Initialize a I/O memory context.
110 *
111 * @returns nothing
112 * @param pIoMemCtx Pointer to a unitialized I/O memory context.
113 * @param paDataSeg Pointer to the S/G list.
114 * @param cSegments Number of segments in the S/G list.
115 */
116DECLINLINE(void) pdmIoMemCtxInit(PPDMIOMEMCTX pIoMemCtx, PCRTSGSEG paDataSeg, size_t cSegments)
117{
118 AssertMsg((cSegments > 0) && paDataSeg, ("Trying to initialize a I/O memory context without a S/G list\n"));
119
120 pIoMemCtx->paDataSeg = paDataSeg;
121 pIoMemCtx->cSegments = cSegments;
122 pIoMemCtx->iSegIdx = 0;
123 pIoMemCtx->pbBuf = (uint8_t *)paDataSeg[0].pvSeg;
124 pIoMemCtx->cbBufLeft = paDataSeg[0].cbSeg;
125}
126
127/**
128 * Return a buffer from the I/O memory context.
129 *
130 * @returns Pointer to the buffer
131 * @param pIoMemCtx Pointer to the I/O memory context.
132 * @param pcbData Pointer to the amount of byte requested.
133 * If the current buffer doesn't have enough bytes left
134 * the amount is returned in the variable.
135 */
136DECLINLINE(uint8_t *) pdmIoMemCtxGetBuffer(PPDMIOMEMCTX pIoMemCtx, size_t *pcbData)
137{
138 size_t cbData = RT_MIN(*pcbData, pIoMemCtx->cbBufLeft);
139 uint8_t *pbBuf = pIoMemCtx->pbBuf;
140
141 pIoMemCtx->cbBufLeft -= cbData;
142
143 /* Advance to the next segment if required. */
144 if (!pIoMemCtx->cbBufLeft)
145 {
146 pIoMemCtx->iSegIdx++;
147
148 if (RT_UNLIKELY(pIoMemCtx->iSegIdx == pIoMemCtx->cSegments))
149 {
150 pIoMemCtx->cbBufLeft = 0;
151 pIoMemCtx->pbBuf = NULL;
152 }
153 else
154 {
155 pIoMemCtx->pbBuf = (uint8_t *)pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].pvSeg;
156 pIoMemCtx->cbBufLeft = pIoMemCtx->paDataSeg[pIoMemCtx->iSegIdx].cbSeg;
157 }
158
159 *pcbData = cbData;
160 }
161 else
162 pIoMemCtx->pbBuf += cbData;
163
164 return pbBuf;
165}
166
167#ifdef DEBUG
168static void pdmacFileCacheValidate(PPDMACFILECACHEGLOBAL pCache)
169{
170 /* Amount of cached data should never exceed the maximum amount. */
171 AssertMsg(pCache->cbCached <= pCache->cbMax,
172 ("Current amount of cached data exceeds maximum\n"));
173
174 /* The amount of cached data in the LRU and FRU list should match cbCached */
175 AssertMsg(pCache->LruRecentlyUsedIn.cbCached + pCache->LruFrequentlyUsed.cbCached == pCache->cbCached,
176 ("Amount of cached data doesn't match\n"));
177
178 AssertMsg(pCache->LruRecentlyUsedOut.cbCached <= pCache->cbRecentlyUsedOutMax,
179 ("Paged out list exceeds maximum\n"));
180}
181#endif
182
183DECLINLINE(void) pdmacFileCacheLockEnter(PPDMACFILECACHEGLOBAL pCache)
184{
185 RTCritSectEnter(&pCache->CritSect);
186#ifdef DEBUG
187 pdmacFileCacheValidate(pCache);
188#endif
189}
190
191DECLINLINE(void) pdmacFileCacheLockLeave(PPDMACFILECACHEGLOBAL pCache)
192{
193#ifdef DEBUG
194 pdmacFileCacheValidate(pCache);
195#endif
196 RTCritSectLeave(&pCache->CritSect);
197}
198
199DECLINLINE(void) pdmacFileCacheSub(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
200{
201 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
202 pCache->cbCached -= cbAmount;
203}
204
205DECLINLINE(void) pdmacFileCacheAdd(PPDMACFILECACHEGLOBAL pCache, uint32_t cbAmount)
206{
207 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
208 pCache->cbCached += cbAmount;
209}
210
211DECLINLINE(void) pdmacFileCacheListAdd(PPDMACFILELRULIST pList, uint32_t cbAmount)
212{
213 pList->cbCached += cbAmount;
214}
215
216DECLINLINE(void) pdmacFileCacheListSub(PPDMACFILELRULIST pList, uint32_t cbAmount)
217{
218 pList->cbCached -= cbAmount;
219}
220
221#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
222/**
223 * Checks consistency of a LRU list.
224 *
225 * @returns nothing
226 * @param pList The LRU list to check.
227 * @param pNotInList Element which is not allowed to occur in the list.
228 */
229static void pdmacFileCacheCheckList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pNotInList)
230{
231 PPDMACFILECACHEENTRY pCurr = pList->pHead;
232
233 /* Check that there are no double entries and no cycles in the list. */
234 while (pCurr)
235 {
236 PPDMACFILECACHEENTRY pNext = pCurr->pNext;
237
238 while (pNext)
239 {
240 AssertMsg(pCurr != pNext,
241 ("Entry %#p is at least two times in list %#p or there is a cycle in the list\n",
242 pCurr, pList));
243 pNext = pNext->pNext;
244 }
245
246 AssertMsg(pCurr != pNotInList, ("Not allowed entry %#p is in list\n", pCurr));
247
248 if (!pCurr->pNext)
249 AssertMsg(pCurr == pList->pTail, ("End of list reached but last element is not list tail\n"));
250
251 pCurr = pCurr->pNext;
252 }
253}
254#endif
255
256/**
257 * Unlinks a cache entry from the LRU list it is assigned to.
258 *
259 * @returns nothing.
260 * @param pEntry The entry to unlink.
261 */
262static void pdmacFileCacheEntryRemoveFromList(PPDMACFILECACHEENTRY pEntry)
263{
264 PPDMACFILELRULIST pList = pEntry->pList;
265 PPDMACFILECACHEENTRY pPrev, pNext;
266
267 LogFlowFunc((": Deleting entry %#p from list %#p\n", pEntry, pList));
268
269 AssertPtr(pList);
270
271#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
272 pdmacFileCacheCheckList(pList, NULL);
273#endif
274
275 pPrev = pEntry->pPrev;
276 pNext = pEntry->pNext;
277
278 AssertMsg(pEntry != pPrev, ("Entry links to itself as previous element\n"));
279 AssertMsg(pEntry != pNext, ("Entry links to itself as next element\n"));
280
281 if (pPrev)
282 pPrev->pNext = pNext;
283 else
284 {
285 pList->pHead = pNext;
286
287 if (pNext)
288 pNext->pPrev = NULL;
289 }
290
291 if (pNext)
292 pNext->pPrev = pPrev;
293 else
294 {
295 pList->pTail = pPrev;
296
297 if (pPrev)
298 pPrev->pNext = NULL;
299 }
300
301 pEntry->pList = NULL;
302 pEntry->pPrev = NULL;
303 pEntry->pNext = NULL;
304 pdmacFileCacheListSub(pList, pEntry->cbData);
305#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
306 pdmacFileCacheCheckList(pList, pEntry);
307#endif
308}
309
310/**
311 * Adds a cache entry to the given LRU list unlinking it from the currently
312 * assigned list if needed.
313 *
314 * @returns nothing.
315 * @param pList List to the add entry to.
316 * @param pEntry Entry to add.
317 */
318static void pdmacFileCacheEntryAddToList(PPDMACFILELRULIST pList, PPDMACFILECACHEENTRY pEntry)
319{
320 LogFlowFunc((": Adding entry %#p to list %#p\n", pEntry, pList));
321#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
322 pdmacFileCacheCheckList(pList, NULL);
323#endif
324
325 /* Remove from old list if needed */
326 if (pEntry->pList)
327 pdmacFileCacheEntryRemoveFromList(pEntry);
328
329 pEntry->pNext = pList->pHead;
330 if (pList->pHead)
331 pList->pHead->pPrev = pEntry;
332 else
333 {
334 Assert(!pList->pTail);
335 pList->pTail = pEntry;
336 }
337
338 pEntry->pPrev = NULL;
339 pList->pHead = pEntry;
340 pdmacFileCacheListAdd(pList, pEntry->cbData);
341 pEntry->pList = pList;
342#ifdef PDMACFILECACHE_WITH_LRULIST_CHECKS
343 pdmacFileCacheCheckList(pList, NULL);
344#endif
345}
346
347/**
348 * Destroys a LRU list freeing all entries.
349 *
350 * @returns nothing
351 * @param pList Pointer to the LRU list to destroy.
352 *
353 * @note The caller must own the critical section of the cache.
354 */
355static void pdmacFileCacheDestroyList(PPDMACFILELRULIST pList)
356{
357 while (pList->pHead)
358 {
359 PPDMACFILECACHEENTRY pEntry = pList->pHead;
360
361 pList->pHead = pEntry->pNext;
362
363 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
364 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
365
366 RTMemPageFree(pEntry->pbData, pEntry->cbData);
367 RTMemFree(pEntry);
368 }
369}
370
371/**
372 * Tries to remove the given amount of bytes from a given list in the cache
373 * moving the entries to one of the given ghosts lists
374 *
375 * @returns Amount of data which could be freed.
376 * @param pCache Pointer to the global cache data.
377 * @param cbData The amount of the data to free.
378 * @param pListSrc The source list to evict data from.
379 * @param pGhostListSrc The ghost list removed entries should be moved to
380 * NULL if the entry should be freed.
381 * @param fReuseBuffer Flag whether a buffer should be reused if it has the same size
382 * @param ppbBuf Where to store the address of the buffer if an entry with the
383 * same size was found and fReuseBuffer is true.
384 *
385 * @note This function may return fewer bytes than requested because entries
386 * may be marked as non evictable if they are used for I/O at the
387 * moment.
388 */
389static size_t pdmacFileCacheEvictPagesFrom(PPDMACFILECACHEGLOBAL pCache, size_t cbData,
390 PPDMACFILELRULIST pListSrc, PPDMACFILELRULIST pGhostListDst,
391 bool fReuseBuffer, uint8_t **ppbBuffer)
392{
393 size_t cbEvicted = 0;
394
395 PDMACFILECACHE_IS_CRITSECT_OWNER(pCache);
396
397 AssertMsg(cbData > 0, ("Evicting 0 bytes not possible\n"));
398 AssertMsg( !pGhostListDst
399 || (pGhostListDst == &pCache->LruRecentlyUsedOut),
400 ("Destination list must be NULL or the recently used but paged out list\n"));
401
402 if (fReuseBuffer)
403 {
404 AssertPtr(ppbBuffer);
405 *ppbBuffer = NULL;
406 }
407
408 /* Start deleting from the tail. */
409 PPDMACFILECACHEENTRY pEntry = pListSrc->pTail;
410
411 while ((cbEvicted < cbData) && pEntry)
412 {
413 PPDMACFILECACHEENTRY pCurr = pEntry;
414
415 pEntry = pEntry->pPrev;
416
417 /* We can't evict pages which are currently in progress or dirty but not in progress */
418 if ( !(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
419 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
420 {
421 /* Ok eviction candidate. Grab the endpoint semaphore and check again
422 * because somebody else might have raced us. */
423 PPDMACFILEENDPOINTCACHE pEndpointCache = &pCurr->pEndpoint->DataCache;
424 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
425
426 if (!(pCurr->fFlags & PDMACFILECACHE_NOT_EVICTABLE)
427 && (ASMAtomicReadU32(&pCurr->cRefs) == 0))
428 {
429 LogFlow(("Evicting entry %#p (%u bytes)\n", pCurr, pCurr->cbData));
430
431 if (fReuseBuffer && (pCurr->cbData == cbData))
432 {
433 STAM_COUNTER_INC(&pCache->StatBuffersReused);
434 *ppbBuffer = pCurr->pbData;
435 }
436 else if (pCurr->pbData)
437 RTMemPageFree(pCurr->pbData, pCurr->cbData);
438
439 pCurr->pbData = NULL;
440 cbEvicted += pCurr->cbData;
441
442 pdmacFileCacheEntryRemoveFromList(pCurr);
443 pdmacFileCacheSub(pCache, pCurr->cbData);
444
445 if (pGhostListDst)
446 {
447 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
448
449 PPDMACFILECACHEENTRY pGhostEntFree = pGhostListDst->pTail;
450
451 /* We have to remove the last entries from the paged out list. */
452 while ( ((pGhostListDst->cbCached + pCurr->cbData) > pCache->cbRecentlyUsedOutMax)
453 && pGhostEntFree)
454 {
455 PPDMACFILECACHEENTRY pFree = pGhostEntFree;
456 PPDMACFILEENDPOINTCACHE pEndpointCacheFree = &pFree->pEndpoint->DataCache;
457
458 pGhostEntFree = pGhostEntFree->pPrev;
459
460 RTSemRWRequestWrite(pEndpointCacheFree->SemRWEntries, RT_INDEFINITE_WAIT);
461
462 if (ASMAtomicReadU32(&pFree->cRefs) == 0)
463 {
464 pdmacFileCacheEntryRemoveFromList(pFree);
465
466 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
467 RTAvlrFileOffsetRemove(pEndpointCacheFree->pTree, pFree->Core.Key);
468 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
469
470 RTMemFree(pFree);
471 }
472
473 RTSemRWReleaseWrite(pEndpointCacheFree->SemRWEntries);
474 }
475
476 if (pGhostListDst->cbCached + pCurr->cbData > pCache->cbRecentlyUsedOutMax)
477 {
478 /* Couldn't remove enough entries. Delete */
479 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
480 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
481 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
482
483 RTMemFree(pCurr);
484 }
485 else
486 pdmacFileCacheEntryAddToList(pGhostListDst, pCurr);
487 }
488 else
489 {
490 /* Delete the entry from the AVL tree it is assigned to. */
491 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
492 RTAvlrFileOffsetRemove(pCurr->pEndpoint->DataCache.pTree, pCurr->Core.Key);
493 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
494
495 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
496 RTMemFree(pCurr);
497 }
498 }
499
500 }
501 else
502 LogFlow(("Entry %#p (%u bytes) is still in progress and can't be evicted\n", pCurr, pCurr->cbData));
503 }
504
505 return cbEvicted;
506}
507
508static bool pdmacFileCacheReclaim(PPDMACFILECACHEGLOBAL pCache, size_t cbData, bool fReuseBuffer, uint8_t **ppbBuffer)
509{
510 size_t cbRemoved = 0;
511
512 if ((pCache->cbCached + cbData) < pCache->cbMax)
513 return true;
514 else if ((pCache->LruRecentlyUsedIn.cbCached + cbData) > pCache->cbRecentlyUsedInMax)
515 {
516 /* Try to evict as many bytes as possible from A1in */
517 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruRecentlyUsedIn,
518 &pCache->LruRecentlyUsedOut, fReuseBuffer, ppbBuffer);
519
520 /*
521 * If it was not possible to remove enough entries
522 * try the frequently accessed cache.
523 */
524 if (cbRemoved < cbData)
525 {
526 Assert(!fReuseBuffer || !*ppbBuffer); /* It is not possible that we got a buffer with the correct size but we didn't freed enough data. */
527
528 /*
529 * If we removed something we can't pass the reuse buffer flag anymore because
530 * we don't need to evict that much data
531 */
532 if (!cbRemoved)
533 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
534 NULL, fReuseBuffer, ppbBuffer);
535 else
536 cbRemoved += pdmacFileCacheEvictPagesFrom(pCache, cbData - cbRemoved, &pCache->LruFrequentlyUsed,
537 NULL, false, NULL);
538 }
539 }
540 else
541 {
542 /* We have to remove entries from frequently access list. */
543 cbRemoved = pdmacFileCacheEvictPagesFrom(pCache, cbData, &pCache->LruFrequentlyUsed,
544 NULL, fReuseBuffer, ppbBuffer);
545 }
546
547 LogFlowFunc((": removed %u bytes, requested %u\n", cbRemoved, cbData));
548 return (cbRemoved >= cbData);
549}
550
551/**
552 * Initiates a read I/O task for the given entry.
553 *
554 * @returns nothing.
555 * @param pEntry The entry to fetch the data to.
556 */
557static void pdmacFileCacheReadFromEndpoint(PPDMACFILECACHEENTRY pEntry)
558{
559 LogFlowFunc((": Reading data into cache entry %#p\n", pEntry));
560
561 /* Make sure no one evicts the entry while it is accessed. */
562 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
563
564 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
565 AssertPtr(pIoTask);
566
567 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
568
569 pIoTask->pEndpoint = pEntry->pEndpoint;
570 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_READ;
571 pIoTask->Off = pEntry->Core.Key;
572 pIoTask->DataSeg.cbSeg = pEntry->cbData;
573 pIoTask->DataSeg.pvSeg = pEntry->pbData;
574 pIoTask->pvUser = pEntry;
575 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
576
577 /* Send it off to the I/O manager. */
578 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
579}
580
581/**
582 * Initiates a write I/O task for the given entry.
583 *
584 * @returns nothing.
585 * @param pEntry The entry to read the data from.
586 */
587static void pdmacFileCacheWriteToEndpoint(PPDMACFILECACHEENTRY pEntry)
588{
589 LogFlowFunc((": Writing data from cache entry %#p\n", pEntry));
590
591 /* Make sure no one evicts the entry while it is accessed. */
592 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
593
594 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEntry->pEndpoint);
595 AssertPtr(pIoTask);
596
597 AssertMsg(pEntry->pbData, ("Entry is in ghost state\n"));
598
599 pIoTask->pEndpoint = pEntry->pEndpoint;
600 pIoTask->enmTransferType = PDMACTASKFILETRANSFER_WRITE;
601 pIoTask->Off = pEntry->Core.Key;
602 pIoTask->DataSeg.cbSeg = pEntry->cbData;
603 pIoTask->DataSeg.pvSeg = pEntry->pbData;
604 pIoTask->pvUser = pEntry;
605 pIoTask->pfnCompleted = pdmacFileCacheTaskCompleted;
606 ASMAtomicIncU32(&pEntry->pEndpoint->DataCache.cWritesOutstanding);
607
608 /* Send it off to the I/O manager. */
609 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
610}
611
612/**
613 * Commit a single dirty entry to the endpoint
614 *
615 * @returns nothing
616 * @param pEntry The entry to commit.
617 */
618static void pdmacFileCacheEntryCommit(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
619{
620 NOREF(pEndpointCache);
621 AssertMsg( (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
622 && !(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS),
623 ("Invalid flags set for entry %#p\n", pEntry));
624
625 pdmacFileCacheWriteToEndpoint(pEntry);
626}
627
628/**
629 * Commit all dirty entries for a single endpoint.
630 *
631 * @returns nothing.
632 * @param pEndpointCache The endpoint cache to commit.
633 */
634static void pdmacFileCacheEndpointCommit(PPDMACFILEENDPOINTCACHE pEndpointCache)
635{
636 uint32_t cbCommitted = 0;
637 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
638
639 /* The list is moved to a new header to reduce locking overhead. */
640 RTLISTNODE ListDirtyNotCommitted;
641 RTSPINLOCKTMP Tmp;
642
643 RTListInit(&ListDirtyNotCommitted);
644 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
645 RTListMove(&ListDirtyNotCommitted, &pEndpointCache->ListDirtyNotCommitted);
646 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
647
648 if (!RTListIsEmpty(&ListDirtyNotCommitted))
649 {
650 PPDMACFILECACHEENTRY pEntry = RTListNodeGetFirst(&ListDirtyNotCommitted,
651 PDMACFILECACHEENTRY,
652 NodeNotCommitted);
653
654 while (!RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted))
655 {
656 PPDMACFILECACHEENTRY pNext = RTListNodeGetNext(&pEntry->NodeNotCommitted, PDMACFILECACHEENTRY,
657 NodeNotCommitted);
658 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
659 cbCommitted += pEntry->cbData;
660 RTListNodeRemove(&pEntry->NodeNotCommitted);
661 pEntry = pNext;
662 }
663
664 /* Commit the last endpoint */
665 Assert(RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted));
666 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
667 RTListNodeRemove(&pEntry->NodeNotCommitted);
668 AssertMsg(RTListIsEmpty(&ListDirtyNotCommitted),
669 ("Committed all entries but list is not empty\n"));
670 }
671
672 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
673 AssertMsg(pEndpointCache->pCache->cbDirty >= cbCommitted,
674 ("Number of committed bytes exceeds number of dirty bytes\n"));
675 ASMAtomicSubU32(&pEndpointCache->pCache->cbDirty, cbCommitted);
676}
677
678/**
679 * Commit all dirty entries in the cache.
680 *
681 * @returns nothing.
682 * @param pCache The global cache instance.
683 */
684static void pdmacFileCacheCommitDirtyEntries(PPDMACFILECACHEGLOBAL pCache)
685{
686 bool fCommitInProgress = ASMAtomicXchgBool(&pCache->fCommitInProgress, true);
687
688 if (!fCommitInProgress)
689 {
690 pdmacFileCacheLockEnter(pCache);
691 Assert(!RTListIsEmpty(&pCache->ListEndpoints));
692
693 PPDMACFILEENDPOINTCACHE pEndpointCache = RTListNodeGetFirst(&pCache->ListEndpoints,
694 PDMACFILEENDPOINTCACHE,
695 NodeCacheEndpoint);
696 AssertPtr(pEndpointCache);
697
698 while (!RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint))
699 {
700 pdmacFileCacheEndpointCommit(pEndpointCache);
701
702 pEndpointCache = RTListNodeGetNext(&pEndpointCache->NodeCacheEndpoint, PDMACFILEENDPOINTCACHE,
703 NodeCacheEndpoint);
704 }
705
706 /* Commit the last endpoint */
707 Assert(RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint));
708 pdmacFileCacheEndpointCommit(pEndpointCache);
709
710 pdmacFileCacheLockLeave(pCache);
711 ASMAtomicWriteBool(&pCache->fCommitInProgress, false);
712 }
713}
714
715/**
716 * Adds the given entry as a dirty to the cache.
717 *
718 * @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
719 * @param pEndpointCache The endpoint cache the entry belongs to.
720 * @param pEntry The entry to add.
721 */
722static bool pdmacFileCacheAddDirtyEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
723{
724 bool fDirtyBytesExceeded = false;
725 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
726
727 /* If the commit timer is disabled we commit right away. */
728 if (pCache->u32CommitTimeoutMs == 0)
729 {
730 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
731 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
732 }
733 else if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY))
734 {
735 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
736
737 RTSPINLOCKTMP Tmp;
738 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
739 RTListAppend(&pEndpointCache->ListDirtyNotCommitted, &pEntry->NodeNotCommitted);
740 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
741
742 uint32_t cbDirty = ASMAtomicAddU32(&pCache->cbDirty, pEntry->cbData);
743
744 fDirtyBytesExceeded = (cbDirty >= pCache->cbCommitDirtyThreshold);
745 }
746
747 return fDirtyBytesExceeded;
748}
749
750
751/**
752 * Completes a task segment freeing all ressources and completes the task handle
753 * if everything was transfered.
754 *
755 * @returns Next task segment handle.
756 * @param pTaskSeg Task segment to complete.
757 * @param rc Status code to set.
758 */
759static PPDMACFILETASKSEG pdmacFileCacheTaskComplete(PPDMACFILETASKSEG pTaskSeg, int rc)
760{
761 PPDMACFILETASKSEG pNext = pTaskSeg->pNext;
762 PPDMASYNCCOMPLETIONTASKFILE pTaskFile = pTaskSeg->pTask;
763
764 if (RT_FAILURE(rc))
765 ASMAtomicCmpXchgS32(&pTaskFile->rc, rc, VINF_SUCCESS);
766
767 uint32_t uOld = ASMAtomicSubS32(&pTaskFile->cbTransferLeft, pTaskSeg->cbTransfer);
768 AssertMsg(uOld >= pTaskSeg->cbTransfer, ("New value would overflow\n"));
769 if (!(uOld - pTaskSeg->cbTransfer)
770 && !ASMAtomicXchgBool(&pTaskFile->fCompleted, true))
771 pdmR3AsyncCompletionCompleteTask(&pTaskFile->Core, pTaskFile->rc, true);
772
773 RTMemFree(pTaskSeg);
774
775 return pNext;
776}
777
778/**
779 * Completion callback for I/O tasks.
780 *
781 * @returns nothing.
782 * @param pTask The completed task.
783 * @param pvUser Opaque user data.
784 * @param rc Status code of the completed request.
785 */
786static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc)
787{
788 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pvUser;
789 PPDMACFILECACHEGLOBAL pCache = pEntry->pCache;
790 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint = pEntry->pEndpoint;
791 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
792
793 /* Reference the entry now as we are clearing the I/O in progres flag
794 * which protects the entry till now. */
795 pdmacFileEpCacheEntryRef(pEntry);
796
797 RTSemRWRequestWrite(pEndpoint->DataCache.SemRWEntries, RT_INDEFINITE_WAIT);
798 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
799
800 /* Process waiting segment list. The data in entry might have changed inbetween. */
801 bool fDirty = false;
802 PPDMACFILETASKSEG pComplete = pEntry->pWaitingHead;
803 PPDMACFILETASKSEG pCurr = pComplete;
804
805 AssertMsg((pCurr && pEntry->pWaitingTail) || (!pCurr && !pEntry->pWaitingTail),
806 ("The list tail was not updated correctly\n"));
807 pEntry->pWaitingTail = NULL;
808 pEntry->pWaitingHead = NULL;
809
810 if (pTask->enmTransferType == PDMACTASKFILETRANSFER_WRITE)
811 {
812 AssertMsg(pEndpointCache->cWritesOutstanding > 0, ("Completed write request but outstanding task count is 0\n"));
813 ASMAtomicDecU32(&pEndpointCache->cWritesOutstanding);
814
815 /*
816 * An error here is difficult to handle as the original request completed already.
817 * The error is logged for now and the VM is paused.
818 * If the user continues the entry is written again in the hope
819 * the user fixed the problem and the next write succeeds.
820 */
821 /** @todo r=aeichner: This solution doesn't work
822 * The user will get the message but the VM will hang afterwards
823 * VMR3Suspend() returns when the VM is suspended but suspending
824 * the VM will reopen the images readonly in DrvVD. They are closed first
825 * which will close the endpoints. This will block EMT while the
826 * I/O manager processes the close request but the IO manager is stuck
827 * in the VMR3Suspend call and can't process the request.
828 * Another problem is that closing the VM means flushing the cache
829 * but the entry failed and will probably fail again.
830 * No idea so far how to solve this problem... but the user gets informed
831 * at least.
832 */
833 if (RT_FAILURE(rc))
834 {
835 LogRel(("I/O cache: Error while writing entry at offset %RTfoff (%u bytes) to file \"%s\"\n",
836 pEntry->Core.Key, pEntry->cbData, pEndpoint->Core.pszUri));
837
838 rc = VMSetRuntimeError(pEndpoint->Core.pEpClass->pVM, 0, "CACHE_IOERR",
839 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);
840 AssertRC(rc);
841 rc = VMR3Suspend(pEndpoint->Core.pEpClass->pVM);
842 }
843 else
844 {
845 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IS_DIRTY;
846
847 while (pCurr)
848 {
849 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
850
851 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
852 fDirty = true;
853
854 pCurr = pCurr->pNext;
855 }
856 }
857 }
858 else
859 {
860 AssertMsg(pTask->enmTransferType == PDMACTASKFILETRANSFER_READ, ("Invalid transfer type\n"));
861 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY),
862 ("Invalid flags set\n"));
863
864 while (pCurr)
865 {
866 if (pCurr->fWrite)
867 {
868 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
869 fDirty = true;
870 }
871 else
872 memcpy(pCurr->pvBuf, pEntry->pbData + pCurr->uBufOffset, pCurr->cbTransfer);
873
874 pCurr = pCurr->pNext;
875 }
876 }
877
878 bool fCommit = false;
879 if (fDirty)
880 fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
881
882 /* Complete a pending flush if all writes have completed */
883 if (!ASMAtomicReadU32(&pEndpointCache->cWritesOutstanding))
884 {
885 PPDMASYNCCOMPLETIONTASKFILE pTaskFlush = (PPDMASYNCCOMPLETIONTASKFILE)ASMAtomicXchgPtr((void * volatile *)&pEndpointCache->pTaskFlush, NULL);
886 if (pTaskFlush)
887 pdmR3AsyncCompletionCompleteTask(&pTaskFlush->Core, VINF_SUCCESS, true);
888 }
889
890 RTSemRWReleaseWrite(pEndpoint->DataCache.SemRWEntries);
891
892 /* Dereference so that it isn't protected anymore except we issued anyother write for it. */
893 pdmacFileEpCacheEntryRelease(pEntry);
894
895 if (fCommit)
896 pdmacFileCacheCommitDirtyEntries(pCache);
897
898 /* Complete waiters now. */
899 while (pComplete)
900 pComplete = pdmacFileCacheTaskComplete(pComplete, rc);
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, pEntry->cbData);
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, PCRTSGSEG 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, PCRTSGSEG 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.

© 2023 Oracle
ContactPrivacy policyTerms of Use