VirtualBox

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

Last change on this file since 29566 was 29250, checked in by vboxsync, 15 years ago

iprt/asm*.h: split out asm-math.h, don't include asm-*.h from asm.h, don't include asm.h from sup.h. Fixed a couple file headers.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 82.9 KB
Line 
1/* $Id: PDMAsyncCompletionFileCache.cpp 29250 2010-05-09 17:53:58Z 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/asm.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
607 /* Send it off to the I/O manager. */
608 pdmacFileEpAddTask(pEntry->pEndpoint, pIoTask);
609}
610
611/**
612 * Commit a single dirty entry to the endpoint
613 *
614 * @returns nothing
615 * @param pEntry The entry to commit.
616 */
617static void pdmacFileCacheEntryCommit(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
618{
619 NOREF(pEndpointCache);
620 AssertMsg( (pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY)
621 && !(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS),
622 ("Invalid flags set for entry %#p\n", pEntry));
623
624 pdmacFileCacheWriteToEndpoint(pEntry);
625}
626
627/**
628 * Commit all dirty entries for a single endpoint.
629 *
630 * @returns nothing.
631 * @param pEndpointCache The endpoint cache to commit.
632 */
633static void pdmacFileCacheEndpointCommit(PPDMACFILEENDPOINTCACHE pEndpointCache)
634{
635 uint32_t cbCommitted = 0;
636 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
637
638 /* The list is moved to a new header to reduce locking overhead. */
639 RTLISTNODE ListDirtyNotCommitted;
640 RTSPINLOCKTMP Tmp;
641
642 RTListInit(&ListDirtyNotCommitted);
643 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
644 RTListMove(&ListDirtyNotCommitted, &pEndpointCache->ListDirtyNotCommitted);
645 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
646
647 if (!RTListIsEmpty(&ListDirtyNotCommitted))
648 {
649 PPDMACFILECACHEENTRY pEntry = RTListNodeGetFirst(&ListDirtyNotCommitted,
650 PDMACFILECACHEENTRY,
651 NodeNotCommitted);
652
653 while (!RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted))
654 {
655 PPDMACFILECACHEENTRY pNext = RTListNodeGetNext(&pEntry->NodeNotCommitted, PDMACFILECACHEENTRY,
656 NodeNotCommitted);
657 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
658 cbCommitted += pEntry->cbData;
659 RTListNodeRemove(&pEntry->NodeNotCommitted);
660 pEntry = pNext;
661 }
662
663 /* Commit the last endpoint */
664 Assert(RTListNodeIsLast(&ListDirtyNotCommitted, &pEntry->NodeNotCommitted));
665 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
666 RTListNodeRemove(&pEntry->NodeNotCommitted);
667 AssertMsg(RTListIsEmpty(&ListDirtyNotCommitted),
668 ("Committed all entries but list is not empty\n"));
669 }
670
671 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
672 AssertMsg(pEndpointCache->pCache->cbDirty >= cbCommitted,
673 ("Number of committed bytes exceeds number of dirty bytes\n"));
674 ASMAtomicSubU32(&pEndpointCache->pCache->cbDirty, cbCommitted);
675}
676
677/**
678 * Commit all dirty entries in the cache.
679 *
680 * @returns nothing.
681 * @param pCache The global cache instance.
682 */
683static void pdmacFileCacheCommitDirtyEntries(PPDMACFILECACHEGLOBAL pCache)
684{
685 bool fCommitInProgress = ASMAtomicXchgBool(&pCache->fCommitInProgress, true);
686
687 if (!fCommitInProgress)
688 {
689 pdmacFileCacheLockEnter(pCache);
690 Assert(!RTListIsEmpty(&pCache->ListEndpoints));
691
692 PPDMACFILEENDPOINTCACHE pEndpointCache = RTListNodeGetFirst(&pCache->ListEndpoints,
693 PDMACFILEENDPOINTCACHE,
694 NodeCacheEndpoint);
695 AssertPtr(pEndpointCache);
696
697 while (!RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint))
698 {
699 pdmacFileCacheEndpointCommit(pEndpointCache);
700
701 pEndpointCache = RTListNodeGetNext(&pEndpointCache->NodeCacheEndpoint, PDMACFILEENDPOINTCACHE,
702 NodeCacheEndpoint);
703 }
704
705 /* Commit the last endpoint */
706 Assert(RTListNodeIsLast(&pCache->ListEndpoints, &pEndpointCache->NodeCacheEndpoint));
707 pdmacFileCacheEndpointCommit(pEndpointCache);
708
709 pdmacFileCacheLockLeave(pCache);
710 ASMAtomicWriteBool(&pCache->fCommitInProgress, false);
711 }
712}
713
714/**
715 * Adds the given entry as a dirty to the cache.
716 *
717 * @returns Flag whether the amount of dirty bytes in the cache exceeds the threshold
718 * @param pEndpointCache The endpoint cache the entry belongs to.
719 * @param pEntry The entry to add.
720 */
721static bool pdmacFileCacheAddDirtyEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
722{
723 bool fDirtyBytesExceeded = false;
724 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
725
726 /* If the commit timer is disabled we commit right away. */
727 if (pCache->u32CommitTimeoutMs == 0)
728 {
729 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
730 pdmacFileCacheEntryCommit(pEndpointCache, pEntry);
731 }
732 else if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY))
733 {
734 pEntry->fFlags |= PDMACFILECACHE_ENTRY_IS_DIRTY;
735
736 RTSPINLOCKTMP Tmp;
737 RTSpinlockAcquire(pEndpointCache->LockList, &Tmp);
738 RTListAppend(&pEndpointCache->ListDirtyNotCommitted, &pEntry->NodeNotCommitted);
739 RTSpinlockRelease(pEndpointCache->LockList, &Tmp);
740
741 uint32_t cbDirty = ASMAtomicAddU32(&pCache->cbDirty, pEntry->cbData);
742
743 fDirtyBytesExceeded = (cbDirty >= pCache->cbCommitDirtyThreshold);
744 }
745
746 return fDirtyBytesExceeded;
747}
748
749
750/**
751 * Completes a task segment freeing all ressources and completes the task handle
752 * if everything was transfered.
753 *
754 * @returns Next task segment handle.
755 * @param pTaskSeg Task segment to complete.
756 * @param rc Status code to set.
757 */
758static PPDMACFILETASKSEG pdmacFileCacheTaskComplete(PPDMACFILETASKSEG pTaskSeg, int rc)
759{
760 PPDMACFILETASKSEG pNext = pTaskSeg->pNext;
761 PPDMASYNCCOMPLETIONTASKFILE pTaskFile = pTaskSeg->pTask;
762
763 if (RT_FAILURE(rc))
764 ASMAtomicCmpXchgS32(&pTaskFile->rc, rc, VINF_SUCCESS);
765
766 uint32_t uOld = ASMAtomicSubS32(&pTaskFile->cbTransferLeft, pTaskSeg->cbTransfer);
767 AssertMsg(uOld >= pTaskSeg->cbTransfer, ("New value would overflow\n"));
768 if (!(uOld - pTaskSeg->cbTransfer)
769 && !ASMAtomicXchgBool(&pTaskFile->fCompleted, true))
770 pdmR3AsyncCompletionCompleteTask(&pTaskFile->Core, pTaskFile->rc, true);
771
772 RTMemFree(pTaskSeg);
773
774 return pNext;
775}
776
777/**
778 * Completion callback for I/O tasks.
779 *
780 * @returns nothing.
781 * @param pTask The completed task.
782 * @param pvUser Opaque user data.
783 * @param rc Status code of the completed request.
784 */
785static void pdmacFileCacheTaskCompleted(PPDMACTASKFILE pTask, void *pvUser, int rc)
786{
787 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pvUser;
788 PPDMACFILECACHEGLOBAL pCache = pEntry->pCache;
789 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint = pEntry->pEndpoint;
790 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
791
792 /* Reference the entry now as we are clearing the I/O in progres flag
793 * which protects the entry till now. */
794 pdmacFileEpCacheEntryRef(pEntry);
795
796 RTSemRWRequestWrite(pEndpoint->DataCache.SemRWEntries, RT_INDEFINITE_WAIT);
797 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IO_IN_PROGRESS;
798
799 /* Process waiting segment list. The data in entry might have changed inbetween. */
800 bool fDirty = false;
801 PPDMACFILETASKSEG pComplete = pEntry->pWaitingHead;
802 PPDMACFILETASKSEG pCurr = pComplete;
803
804 AssertMsg((pCurr && pEntry->pWaitingTail) || (!pCurr && !pEntry->pWaitingTail),
805 ("The list tail was not updated correctly\n"));
806 pEntry->pWaitingTail = NULL;
807 pEntry->pWaitingHead = NULL;
808
809 if (pTask->enmTransferType == PDMACTASKFILETRANSFER_WRITE)
810 {
811 /*
812 * An error here is difficult to handle as the original request completed already.
813 * The error is logged for now and the VM is paused.
814 * If the user continues the entry is written again in the hope
815 * the user fixed the problem and the next write succeeds.
816 */
817 /** @todo r=aeichner: This solution doesn't work
818 * The user will get the message but the VM will hang afterwards
819 * VMR3Suspend() returns when the VM is suspended but suspending
820 * the VM will reopen the images readonly in DrvVD. They are closed first
821 * which will close the endpoints. This will block EMT while the
822 * I/O manager processes the close request but the IO manager is stuck
823 * in the VMR3Suspend call and can't process the request.
824 * Another problem is that closing the VM means flushing the cache
825 * but the entry failed and will probably fail again.
826 * No idea so far how to solve this problem... but the user gets informed
827 * at least.
828 */
829 if (RT_FAILURE(rc))
830 {
831 LogRel(("I/O cache: Error while writing entry at offset %RTfoff (%u bytes) to file \"%s\"\n",
832 pEntry->Core.Key, pEntry->cbData, pEndpoint->Core.pszUri));
833
834 rc = VMSetRuntimeError(pEndpoint->Core.pEpClass->pVM, 0, "CACHE_IOERR",
835 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);
836 AssertRC(rc);
837 rc = VMR3Suspend(pEndpoint->Core.pEpClass->pVM);
838 }
839 else
840 {
841 pEntry->fFlags &= ~PDMACFILECACHE_ENTRY_IS_DIRTY;
842
843 while (pCurr)
844 {
845 AssertMsg(pCurr->fWrite, ("Completed write entries should never have read tasks attached\n"));
846
847 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
848 fDirty = true;
849
850 pCurr = pCurr->pNext;
851 }
852 }
853 }
854 else
855 {
856 AssertMsg(pTask->enmTransferType == PDMACTASKFILETRANSFER_READ, ("Invalid transfer type\n"));
857 AssertMsg(!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IS_DIRTY),
858 ("Invalid flags set\n"));
859
860 while (pCurr)
861 {
862 if (pCurr->fWrite)
863 {
864 memcpy(pEntry->pbData + pCurr->uBufOffset, pCurr->pvBuf, pCurr->cbTransfer);
865 fDirty = true;
866 }
867 else
868 memcpy(pCurr->pvBuf, pEntry->pbData + pCurr->uBufOffset, pCurr->cbTransfer);
869
870 pCurr = pCurr->pNext;
871 }
872 }
873
874 bool fCommit = false;
875 if (fDirty)
876 fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
877
878 RTSemRWReleaseWrite(pEndpoint->DataCache.SemRWEntries);
879
880 /* Dereference so that it isn't protected anymore except we issued anyother write for it. */
881 pdmacFileEpCacheEntryRelease(pEntry);
882
883 if (fCommit)
884 pdmacFileCacheCommitDirtyEntries(pCache);
885
886 /* Complete waiters now. */
887 while (pComplete)
888 pComplete = pdmacFileCacheTaskComplete(pComplete, rc);
889}
890
891/**
892 * Commit timer callback.
893 */
894static void pdmacFileCacheCommitTimerCallback(PVM pVM, PTMTIMER pTimer, void *pvUser)
895{
896 PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pvUser;
897 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
898
899 LogFlowFunc(("Commit interval expired, commiting dirty entries\n"));
900
901 if (ASMAtomicReadU32(&pCache->cbDirty) > 0)
902 pdmacFileCacheCommitDirtyEntries(pCache);
903
904 TMTimerSetMillies(pTimer, pCache->u32CommitTimeoutMs);
905 LogFlowFunc(("Entries committed, going to sleep\n"));
906}
907
908/**
909 * Initializies the I/O cache.
910 *
911 * returns VBox status code.
912 * @param pClassFile The global class data for file endpoints.
913 * @param pCfgNode CFGM node to query configuration data from.
914 */
915int pdmacFileCacheInit(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile, PCFGMNODE pCfgNode)
916{
917 int rc = VINF_SUCCESS;
918 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
919
920 rc = CFGMR3QueryU32Def(pCfgNode, "CacheSize", &pCache->cbMax, 5 * _1M);
921 AssertLogRelRCReturn(rc, rc);
922
923 RTListInit(&pCache->ListEndpoints);
924 pCache->cRefs = 0;
925 pCache->cbCached = 0;
926 pCache->fCommitInProgress = 0;
927 LogFlowFunc((": Maximum number of bytes cached %u\n", pCache->cbMax));
928
929 /* Initialize members */
930 pCache->LruRecentlyUsedIn.pHead = NULL;
931 pCache->LruRecentlyUsedIn.pTail = NULL;
932 pCache->LruRecentlyUsedIn.cbCached = 0;
933
934 pCache->LruRecentlyUsedOut.pHead = NULL;
935 pCache->LruRecentlyUsedOut.pTail = NULL;
936 pCache->LruRecentlyUsedOut.cbCached = 0;
937
938 pCache->LruFrequentlyUsed.pHead = NULL;
939 pCache->LruFrequentlyUsed.pTail = NULL;
940 pCache->LruFrequentlyUsed.cbCached = 0;
941
942 pCache->cbRecentlyUsedInMax = (pCache->cbMax / 100) * 25; /* 25% of the buffer size */
943 pCache->cbRecentlyUsedOutMax = (pCache->cbMax / 100) * 50; /* 50% of the buffer size */
944 LogFlowFunc((": cbRecentlyUsedInMax=%u cbRecentlyUsedOutMax=%u\n", pCache->cbRecentlyUsedInMax, pCache->cbRecentlyUsedOutMax));
945
946 /** @todo r=aeichner: Experiment to find optimal default values */
947 rc = CFGMR3QueryU32Def(pCfgNode, "CacheCommitIntervalMs", &pCache->u32CommitTimeoutMs, 10000 /* 10sec */);
948 AssertLogRelRCReturn(rc, rc);
949 rc = CFGMR3QueryU32(pCfgNode, "CacheCommitThreshold", &pCache->cbCommitDirtyThreshold);
950 if ( rc == VERR_CFGM_VALUE_NOT_FOUND
951 || rc == VERR_CFGM_NO_PARENT)
952 {
953 /* Start committing after 50% of the cache are dirty */
954 pCache->cbCommitDirtyThreshold = pCache->cbMax / 2;
955 }
956 else
957 return rc;
958
959 STAMR3Register(pClassFile->Core.pVM, &pCache->cbMax,
960 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
961 "/PDM/AsyncCompletion/File/cbMax",
962 STAMUNIT_BYTES,
963 "Maximum cache size");
964 STAMR3Register(pClassFile->Core.pVM, &pCache->cbCached,
965 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
966 "/PDM/AsyncCompletion/File/cbCached",
967 STAMUNIT_BYTES,
968 "Currently used cache");
969 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedIn.cbCached,
970 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
971 "/PDM/AsyncCompletion/File/cbCachedMruIn",
972 STAMUNIT_BYTES,
973 "Number of bytes cached in MRU list");
974 STAMR3Register(pClassFile->Core.pVM, &pCache->LruRecentlyUsedOut.cbCached,
975 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
976 "/PDM/AsyncCompletion/File/cbCachedMruOut",
977 STAMUNIT_BYTES,
978 "Number of bytes cached in FRU list");
979 STAMR3Register(pClassFile->Core.pVM, &pCache->LruFrequentlyUsed.cbCached,
980 STAMTYPE_U32, STAMVISIBILITY_ALWAYS,
981 "/PDM/AsyncCompletion/File/cbCachedFru",
982 STAMUNIT_BYTES,
983 "Number of bytes cached in FRU ghost list");
984
985#ifdef VBOX_WITH_STATISTICS
986 STAMR3Register(pClassFile->Core.pVM, &pCache->cHits,
987 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
988 "/PDM/AsyncCompletion/File/CacheHits",
989 STAMUNIT_COUNT, "Number of hits in the cache");
990 STAMR3Register(pClassFile->Core.pVM, &pCache->cPartialHits,
991 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
992 "/PDM/AsyncCompletion/File/CachePartialHits",
993 STAMUNIT_COUNT, "Number of partial hits in the cache");
994 STAMR3Register(pClassFile->Core.pVM, &pCache->cMisses,
995 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
996 "/PDM/AsyncCompletion/File/CacheMisses",
997 STAMUNIT_COUNT, "Number of misses when accessing the cache");
998 STAMR3Register(pClassFile->Core.pVM, &pCache->StatRead,
999 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1000 "/PDM/AsyncCompletion/File/CacheRead",
1001 STAMUNIT_BYTES, "Number of bytes read from the cache");
1002 STAMR3Register(pClassFile->Core.pVM, &pCache->StatWritten,
1003 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1004 "/PDM/AsyncCompletion/File/CacheWritten",
1005 STAMUNIT_BYTES, "Number of bytes written to the cache");
1006 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeGet,
1007 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1008 "/PDM/AsyncCompletion/File/CacheTreeGet",
1009 STAMUNIT_TICKS_PER_CALL, "Time taken to access an entry in the tree");
1010 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeInsert,
1011 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1012 "/PDM/AsyncCompletion/File/CacheTreeInsert",
1013 STAMUNIT_TICKS_PER_CALL, "Time taken to insert an entry in the tree");
1014 STAMR3Register(pClassFile->Core.pVM, &pCache->StatTreeRemove,
1015 STAMTYPE_PROFILE_ADV, STAMVISIBILITY_ALWAYS,
1016 "/PDM/AsyncCompletion/File/CacheTreeRemove",
1017 STAMUNIT_TICKS_PER_CALL, "Time taken to remove an entry an the tree");
1018 STAMR3Register(pClassFile->Core.pVM, &pCache->StatBuffersReused,
1019 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1020 "/PDM/AsyncCompletion/File/CacheBuffersReused",
1021 STAMUNIT_COUNT, "Number of times a buffer could be reused");
1022#endif
1023
1024 /* Initialize the critical section */
1025 rc = RTCritSectInit(&pCache->CritSect);
1026
1027 if (RT_SUCCESS(rc))
1028 {
1029 /* Create the commit timer */
1030 if (pCache->u32CommitTimeoutMs > 0)
1031 rc = TMR3TimerCreateInternal(pClassFile->Core.pVM, TMCLOCK_REAL,
1032 pdmacFileCacheCommitTimerCallback,
1033 pClassFile,
1034 "Cache-Commit",
1035 &pClassFile->Cache.pTimerCommit);
1036
1037 if (RT_SUCCESS(rc))
1038 {
1039 LogRel(("AIOMgr: Cache successfully initialised. Cache size is %u bytes\n", pCache->cbMax));
1040 LogRel(("AIOMgr: Cache commit interval is %u ms\n", pCache->u32CommitTimeoutMs));
1041 LogRel(("AIOMgr: Cache commit threshold is %u bytes\n", pCache->cbCommitDirtyThreshold));
1042 return VINF_SUCCESS;
1043 }
1044
1045 RTCritSectDelete(&pCache->CritSect);
1046 }
1047
1048 return rc;
1049}
1050
1051/**
1052 * Destroysthe cache freeing all data.
1053 *
1054 * returns nothing.
1055 * @param pClassFile The global class data for file endpoints.
1056 */
1057void pdmacFileCacheDestroy(PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1058{
1059 PPDMACFILECACHEGLOBAL pCache = &pClassFile->Cache;
1060
1061 /* Make sure no one else uses the cache now */
1062 pdmacFileCacheLockEnter(pCache);
1063
1064 /* Cleanup deleting all cache entries waiting for in progress entries to finish. */
1065 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedIn);
1066 pdmacFileCacheDestroyList(&pCache->LruRecentlyUsedOut);
1067 pdmacFileCacheDestroyList(&pCache->LruFrequentlyUsed);
1068
1069 pdmacFileCacheLockLeave(pCache);
1070
1071 RTCritSectDelete(&pCache->CritSect);
1072}
1073
1074/**
1075 * Initializes per endpoint cache data
1076 * like the AVL tree used to access cached entries.
1077 *
1078 * @returns VBox status code.
1079 * @param pEndpoint The endpoint to init the cache for,
1080 * @param pClassFile The global class data for file endpoints.
1081 */
1082int pdmacFileEpCacheInit(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONEPCLASSFILE pClassFile)
1083{
1084 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1085
1086 pEndpointCache->pCache = &pClassFile->Cache;
1087 RTListInit(&pEndpointCache->ListDirtyNotCommitted);
1088 int rc = RTSpinlockCreate(&pEndpointCache->LockList);
1089
1090 if (RT_SUCCESS(rc))
1091 {
1092 rc = RTSemRWCreate(&pEndpointCache->SemRWEntries);
1093 if (RT_SUCCESS(rc))
1094 {
1095 pEndpointCache->pTree = (PAVLRFOFFTREE)RTMemAllocZ(sizeof(AVLRFOFFTREE));
1096 if (pEndpointCache->pTree)
1097 {
1098 pClassFile->Cache.cRefs++;
1099 RTListAppend(&pClassFile->Cache.ListEndpoints, &pEndpointCache->NodeCacheEndpoint);
1100
1101 /* Arm the timer if this is the first endpoint. */
1102 if ( pClassFile->Cache.cRefs == 1
1103 && pClassFile->Cache.u32CommitTimeoutMs > 0)
1104 rc = TMTimerSetMillies(pClassFile->Cache.pTimerCommit, pClassFile->Cache.u32CommitTimeoutMs);
1105 }
1106 else
1107 rc = VERR_NO_MEMORY;
1108
1109 if (RT_FAILURE(rc))
1110 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1111 }
1112
1113 if (RT_FAILURE(rc))
1114 RTSpinlockDestroy(pEndpointCache->LockList);
1115 }
1116
1117#ifdef VBOX_WITH_STATISTICS
1118 if (RT_SUCCESS(rc))
1119 {
1120 STAMR3RegisterF(pClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred,
1121 STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS,
1122 STAMUNIT_COUNT, "Number of deferred writes",
1123 "/PDM/AsyncCompletion/File/%s/Cache/DeferredWrites", RTPathFilename(pEndpoint->Core.pszUri));
1124 }
1125#endif
1126
1127 LogFlowFunc(("Leave rc=%Rrc\n", rc));
1128 return rc;
1129}
1130
1131/**
1132 * Callback for the AVL destroy routine. Frees a cache entry for this endpoint.
1133 *
1134 * @returns IPRT status code.
1135 * @param pNode The node to destroy.
1136 * @param pvUser Opaque user data.
1137 */
1138static int pdmacFileEpCacheEntryDestroy(PAVLRFOFFNODECORE pNode, void *pvUser)
1139{
1140 PPDMACFILECACHEENTRY pEntry = (PPDMACFILECACHEENTRY)pNode;
1141 PPDMACFILECACHEGLOBAL pCache = (PPDMACFILECACHEGLOBAL)pvUser;
1142 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEntry->pEndpoint->DataCache;
1143
1144 while (ASMAtomicReadU32(&pEntry->fFlags) & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY))
1145 {
1146 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1147 RTThreadSleep(250);
1148 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1149 }
1150
1151 AssertMsg(!(pEntry->fFlags & (PDMACFILECACHE_ENTRY_IO_IN_PROGRESS | PDMACFILECACHE_ENTRY_IS_DIRTY)),
1152 ("Entry is dirty and/or still in progress fFlags=%#x\n", pEntry->fFlags));
1153
1154 bool fUpdateCache = pEntry->pList == &pCache->LruFrequentlyUsed
1155 || pEntry->pList == &pCache->LruRecentlyUsedIn;
1156
1157 pdmacFileCacheEntryRemoveFromList(pEntry);
1158
1159 if (fUpdateCache)
1160 pdmacFileCacheSub(pCache, pEntry->cbData);
1161
1162 RTMemPageFree(pEntry->pbData, pEntry->cbData);
1163 RTMemFree(pEntry);
1164
1165 return VINF_SUCCESS;
1166}
1167
1168/**
1169 * Destroys all cache ressources used by the given endpoint.
1170 *
1171 * @returns nothing.
1172 * @param pEndpoint The endpoint to the destroy.
1173 */
1174void pdmacFileEpCacheDestroy(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint)
1175{
1176 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1177 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1178
1179 /* Make sure nobody is accessing the cache while we delete the tree. */
1180 pdmacFileCacheLockEnter(pCache);
1181 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1182 RTAvlrFileOffsetDestroy(pEndpointCache->pTree, pdmacFileEpCacheEntryDestroy, pCache);
1183 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1184
1185 RTSpinlockDestroy(pEndpointCache->LockList);
1186
1187 pCache->cRefs--;
1188 RTListNodeRemove(&pEndpointCache->NodeCacheEndpoint);
1189
1190 if ( !pCache->cRefs
1191 && pCache->u32CommitTimeoutMs > 0)
1192 TMTimerStop(pCache->pTimerCommit);
1193
1194 pdmacFileCacheLockLeave(pCache);
1195
1196 RTSemRWDestroy(pEndpointCache->SemRWEntries);
1197
1198#ifdef VBOX_WITH_STATISTICS
1199 PPDMASYNCCOMPLETIONEPCLASSFILE pEpClassFile = (PPDMASYNCCOMPLETIONEPCLASSFILE)pEndpoint->Core.pEpClass;
1200
1201 STAMR3Deregister(pEpClassFile->Core.pVM, &pEndpointCache->StatWriteDeferred);
1202#endif
1203}
1204
1205static PPDMACFILECACHEENTRY pdmacFileEpCacheGetCacheEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off)
1206{
1207 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1208 PPDMACFILECACHEENTRY pEntry = NULL;
1209
1210 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1211
1212 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1213 pEntry = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetRangeGet(pEndpointCache->pTree, off);
1214 if (pEntry)
1215 pdmacFileEpCacheEntryRef(pEntry);
1216 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1217
1218 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1219
1220 return pEntry;
1221}
1222
1223/**
1224 * Return the best fit cache entries for the given offset.
1225 *
1226 * @returns nothing.
1227 * @param pEndpointCache The endpoint cache.
1228 * @param off The offset.
1229 * @param pEntryAbove Where to store the pointer to the best fit entry above the
1230 * the given offset. NULL if not required.
1231 * @param pEntryBelow Where to store the pointer to the best fit entry below the
1232 * the given offset. NULL if not required.
1233 */
1234static void pdmacFileEpCacheGetCacheBestFitEntryByOffset(PPDMACFILEENDPOINTCACHE pEndpointCache, RTFOFF off,
1235 PPDMACFILECACHEENTRY *ppEntryAbove,
1236 PPDMACFILECACHEENTRY *ppEntryBelow)
1237{
1238 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1239
1240 STAM_PROFILE_ADV_START(&pCache->StatTreeGet, Cache);
1241
1242 RTSemRWRequestRead(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1243 if (ppEntryAbove)
1244 {
1245 *ppEntryAbove = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, true /*fAbove*/);
1246 if (*ppEntryAbove)
1247 pdmacFileEpCacheEntryRef(*ppEntryAbove);
1248 }
1249
1250 if (ppEntryBelow)
1251 {
1252 *ppEntryBelow = (PPDMACFILECACHEENTRY)RTAvlrFileOffsetGetBestFit(pEndpointCache->pTree, off, false /*fAbove*/);
1253 if (*ppEntryBelow)
1254 pdmacFileEpCacheEntryRef(*ppEntryBelow);
1255 }
1256 RTSemRWReleaseRead(pEndpointCache->SemRWEntries);
1257
1258 STAM_PROFILE_ADV_STOP(&pCache->StatTreeGet, Cache);
1259}
1260
1261static void pdmacFileEpCacheInsertEntry(PPDMACFILEENDPOINTCACHE pEndpointCache, PPDMACFILECACHEENTRY pEntry)
1262{
1263 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1264
1265 STAM_PROFILE_ADV_START(&pCache->StatTreeInsert, Cache);
1266 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1267 bool fInserted = RTAvlrFileOffsetInsert(pEndpointCache->pTree, &pEntry->Core);
1268 AssertMsg(fInserted, ("Node was not inserted into tree\n"));
1269 STAM_PROFILE_ADV_STOP(&pCache->StatTreeInsert, Cache);
1270 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1271}
1272
1273/**
1274 * Allocates and initializes a new entry for the cache.
1275 * The entry has a reference count of 1.
1276 *
1277 * @returns Pointer to the new cache entry or NULL if out of memory.
1278 * @param pCache The cache the entry belongs to.
1279 * @param pEndoint The endpoint the entry holds data for.
1280 * @param off Start offset.
1281 * @param cbData Size of the cache entry.
1282 * @param pbBuffer Pointer to the buffer to use.
1283 * NULL if a new buffer should be allocated.
1284 * The buffer needs to have the same size of the entry.
1285 */
1286static PPDMACFILECACHEENTRY pdmacFileCacheEntryAlloc(PPDMACFILECACHEGLOBAL pCache,
1287 PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1288 RTFOFF off, size_t cbData, uint8_t *pbBuffer)
1289{
1290 PPDMACFILECACHEENTRY pEntryNew = (PPDMACFILECACHEENTRY)RTMemAllocZ(sizeof(PDMACFILECACHEENTRY));
1291
1292 if (RT_UNLIKELY(!pEntryNew))
1293 return NULL;
1294
1295 pEntryNew->Core.Key = off;
1296 pEntryNew->Core.KeyLast = off + cbData - 1;
1297 pEntryNew->pEndpoint = pEndpoint;
1298 pEntryNew->pCache = pCache;
1299 pEntryNew->fFlags = 0;
1300 pEntryNew->cRefs = 1; /* We are using it now. */
1301 pEntryNew->pList = NULL;
1302 pEntryNew->cbData = cbData;
1303 pEntryNew->pWaitingHead = NULL;
1304 pEntryNew->pWaitingTail = NULL;
1305 if (pbBuffer)
1306 pEntryNew->pbData = pbBuffer;
1307 else
1308 pEntryNew->pbData = (uint8_t *)RTMemPageAlloc(cbData);
1309
1310 if (RT_UNLIKELY(!pEntryNew->pbData))
1311 {
1312 RTMemFree(pEntryNew);
1313 return NULL;
1314 }
1315
1316 return pEntryNew;
1317}
1318
1319/**
1320 * Adds a segment to the waiting list for a cache entry
1321 * which is currently in progress.
1322 *
1323 * @returns nothing.
1324 * @param pEntry The cache entry to add the segment to.
1325 * @param pSeg The segment to add.
1326 */
1327DECLINLINE(void) pdmacFileEpCacheEntryAddWaitingSegment(PPDMACFILECACHEENTRY pEntry, PPDMACFILETASKSEG pSeg)
1328{
1329 pSeg->pNext = NULL;
1330
1331 if (pEntry->pWaitingHead)
1332 {
1333 AssertPtr(pEntry->pWaitingTail);
1334
1335 pEntry->pWaitingTail->pNext = pSeg;
1336 pEntry->pWaitingTail = pSeg;
1337 }
1338 else
1339 {
1340 Assert(!pEntry->pWaitingTail);
1341
1342 pEntry->pWaitingHead = pSeg;
1343 pEntry->pWaitingTail = pSeg;
1344 }
1345}
1346
1347/**
1348 * Checks that a set of flags is set/clear acquiring the R/W semaphore
1349 * in exclusive mode.
1350 *
1351 * @returns true if the flag in fSet is set and the one in fClear is clear.
1352 * false othwerise.
1353 * The R/W semaphore is only held if true is returned.
1354 *
1355 * @param pEndpointCache The endpoint cache instance data.
1356 * @param pEntry The entry to check the flags for.
1357 * @param fSet The flag which is tested to be set.
1358 * @param fClear The flag which is tested to be clear.
1359 */
1360DECLINLINE(bool) pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(PPDMACFILEENDPOINTCACHE pEndpointCache,
1361 PPDMACFILECACHEENTRY pEntry,
1362 uint32_t fSet, uint32_t fClear)
1363{
1364 uint32_t fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1365 bool fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1366
1367 if (fPassed)
1368 {
1369 /* Acquire the lock and check again becuase the completion callback might have raced us. */
1370 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1371
1372 fFlags = ASMAtomicReadU32(&pEntry->fFlags);
1373 fPassed = ((fFlags & fSet) && !(fFlags & fClear));
1374
1375 /* Drop the lock if we didn't passed the test. */
1376 if (!fPassed)
1377 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1378 }
1379
1380 return fPassed;
1381}
1382
1383/**
1384 * Copies data to a buffer described by a I/O memory context.
1385 *
1386 * @returns nothing.
1387 * @param pIoMemCtx The I/O memory context to copy the data into.
1388 * @param pbData Pointer to the data data to copy.
1389 * @param cbData Amount of data to copy.
1390 */
1391static void pdmacFileEpCacheCopyToIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1392 uint8_t *pbData,
1393 size_t cbData)
1394{
1395 while (cbData)
1396 {
1397 size_t cbCopy = cbData;
1398 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1399
1400 AssertPtr(pbBuf);
1401
1402 memcpy(pbBuf, pbData, cbCopy);
1403
1404 cbData -= cbCopy;
1405 pbData += cbCopy;
1406 }
1407}
1408
1409/**
1410 * Copies data from a buffer described by a I/O memory context.
1411 *
1412 * @returns nothing.
1413 * @param pIoMemCtx The I/O memory context to copy the data from.
1414 * @param pbData Pointer to the destination buffer.
1415 * @param cbData Amount of data to copy.
1416 */
1417static void pdmacFileEpCacheCopyFromIoMemCtx(PPDMIOMEMCTX pIoMemCtx,
1418 uint8_t *pbData,
1419 size_t cbData)
1420{
1421 while (cbData)
1422 {
1423 size_t cbCopy = cbData;
1424 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbCopy);
1425
1426 AssertPtr(pbBuf);
1427
1428 memcpy(pbData, pbBuf, cbCopy);
1429
1430 cbData -= cbCopy;
1431 pbData += cbCopy;
1432 }
1433}
1434
1435/**
1436 * Add a buffer described by the I/O memory context
1437 * to the entry waiting for completion.
1438 *
1439 * @returns nothing.
1440 * @param pEntry The entry to add the buffer to.
1441 * @param pTask Task associated with the buffer.
1442 * @param pIoMemCtx The memory context to use.
1443 * @param OffDiff Offset from the start of the buffer
1444 * in the entry.
1445 * @param cbData Amount of data to wait for onthis entry.
1446 * @param fWrite Flag whether the task waits because it wants to write
1447 * to the cache entry.
1448 */
1449static void pdmacFileEpCacheEntryWaitersAdd(PPDMACFILECACHEENTRY pEntry,
1450 PPDMASYNCCOMPLETIONTASKFILE pTask,
1451 PPDMIOMEMCTX pIoMemCtx,
1452 RTFOFF OffDiff,
1453 size_t cbData,
1454 bool fWrite)
1455{
1456 while (cbData)
1457 {
1458 PPDMACFILETASKSEG pSeg = (PPDMACFILETASKSEG)RTMemAllocZ(sizeof(PDMACFILETASKSEG));
1459 size_t cbSeg = cbData;
1460 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1461
1462 pSeg->pTask = pTask;
1463 pSeg->uBufOffset = OffDiff;
1464 pSeg->cbTransfer = cbSeg;
1465 pSeg->pvBuf = pbBuf;
1466 pSeg->fWrite = fWrite;
1467
1468 pdmacFileEpCacheEntryAddWaitingSegment(pEntry, pSeg);
1469
1470 cbData -= cbSeg;
1471 OffDiff += cbSeg;
1472 }
1473}
1474
1475/**
1476 * Passthrough a part of a request directly to the I/O manager
1477 * handling the endpoint.
1478 *
1479 * @returns nothing.
1480 * @param pEndpoint The endpoint.
1481 * @param pTask The task.
1482 * @param pIoMemCtx The I/O memory context to use.
1483 * @param offStart Offset to start transfer from.
1484 * @param cbData Amount of data to transfer.
1485 * @param enmTransferType The transfer type (read/write)
1486 */
1487static void pdmacFileEpCacheRequestPassthrough(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1488 PPDMASYNCCOMPLETIONTASKFILE pTask,
1489 PPDMIOMEMCTX pIoMemCtx,
1490 RTFOFF offStart, size_t cbData,
1491 PDMACTASKFILETRANSFER enmTransferType)
1492{
1493 while (cbData)
1494 {
1495 size_t cbSeg = cbData;
1496 uint8_t *pbBuf = pdmIoMemCtxGetBuffer(pIoMemCtx, &cbSeg);
1497 PPDMACTASKFILE pIoTask = pdmacFileTaskAlloc(pEndpoint);
1498 AssertPtr(pIoTask);
1499
1500 pIoTask->pEndpoint = pEndpoint;
1501 pIoTask->enmTransferType = enmTransferType;
1502 pIoTask->Off = offStart;
1503 pIoTask->DataSeg.cbSeg = cbSeg;
1504 pIoTask->DataSeg.pvSeg = pbBuf;
1505 pIoTask->pvUser = pTask;
1506 pIoTask->pfnCompleted = pdmacFileEpTaskCompleted;
1507
1508 offStart += cbSeg;
1509 cbData -= cbSeg;
1510
1511 /* Send it off to the I/O manager. */
1512 pdmacFileEpAddTask(pEndpoint, pIoTask);
1513 }
1514}
1515
1516/**
1517 * Calculate aligned offset and size for a new cache entry
1518 * which do not intersect with an already existing entry and the
1519 * file end.
1520 *
1521 * @returns The number of bytes the entry can hold of the requested amount
1522 * of byte.
1523 * @param pEndpoint The endpoint.
1524 * @param pEndpointCache The endpoint cache.
1525 * @param off The start offset.
1526 * @param cb The number of bytes the entry needs to hold at least.
1527 * @param uAlignment Alignment of the boundary sizes.
1528 * @param poffAligned Where to store the aligned offset.
1529 * @param pcbAligned Where to store the aligned size of the entry.
1530 */
1531static size_t pdmacFileEpCacheEntryBoundariesCalc(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1532 PPDMACFILEENDPOINTCACHE pEndpointCache,
1533 RTFOFF off, size_t cb,
1534 unsigned uAlignment,
1535 RTFOFF *poffAligned, size_t *pcbAligned)
1536{
1537 size_t cbAligned;
1538 size_t cbInEntry = 0;
1539 RTFOFF offAligned;
1540 PPDMACFILECACHEENTRY pEntryAbove = NULL;
1541 PPDMACFILECACHEENTRY pEntryBelow = NULL;
1542
1543 /* Get the best fit entries around the offset */
1544 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1545 &pEntryAbove, &pEntryBelow);
1546
1547 /* Log the info */
1548 LogFlow(("%sest fit entry below off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1549 pEntryBelow ? "B" : "No b",
1550 off,
1551 pEntryBelow ? pEntryBelow->Core.Key : 0,
1552 pEntryBelow ? pEntryBelow->Core.KeyLast : 0,
1553 pEntryBelow ? pEntryBelow->cbData : 0));
1554
1555 LogFlow(("%sest fit entry above off=%RTfoff (BestFit=%RTfoff BestFitEnd=%RTfoff BestFitSize=%u)\n",
1556 pEntryAbove ? "B" : "No b",
1557 off,
1558 pEntryAbove ? pEntryAbove->Core.Key : 0,
1559 pEntryAbove ? pEntryAbove->Core.KeyLast : 0,
1560 pEntryAbove ? pEntryAbove->cbData : 0));
1561
1562 /* Align the offset first. */
1563 offAligned = off & ~(RTFOFF)(512-1);
1564 if ( pEntryBelow
1565 && offAligned <= pEntryBelow->Core.KeyLast)
1566 offAligned = pEntryBelow->Core.KeyLast;
1567
1568 if ( pEntryAbove
1569 && off + (RTFOFF)cb > pEntryAbove->Core.Key)
1570 {
1571 cbInEntry = pEntryAbove->Core.Key - off;
1572 cbAligned = pEntryAbove->Core.Key - offAligned;
1573 }
1574 else
1575 {
1576 /*
1577 * Align the size to a 4KB boundary.
1578 * Memory size is aligned to a page boundary
1579 * and memory is wasted if the size is rather small.
1580 * (For example reads with a size of 512 bytes).
1581 */
1582 cbInEntry = cb;
1583 cbAligned = RT_ALIGN_Z(cb + (off - offAligned), uAlignment);
1584
1585 /*
1586 * Clip to file size if the original request doesn't
1587 * exceed the file (not an appending write)
1588 */
1589 uint64_t cbReq = off + (RTFOFF)cb;
1590 if (cbReq >= pEndpoint->cbFile)
1591 cbAligned = cbReq - offAligned;
1592 else
1593 cbAligned = RT_MIN(pEndpoint->cbFile - offAligned, cbAligned);
1594 if (pEntryAbove)
1595 {
1596 Assert(pEntryAbove->Core.Key >= off);
1597 cbAligned = RT_MIN(cbAligned, (uint64_t)pEntryAbove->Core.Key - offAligned);
1598 }
1599 }
1600
1601 /* A few sanity checks */
1602 AssertMsg(!pEntryBelow || pEntryBelow->Core.KeyLast < offAligned,
1603 ("Aligned start offset intersects with another cache entry\n"));
1604 AssertMsg(!pEntryAbove || (offAligned + (RTFOFF)cbAligned) <= pEntryAbove->Core.Key,
1605 ("Aligned size intersects with another cache entry\n"));
1606 Assert(cbInEntry <= cbAligned);
1607 AssertMsg( ( offAligned + (RTFOFF)cbAligned <= (RTFOFF)pEndpoint->cbFile
1608 && off + (RTFOFF)cb <= (RTFOFF)pEndpoint->cbFile)
1609 || (offAligned + (RTFOFF)cbAligned <= off + (RTFOFF)cb),
1610 ("Unwanted file size increase\n"));
1611
1612 if (pEntryBelow)
1613 pdmacFileEpCacheEntryRelease(pEntryBelow);
1614 if (pEntryAbove)
1615 pdmacFileEpCacheEntryRelease(pEntryAbove);
1616
1617 LogFlow(("offAligned=%RTfoff cbAligned=%u\n", offAligned, cbAligned));
1618
1619 *poffAligned = offAligned;
1620 *pcbAligned = cbAligned;
1621
1622 return cbInEntry;
1623}
1624
1625/**
1626 * Create a new cache entry evicting data from the cache if required.
1627 *
1628 * @returns Pointer to the new cache entry or NULL
1629 * if not enough bytes could be evicted from the cache.
1630 * @param pEndpoint The endpoint.
1631 * @param pEndpointCache The endpoint cache.
1632 * @param off The offset.
1633 * @param cb Number of bytes the cache entry should have.
1634 * @param uAlignment Alignment the size of the entry should have.
1635 * @param pcbData Where to store the number of bytes the new
1636 * entry can hold. May be lower than actually requested
1637 * due to another entry intersecting the access range.
1638 */
1639static PPDMACFILECACHEENTRY pdmacFileEpCacheEntryCreate(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint,
1640 PPDMACFILEENDPOINTCACHE pEndpointCache,
1641 RTFOFF off, size_t cb,
1642 unsigned uAlignment,
1643 size_t *pcbData)
1644{
1645 RTFOFF offStart = 0;
1646 size_t cbEntry = 0;
1647 PPDMACFILECACHEENTRY pEntryNew = NULL;
1648 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1649 uint8_t *pbBuffer = NULL;
1650
1651 *pcbData = pdmacFileEpCacheEntryBoundariesCalc(pEndpoint,
1652 pEndpointCache,
1653 off, cb,
1654 uAlignment,
1655 &offStart, &cbEntry);
1656
1657 pdmacFileCacheLockEnter(pCache);
1658 bool fEnough = pdmacFileCacheReclaim(pCache, cbEntry, true, &pbBuffer);
1659
1660 if (fEnough)
1661 {
1662 LogFlow(("Evicted enough bytes (%u requested). Creating new cache entry\n", cbEntry));
1663
1664 pEntryNew = pdmacFileCacheEntryAlloc(pCache, pEndpoint,
1665 offStart, cbEntry,
1666 pbBuffer);
1667 if (RT_LIKELY(pEntryNew))
1668 {
1669 pdmacFileCacheEntryAddToList(&pCache->LruRecentlyUsedIn, pEntryNew);
1670 pdmacFileCacheAdd(pCache, cbEntry);
1671 pdmacFileCacheLockLeave(pCache);
1672
1673 pdmacFileEpCacheInsertEntry(pEndpointCache, pEntryNew);
1674
1675 AssertMsg( (off >= pEntryNew->Core.Key)
1676 && (off + (RTFOFF)*pcbData <= pEntryNew->Core.KeyLast + 1),
1677 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1678 off, pEntryNew->Core.Key));
1679 }
1680 else
1681 pdmacFileCacheLockLeave(pCache);
1682 }
1683 else
1684 pdmacFileCacheLockLeave(pCache);
1685
1686 return pEntryNew;
1687}
1688
1689/**
1690 * Reads the specified data from the endpoint using the cache if possible.
1691 *
1692 * @returns VBox status code.
1693 * @param pEndpoint The endpoint to read from.
1694 * @param pTask The task structure used as identifier for this request.
1695 * @param off The offset to start reading from.
1696 * @param paSegments Pointer to the array holding the destination buffers.
1697 * @param cSegments Number of segments in the array.
1698 * @param cbRead Number of bytes to read.
1699 */
1700int pdmacFileEpCacheRead(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1701 RTFOFF off, PCRTSGSEG paSegments, size_t cSegments,
1702 size_t cbRead)
1703{
1704 int rc = VINF_SUCCESS;
1705 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1706 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1707 PPDMACFILECACHEENTRY pEntry;
1708
1709 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbRead=%u\n",
1710 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbRead));
1711
1712 /* Set to completed to make sure that the task is valid while we access it. */
1713 ASMAtomicWriteBool(&pTask->fCompleted, true);
1714
1715 /* Init the I/O memory context */
1716 PDMIOMEMCTX IoMemCtx;
1717 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1718
1719 while (cbRead)
1720 {
1721 size_t cbToRead;
1722
1723 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1724
1725 /*
1726 * If there is no entry we try to create a new one eviciting unused pages
1727 * if the cache is full. If this is not possible we will pass the request through
1728 * and skip the caching (all entries may be still in progress so they can't
1729 * be evicted)
1730 * If we have an entry it can be in one of the LRU lists where the entry
1731 * contains data (recently used or frequently used LRU) so we can just read
1732 * the data we need and put the entry at the head of the frequently used LRU list.
1733 * In case the entry is in one of the ghost lists it doesn't contain any data.
1734 * We have to fetch it again evicting pages from either T1 or T2 to make room.
1735 */
1736 if (pEntry)
1737 {
1738 RTFOFF OffDiff = off - pEntry->Core.Key;
1739
1740 AssertMsg(off >= pEntry->Core.Key,
1741 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1742 off, pEntry->Core.Key));
1743
1744 AssertPtr(pEntry->pList);
1745
1746 cbToRead = RT_MIN(pEntry->cbData - OffDiff, cbRead);
1747
1748 AssertMsg(off + (RTFOFF)cbToRead <= pEntry->Core.Key + pEntry->Core.KeyLast + 1,
1749 ("Buffer of cache entry exceeded off=%RTfoff cbToRead=%d\n",
1750 off, cbToRead));
1751
1752 cbRead -= cbToRead;
1753
1754 if (!cbRead)
1755 STAM_COUNTER_INC(&pCache->cHits);
1756 else
1757 STAM_COUNTER_INC(&pCache->cPartialHits);
1758
1759 STAM_COUNTER_ADD(&pCache->StatRead, cbToRead);
1760
1761 /* Ghost lists contain no data. */
1762 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1763 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1764 {
1765 if (pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
1766 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
1767 PDMACFILECACHE_ENTRY_IS_DIRTY))
1768 {
1769 /* Entry didn't completed yet. Append to the list */
1770 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1771 &IoMemCtx,
1772 OffDiff, cbToRead,
1773 false /* fWrite */);
1774 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1775 }
1776 else
1777 {
1778 /* Read as much as we can from the entry. */
1779 pdmacFileEpCacheCopyToIoMemCtx(&IoMemCtx, pEntry->pbData + OffDiff, cbToRead);
1780 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToRead);
1781 }
1782
1783 /* Move this entry to the top position */
1784 if (pEntry->pList == &pCache->LruFrequentlyUsed)
1785 {
1786 pdmacFileCacheLockEnter(pCache);
1787 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1788 pdmacFileCacheLockLeave(pCache);
1789 }
1790 /* Release the entry */
1791 pdmacFileEpCacheEntryRelease(pEntry);
1792 }
1793 else
1794 {
1795 uint8_t *pbBuffer = NULL;
1796
1797 LogFlow(("Fetching data for ghost entry %#p from file\n", pEntry));
1798
1799 pdmacFileCacheLockEnter(pCache);
1800 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
1801 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
1802
1803 /* Move the entry to Am and fetch it to the cache. */
1804 if (fEnough)
1805 {
1806 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
1807 pdmacFileCacheAdd(pCache, pEntry->cbData);
1808 pdmacFileCacheLockLeave(pCache);
1809
1810 if (pbBuffer)
1811 pEntry->pbData = pbBuffer;
1812 else
1813 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
1814 AssertPtr(pEntry->pbData);
1815
1816 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1817 &IoMemCtx,
1818 OffDiff, cbToRead,
1819 false /* fWrite */);
1820 pdmacFileCacheReadFromEndpoint(pEntry);
1821 /* Release the entry */
1822 pdmacFileEpCacheEntryRelease(pEntry);
1823 }
1824 else
1825 {
1826 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
1827 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
1828 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
1829 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
1830 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
1831
1832 pdmacFileCacheLockLeave(pCache);
1833
1834 RTMemFree(pEntry);
1835
1836 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1837 &IoMemCtx, off, cbToRead,
1838 PDMACTASKFILETRANSFER_READ);
1839 }
1840 }
1841 }
1842 else
1843 {
1844#ifdef VBOX_WITH_IO_READ_CACHE
1845 /* No entry found for this offset. Create a new entry and fetch the data to the cache. */
1846 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
1847 pEndpointCache,
1848 off, cbRead,
1849 PAGE_SIZE,
1850 &cbToRead);
1851
1852 cbRead -= cbToRead;
1853
1854 if (pEntryNew)
1855 {
1856 if (!cbRead)
1857 STAM_COUNTER_INC(&pCache->cMisses);
1858 else
1859 STAM_COUNTER_INC(&pCache->cPartialHits);
1860
1861 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
1862 &IoMemCtx,
1863 off - pEntryNew->Core.Key,
1864 cbToRead,
1865 false /* fWrite */);
1866 pdmacFileCacheReadFromEndpoint(pEntryNew);
1867 pdmacFileEpCacheEntryRelease(pEntryNew); /* it is protected by the I/O in progress flag now. */
1868 }
1869 else
1870 {
1871 /*
1872 * There is not enough free space in the cache.
1873 * Pass the request directly to the I/O manager.
1874 */
1875 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToRead));
1876
1877 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1878 &IoMemCtx, off, cbToRead,
1879 PDMACTASKFILETRANSFER_READ);
1880 }
1881#else
1882 /* Clip read size if neccessary. */
1883 PPDMACFILECACHEENTRY pEntryAbove;
1884 pdmacFileEpCacheGetCacheBestFitEntryByOffset(pEndpointCache, off,
1885 &pEntryAbove, NULL);
1886
1887 if (pEntryAbove)
1888 {
1889 if (off + (RTFOFF)cbRead > pEntryAbove->Core.Key)
1890 cbToRead = pEntryAbove->Core.Key - off;
1891 else
1892 cbToRead = cbRead;
1893
1894 pdmacFileEpCacheEntryRelease(pEntryAbove);
1895 }
1896 else
1897 cbToRead = cbRead;
1898
1899 cbRead -= cbToRead;
1900 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
1901 &IoMemCtx, off, cbToRead,
1902 PDMACTASKFILETRANSFER_READ);
1903#endif
1904 }
1905 off += cbToRead;
1906 }
1907
1908 ASMAtomicWriteBool(&pTask->fCompleted, false);
1909
1910 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
1911 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
1912 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
1913 else
1914 rc = VINF_AIO_TASK_PENDING;
1915
1916 LogFlowFunc((": Leave rc=%Rrc\n", rc));
1917
1918 return rc;
1919}
1920
1921/**
1922 * Writes the given data to the endpoint using the cache if possible.
1923 *
1924 * @returns VBox status code.
1925 * @param pEndpoint The endpoint to write to.
1926 * @param pTask The task structure used as identifier for this request.
1927 * @param off The offset to start writing to
1928 * @param paSegments Pointer to the array holding the source buffers.
1929 * @param cSegments Number of segments in the array.
1930 * @param cbWrite Number of bytes to write.
1931 */
1932int pdmacFileEpCacheWrite(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask,
1933 RTFOFF off, PCRTSGSEG paSegments, size_t cSegments,
1934 size_t cbWrite)
1935{
1936 int rc = VINF_SUCCESS;
1937 PPDMACFILEENDPOINTCACHE pEndpointCache = &pEndpoint->DataCache;
1938 PPDMACFILECACHEGLOBAL pCache = pEndpointCache->pCache;
1939 PPDMACFILECACHEENTRY pEntry;
1940
1941 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p off=%RTfoff paSegments=%#p cSegments=%u cbWrite=%u\n",
1942 pEndpoint, pEndpoint->Core.pszUri, pTask, off, paSegments, cSegments, cbWrite));
1943
1944 /* Set to completed to make sure that the task is valid while we access it. */
1945 ASMAtomicWriteBool(&pTask->fCompleted, true);
1946
1947 /* Init the I/O memory context */
1948 PDMIOMEMCTX IoMemCtx;
1949 pdmIoMemCtxInit(&IoMemCtx, paSegments, cSegments);
1950
1951 while (cbWrite)
1952 {
1953 size_t cbToWrite;
1954
1955 pEntry = pdmacFileEpCacheGetCacheEntryByOffset(pEndpointCache, off);
1956
1957 if (pEntry)
1958 {
1959 /* Write the data into the entry and mark it as dirty */
1960 AssertPtr(pEntry->pList);
1961
1962 RTFOFF OffDiff = off - pEntry->Core.Key;
1963
1964 AssertMsg(off >= pEntry->Core.Key,
1965 ("Overflow in calculation off=%RTfoff OffsetAligned=%RTfoff\n",
1966 off, pEntry->Core.Key));
1967
1968 cbToWrite = RT_MIN(pEntry->cbData - OffDiff, cbWrite);
1969 cbWrite -= cbToWrite;
1970
1971 if (!cbWrite)
1972 STAM_COUNTER_INC(&pCache->cHits);
1973 else
1974 STAM_COUNTER_INC(&pCache->cPartialHits);
1975
1976 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
1977
1978 /* Ghost lists contain no data. */
1979 if ( (pEntry->pList == &pCache->LruRecentlyUsedIn)
1980 || (pEntry->pList == &pCache->LruFrequentlyUsed))
1981 {
1982 /* Check if the entry is dirty. */
1983 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
1984 PDMACFILECACHE_ENTRY_IS_DIRTY,
1985 0))
1986 {
1987 /* If it is dirty but not in progrss just update the data. */
1988 if (!(pEntry->fFlags & PDMACFILECACHE_ENTRY_IO_IN_PROGRESS))
1989 {
1990 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
1991 pEntry->pbData + OffDiff,
1992 cbToWrite);
1993 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
1994 }
1995 else
1996 {
1997 /* The data isn't written to the file yet */
1998 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
1999 &IoMemCtx,
2000 OffDiff, cbToWrite,
2001 true /* fWrite */);
2002 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2003 }
2004
2005 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2006 }
2007 else /* Dirty bit not set */
2008 {
2009 /*
2010 * Check if a read is in progress for this entry.
2011 * We have to defer processing in that case.
2012 */
2013 if(pdmacFileEpCacheEntryFlagIsSetClearAcquireLock(pEndpointCache, pEntry,
2014 PDMACFILECACHE_ENTRY_IO_IN_PROGRESS,
2015 0))
2016 {
2017 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2018 &IoMemCtx,
2019 OffDiff, cbToWrite,
2020 true /* fWrite */);
2021 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2022 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2023 }
2024 else /* I/O in progress flag not set */
2025 {
2026 /* Write as much as we can into the entry and update the file. */
2027 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2028 pEntry->pbData + OffDiff,
2029 cbToWrite);
2030 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2031
2032 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntry);
2033 if (fCommit)
2034 pdmacFileCacheCommitDirtyEntries(pCache);
2035 }
2036 } /* Dirty bit not set */
2037
2038 /* Move this entry to the top position */
2039 if (pEntry->pList == &pCache->LruFrequentlyUsed)
2040 {
2041 pdmacFileCacheLockEnter(pCache);
2042 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2043 pdmacFileCacheLockLeave(pCache);
2044 }
2045
2046 pdmacFileEpCacheEntryRelease(pEntry);
2047 }
2048 else /* Entry is on the ghost list */
2049 {
2050 uint8_t *pbBuffer = NULL;
2051
2052 pdmacFileCacheLockEnter(pCache);
2053 pdmacFileCacheEntryRemoveFromList(pEntry); /* Remove it before we remove data, otherwise it may get freed when evicting data. */
2054 bool fEnough = pdmacFileCacheReclaim(pCache, pEntry->cbData, true, &pbBuffer);
2055
2056 if (fEnough)
2057 {
2058 /* Move the entry to Am and fetch it to the cache. */
2059 pdmacFileCacheEntryAddToList(&pCache->LruFrequentlyUsed, pEntry);
2060 pdmacFileCacheAdd(pCache, pEntry->cbData);
2061 pdmacFileCacheLockLeave(pCache);
2062
2063 if (pbBuffer)
2064 pEntry->pbData = pbBuffer;
2065 else
2066 pEntry->pbData = (uint8_t *)RTMemPageAlloc(pEntry->cbData);
2067 AssertPtr(pEntry->pbData);
2068
2069 pdmacFileEpCacheEntryWaitersAdd(pEntry, pTask,
2070 &IoMemCtx,
2071 OffDiff, cbToWrite,
2072 true /* fWrite */);
2073 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2074 pdmacFileCacheReadFromEndpoint(pEntry);
2075
2076 /* Release the reference. If it is still needed the I/O in progress flag should protect it now. */
2077 pdmacFileEpCacheEntryRelease(pEntry);
2078 }
2079 else
2080 {
2081 RTSemRWRequestWrite(pEndpointCache->SemRWEntries, RT_INDEFINITE_WAIT);
2082 STAM_PROFILE_ADV_START(&pCache->StatTreeRemove, Cache);
2083 RTAvlrFileOffsetRemove(pEndpointCache->pTree, pEntry->Core.Key);
2084 STAM_PROFILE_ADV_STOP(&pCache->StatTreeRemove, Cache);
2085 RTSemRWReleaseWrite(pEndpointCache->SemRWEntries);
2086
2087 pdmacFileCacheLockLeave(pCache);
2088
2089 RTMemFree(pEntry);
2090 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2091 &IoMemCtx, off, cbToWrite,
2092 PDMACTASKFILETRANSFER_WRITE);
2093 }
2094 }
2095 }
2096 else /* No entry found */
2097 {
2098 /*
2099 * No entry found. Try to create a new cache entry to store the data in and if that fails
2100 * write directly to the file.
2101 */
2102 PPDMACFILECACHEENTRY pEntryNew = pdmacFileEpCacheEntryCreate(pEndpoint,
2103 pEndpointCache,
2104 off, cbWrite,
2105 512,
2106 &cbToWrite);
2107
2108 cbWrite -= cbToWrite;
2109
2110 if (pEntryNew)
2111 {
2112 RTFOFF offDiff = off - pEntryNew->Core.Key;
2113
2114 STAM_COUNTER_INC(&pCache->cHits);
2115
2116 /*
2117 * Check if it is possible to just write the data without waiting
2118 * for it to get fetched first.
2119 */
2120 if (!offDiff && pEntryNew->cbData == cbToWrite)
2121 {
2122 pdmacFileEpCacheCopyFromIoMemCtx(&IoMemCtx,
2123 pEntryNew->pbData,
2124 cbToWrite);
2125 ASMAtomicSubS32(&pTask->cbTransferLeft, cbToWrite);
2126
2127 bool fCommit = pdmacFileCacheAddDirtyEntry(pEndpointCache, pEntryNew);
2128 if (fCommit)
2129 pdmacFileCacheCommitDirtyEntries(pCache);
2130 STAM_COUNTER_ADD(&pCache->StatWritten, cbToWrite);
2131 }
2132 else
2133 {
2134 /* Defer the write and fetch the data from the endpoint. */
2135 pdmacFileEpCacheEntryWaitersAdd(pEntryNew, pTask,
2136 &IoMemCtx,
2137 offDiff, cbToWrite,
2138 true /* fWrite */);
2139 STAM_COUNTER_INC(&pEndpointCache->StatWriteDeferred);
2140 pdmacFileCacheReadFromEndpoint(pEntryNew);
2141 }
2142
2143 pdmacFileEpCacheEntryRelease(pEntryNew);
2144 }
2145 else
2146 {
2147 /*
2148 * There is not enough free space in the cache.
2149 * Pass the request directly to the I/O manager.
2150 */
2151 LogFlow(("Couldn't evict %u bytes from the cache. Remaining request will be passed through\n", cbToWrite));
2152
2153 STAM_COUNTER_INC(&pCache->cMisses);
2154
2155 pdmacFileEpCacheRequestPassthrough(pEndpoint, pTask,
2156 &IoMemCtx, off, cbToWrite,
2157 PDMACTASKFILETRANSFER_WRITE);
2158 }
2159 }
2160
2161 off += cbToWrite;
2162 }
2163
2164 ASMAtomicWriteBool(&pTask->fCompleted, false);
2165
2166 if (ASMAtomicReadS32(&pTask->cbTransferLeft) == 0
2167 && !ASMAtomicXchgBool(&pTask->fCompleted, true))
2168 pdmR3AsyncCompletionCompleteTask(&pTask->Core, VINF_SUCCESS, false);
2169 else
2170 rc = VINF_AIO_TASK_PENDING;
2171
2172 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2173
2174 return rc;
2175}
2176
2177int pdmacFileEpCacheFlush(PPDMASYNCCOMPLETIONENDPOINTFILE pEndpoint, PPDMASYNCCOMPLETIONTASKFILE pTask)
2178{
2179 int rc = VINF_SUCCESS;
2180
2181 LogFlowFunc((": pEndpoint=%#p{%s} pTask=%#p\n",
2182 pEndpoint, pEndpoint->Core.pszUri, pTask));
2183
2184 /* Commit dirty entries in the cache. */
2185 pdmacFileCacheEndpointCommit(&pEndpoint->DataCache);
2186
2187 LogFlowFunc((": Leave rc=%Rrc\n", rc));
2188 return rc;
2189}
2190
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