/* $Id: PDMQueue.cpp 4071 2007-08-07 17:07:59Z vboxsync $ */ /** @file * PDM Queue - Transport data and tasks to EMT and R3. */ /* * Copyright (C) 2006-2007 innotek GmbH * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE * distribution. VirtualBox OSE is distributed in the hope that it will * be useful, but WITHOUT ANY WARRANTY of any kind. */ /******************************************************************************* * Header Files * *******************************************************************************/ #define LOG_GROUP LOG_GROUP_PDM_QUEUE #include "PDMInternal.h" #include #include #include #include #include #include #include #include #include /******************************************************************************* * Internal Functions * *******************************************************************************/ static inline void pdmR3QueueFree(PPDMQUEUE pQueue, PPDMQUEUEITEMCORE pItem); static bool pdmR3QueueFlush(PPDMQUEUE pQueue); static DECLCALLBACK(void) pdmR3QueueTimer(PVM pVM, PTMTIMER pTimer, void *pvUser); /** * Internal worker for the queue creation apis. * * @returns VBox status. * @param pVM VM handle. * @param cbItem Item size. * @param cItems Number of items. * @param cMilliesInterval Number of milliseconds between polling the queue. * If 0 then the emulation thread will be notified whenever an item arrives. * @param fGCEnabled Set if the queue will be used from GC and need to be allocated from the Hyper heap. * @param ppQueue Where to store the queue handle. */ static int pdmR3QueueCreate(PVM pVM, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval, bool fGCEnabled, PPDMQUEUE *ppQueue) { /* * Validate input. */ if (cbItem < sizeof(PDMQUEUEITEMCORE)) { AssertMsgFailed(("cbItem=%d\n", cbItem)); return VERR_INVALID_PARAMETER; } if (cItems < 1 || cItems >= 0x10000) { AssertMsgFailed(("cItems=%d valid:[1-65535]\n", cItems)); return VERR_INVALID_PARAMETER; } /* * Align the item size and calculate the structure size. */ cbItem = RT_ALIGN(cbItem, sizeof(RTUINTPTR)); unsigned cb = cbItem * cItems + RT_ALIGN_Z(RT_OFFSETOF(PDMQUEUE, aFreeItems[cItems + PDMQUEUE_FREE_SLACK]), 16); PPDMQUEUE pQueue; int rc; if (fGCEnabled) rc = MMHyperAlloc(pVM, cb, 0, MM_TAG_PDM_QUEUE, (void **)&pQueue ); else rc = MMR3HeapAllocZEx(pVM, MM_TAG_PDM_QUEUE, cb, (void **)&pQueue); if (VBOX_FAILURE(rc)) return rc; /* * Initialize the data fields. */ pQueue->pVMHC = pVM; pQueue->pVMGC = fGCEnabled ? pVM->pVMGC : 0; pQueue->cMilliesInterval = cMilliesInterval; //pQueue->pTimer = NULL; pQueue->cbItem = cbItem; pQueue->cItems = cItems; //pQueue->pPendingHC = NULL; //pQueue->pPendingGC = NULL; pQueue->iFreeHead = cItems; //pQueue->iFreeTail = 0; PPDMQUEUEITEMCORE pItem = (PPDMQUEUEITEMCORE)((char *)pQueue + RT_ALIGN_Z(RT_OFFSETOF(PDMQUEUE, aFreeItems[cItems + PDMQUEUE_FREE_SLACK]), 16)); for (unsigned i = 0; i < cItems; i++, pItem = (PPDMQUEUEITEMCORE)((char *)pItem + cbItem)) { pQueue->aFreeItems[i].pItemHC = pItem; if (fGCEnabled) pQueue->aFreeItems[i].pItemGC = MMHyperHC2GC(pVM, pItem); } /* * Create timer? */ if (cMilliesInterval) { int rc = TMR3TimerCreateInternal(pVM, TMCLOCK_REAL, pdmR3QueueTimer, pQueue, "Queue timer", &pQueue->pTimer); if (VBOX_SUCCESS(rc)) { rc = TMTimerSetMillies(pQueue->pTimer, cMilliesInterval); if (VBOX_FAILURE(rc)) { AssertMsgFailed(("TMTimerSetMillies failed rc=%Vrc\n", rc)); int rc2 = TMTimerDestroy(pQueue->pTimer); AssertRC(rc2); } } else AssertMsgFailed(("TMR3TimerCreateInternal failed rc=%Vrc\n", rc)); if (VBOX_FAILURE(rc)) { if (fGCEnabled) MMHyperFree(pVM, pQueue); else MMR3HeapFree(pQueue); return rc; } /* * Insert into the queue list for timer driven queues. */ pQueue->pNext = pVM->pdm.s.pQueuesTimer; pVM->pdm.s.pQueuesTimer = pQueue; } else { /* * Insert into the queue list for forced action driven queues. * This is a FIFO, so insert at the end. */ /** @todo we should add a priority priority to the queues so we don't have to rely on * the initialization order to deal with problems like #1605 (pgm/pcnet deadlock * caused by the critsect queue to be last in the chain). * Update, the critical sections are no longer using queues. */ if (!pVM->pdm.s.pQueuesForced) pVM->pdm.s.pQueuesForced = pQueue; else { PPDMQUEUE pPrev = pVM->pdm.s.pQueuesForced; while (pPrev->pNext) pPrev = pPrev->pNext; pPrev->pNext = pQueue; } } *ppQueue = pQueue; return VINF_SUCCESS; } /** * Create a queue with a device owner. * * @returns VBox status code. * @param pVM VM handle. * @param pDevIns Device instance. * @param cbItem Size a queue item. * @param cItems Number of items in the queue. * @param cMilliesInterval Number of milliseconds between polling the queue. * If 0 then the emulation thread will be notified whenever an item arrives. * @param pfnCallback The consumer function. * @param fGCEnabled Set if the queue must be usable from GC. * @param ppQueue Where to store the queue handle on success. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueCreateDevice(PVM pVM, PPDMDEVINS pDevIns, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval, PFNPDMQUEUEDEV pfnCallback, bool fGCEnabled, PPDMQUEUE *ppQueue) { LogFlow(("PDMR3QueueCreateDevice: pDevIns=%p cbItem=%d cItems=%d cMilliesInterval=%d pfnCallback=%p fGCEnabled=%RTbool\n", pDevIns, cbItem, cItems, cMilliesInterval, pfnCallback, fGCEnabled)); /* * Validate input. */ VM_ASSERT_EMT(pVM); if (!pfnCallback) { AssertMsgFailed(("No consumer callback!\n")); return VERR_INVALID_PARAMETER; } /* * Create the queue. */ PPDMQUEUE pQueue; int rc = pdmR3QueueCreate(pVM, cbItem, cItems, cMilliesInterval, fGCEnabled, &pQueue); if (VBOX_SUCCESS(rc)) { pQueue->enmType = PDMQUEUETYPE_DEV; pQueue->u.Dev.pDevIns = pDevIns; pQueue->u.Dev.pfnCallback = pfnCallback; *ppQueue = pQueue; Log(("PDM: Created device queue %p; cbItem=%d cItems=%d cMillies=%d pfnCallback=%p pDevIns=%p\n", cbItem, cItems, cMilliesInterval, pfnCallback, pDevIns)); } return rc; } /** * Create a queue with a driver owner. * * @returns VBox status code. * @param pVM VM handle. * @param pDrvIns Driver instance. * @param cbItem Size a queue item. * @param cItems Number of items in the queue. * @param cMilliesInterval Number of milliseconds between polling the queue. * If 0 then the emulation thread will be notified whenever an item arrives. * @param pfnCallback The consumer function. * @param ppQueue Where to store the queue handle on success. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueCreateDriver(PVM pVM, PPDMDRVINS pDrvIns, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval, PFNPDMQUEUEDRV pfnCallback, PPDMQUEUE *ppQueue) { LogFlow(("PDMR3QueueCreateDriver: pDrvIns=%p cbItem=%d cItems=%d cMilliesInterval=%d pfnCallback=%p\n", pDrvIns, cbItem, cItems, cMilliesInterval, pfnCallback)); /* * Validate input. */ VM_ASSERT_EMT(pVM); if (!pfnCallback) { AssertMsgFailed(("No consumer callback!\n")); return VERR_INVALID_PARAMETER; } /* * Create the queue. */ PPDMQUEUE pQueue; int rc = pdmR3QueueCreate(pVM, cbItem, cItems, cMilliesInterval, false, &pQueue); if (VBOX_SUCCESS(rc)) { pQueue->enmType = PDMQUEUETYPE_DRV; pQueue->u.Drv.pDrvIns = pDrvIns; pQueue->u.Drv.pfnCallback = pfnCallback; *ppQueue = pQueue; Log(("PDM: Created driver queue %p; cbItem=%d cItems=%d cMillies=%d pfnCallback=%p pDrvIns=%p\n", cbItem, cItems, cMilliesInterval, pfnCallback, pDrvIns)); } return rc; } /** * Create a queue with an internal owner. * * @returns VBox status code. * @param pVM VM handle. * @param cbItem Size a queue item. * @param cItems Number of items in the queue. * @param cMilliesInterval Number of milliseconds between polling the queue. * If 0 then the emulation thread will be notified whenever an item arrives. * @param pfnCallback The consumer function. * @param fGCEnabled Set if the queue must be usable from GC. * @param ppQueue Where to store the queue handle on success. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueCreateInternal(PVM pVM, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval, PFNPDMQUEUEINT pfnCallback, bool fGCEnabled, PPDMQUEUE *ppQueue) { LogFlow(("PDMR3QueueCreateInternal: cbItem=%d cItems=%d cMilliesInterval=%d pfnCallback=%p fGCEnabled=%RTbool\n", cbItem, cItems, cMilliesInterval, pfnCallback, fGCEnabled)); /* * Validate input. */ VM_ASSERT_EMT(pVM); if (!pfnCallback) { AssertMsgFailed(("No consumer callback!\n")); return VERR_INVALID_PARAMETER; } /* * Create the queue. */ PPDMQUEUE pQueue; int rc = pdmR3QueueCreate(pVM, cbItem, cItems, cMilliesInterval, fGCEnabled, &pQueue); if (VBOX_SUCCESS(rc)) { pQueue->enmType = PDMQUEUETYPE_INTERNAL; pQueue->u.Int.pfnCallback = pfnCallback; *ppQueue = pQueue; Log(("PDM: Created internal queue %p; cbItem=%d cItems=%d cMillies=%d pfnCallback=%p\n", cbItem, cItems, cMilliesInterval, pfnCallback)); } return rc; } /** * Create a queue with an external owner. * * @returns VBox status code. * @param pVM VM handle. * @param cbItem Size a queue item. * @param cItems Number of items in the queue. * @param cMilliesInterval Number of milliseconds between polling the queue. * If 0 then the emulation thread will be notified whenever an item arrives. * @param pfnCallback The consumer function. * @param pvUser The user argument to the consumer function. * @param ppQueue Where to store the queue handle on success. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueCreateExternal(PVM pVM, RTUINT cbItem, RTUINT cItems, uint32_t cMilliesInterval, PFNPDMQUEUEEXT pfnCallback, void *pvUser, PPDMQUEUE *ppQueue) { LogFlow(("PDMR3QueueCreateExternal: cbItem=%d cItems=%d cMilliesInterval=%d pfnCallback=%p\n", cbItem, cItems, cMilliesInterval, pfnCallback)); /* * Validate input. */ VM_ASSERT_EMT(pVM); if (!pfnCallback) { AssertMsgFailed(("No consumer callback!\n")); return VERR_INVALID_PARAMETER; } /* * Create the queue. */ PPDMQUEUE pQueue; int rc = pdmR3QueueCreate(pVM, cbItem, cItems, cMilliesInterval, false, &pQueue); if (VBOX_SUCCESS(rc)) { pQueue->enmType = PDMQUEUETYPE_EXTERNAL; pQueue->u.Ext.pvUser = pvUser; pQueue->u.Ext.pfnCallback = pfnCallback; *ppQueue = pQueue; Log(("PDM: Created external queue %p; cbItem=%d cItems=%d cMillies=%d pfnCallback=%p pvUser=%p\n", cbItem, cItems, cMilliesInterval, pfnCallback, pvUser)); } return rc; } /** * Destroy a queue. * * @returns VBox status code. * @param pQueue Queue to destroy. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueDestroy(PPDMQUEUE pQueue) { LogFlow(("PDMR3QueueDestroy: pQueue=%p\n", pQueue)); /* * Validate input. */ if (!pQueue) return VERR_INVALID_PARAMETER; Assert(pQueue && pQueue->pVMHC); PVM pVM = pQueue->pVMHC; VM_ASSERT_EMT(pVM); /* * Unlink it. */ if (pQueue->pTimer) { if (pVM->pdm.s.pQueuesTimer != pQueue) { PPDMQUEUE pCur = pVM->pdm.s.pQueuesTimer; while (pCur) { if (pCur->pNext == pQueue) { pCur->pNext = pQueue->pNext; break; } pCur = pCur->pNext; } AssertMsg(pCur, ("Didn't find the queue!\n")); } else pVM->pdm.s.pQueuesTimer = pQueue->pNext; } else { if (pVM->pdm.s.pQueuesForced != pQueue) { PPDMQUEUE pCur = pVM->pdm.s.pQueuesForced; while (pCur) { if (pCur->pNext == pQueue) { pCur->pNext = pQueue->pNext; break; } pCur = pCur->pNext; } AssertMsg(pCur, ("Didn't find the queue!\n")); } else pVM->pdm.s.pQueuesForced = pQueue->pNext; } pQueue->pNext = NULL; pQueue->pVMHC = NULL; /* * Destroy the timer and free it. */ if (pQueue->pTimer) { TMTimerDestroy(pQueue->pTimer); pQueue->pTimer = NULL; } if (pQueue->pVMGC) { pQueue->pVMGC = 0; MMHyperFree(pVM, pQueue); } else MMR3HeapFree(pQueue); return VINF_SUCCESS; } /** * Destroy a all queues owned by the specified device. * * @returns VBox status code. * @param pVM VM handle. * @param pDevIns Device instance. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueDestroyDevice(PVM pVM, PPDMDEVINS pDevIns) { LogFlow(("PDMR3QueueDestroyDevice: pDevIns=%p\n", pDevIns)); /* * Validate input. */ if (!pDevIns) return VERR_INVALID_PARAMETER; VM_ASSERT_EMT(pVM); /* * Unlink it. */ PPDMQUEUE pQueueNext = pVM->pdm.s.pQueuesTimer; PPDMQUEUE pQueue = pVM->pdm.s.pQueuesForced; do { while (pQueue) { if ( pQueue->enmType == PDMQUEUETYPE_DEV && pQueue->u.Dev.pDevIns == pDevIns) { PPDMQUEUE pQueueDestroy = pQueue; pQueue = pQueue->pNext; int rc = PDMR3QueueDestroy(pQueueDestroy); AssertRC(rc); } else pQueue = pQueue->pNext; } /* next queue list */ pQueue = pQueueNext; pQueueNext = NULL; } while (pQueue); return VINF_SUCCESS; } /** * Destroy a all queues owned by the specified driver. * * @returns VBox status code. * @param pVM VM handle. * @param pDrvIns Driver instance. * @thread Emulation thread only. */ PDMR3DECL(int) PDMR3QueueDestroyDriver(PVM pVM, PPDMDRVINS pDrvIns) { LogFlow(("PDMR3QueueDestroyDriver: pDrvIns=%p\n", pDrvIns)); /* * Validate input. */ if (!pDrvIns) return VERR_INVALID_PARAMETER; VM_ASSERT_EMT(pVM); /* * Unlink it. */ PPDMQUEUE pQueueNext = pVM->pdm.s.pQueuesTimer; PPDMQUEUE pQueue = pVM->pdm.s.pQueuesForced; do { while (pQueue) { if ( pQueue->enmType == PDMQUEUETYPE_DRV && pQueue->u.Drv.pDrvIns == pDrvIns) { PPDMQUEUE pQueueDestroy = pQueue; pQueue = pQueue->pNext; int rc = PDMR3QueueDestroy(pQueueDestroy); AssertRC(rc); } else pQueue = pQueue->pNext; } /* next queue list */ pQueue = pQueueNext; pQueueNext = NULL; } while (pQueue); return VINF_SUCCESS; } /** * Relocate the queues. * * @param pVM The VM handle. * @param offDelta The relocation delta. */ void pdmR3QueueRelocate(PVM pVM, RTGCINTPTR offDelta) { /* * Process the queues. */ PPDMQUEUE pQueueNext = pVM->pdm.s.pQueuesTimer; PPDMQUEUE pQueue = pVM->pdm.s.pQueuesForced; do { while (pQueue) { if (pQueue->pVMGC) { pQueue->pVMGC = pVM->pVMGC; /* Pending GC items. */ if (pQueue->pPendingGC) { pQueue->pPendingGC += offDelta; PPDMQUEUEITEMCORE pCur = (PPDMQUEUEITEMCORE)MMHyperGC2HC(pVM, pQueue->pPendingGC); while (pCur->pNextGC) { pCur->pNextGC += offDelta; pCur = (PPDMQUEUEITEMCORE)MMHyperGC2HC(pVM, pCur->pNextGC); } } /* The free items. */ uint32_t i = pQueue->iFreeTail; while (i != pQueue->iFreeHead) { pQueue->aFreeItems[i].pItemGC = MMHyperHC2GC(pVM, pQueue->aFreeItems[i].pItemHC); i = (i + 1) % (pQueue->cItems + PDMQUEUE_FREE_SLACK); } } /* next queue */ pQueue = pQueue->pNext; } /* next queue list */ pQueue = pQueueNext; pQueueNext = NULL; } while (pQueue); } /** * Flush pending queues. * This is a forced action callback. * * @param pVM VM handle. * @thread Emulation thread only. */ PDMR3DECL(void) PDMR3QueueFlushAll(PVM pVM) { VM_ASSERT_EMT(pVM); LogFlow(("PDMR3QueuesFlush:\n")); VM_FF_CLEAR(pVM, VM_FF_PDM_QUEUES); for (PPDMQUEUE pCur = pVM->pdm.s.pQueuesForced; pCur; pCur = pCur->pNext) { if ( pCur->pPendingHC || pCur->pPendingGC) { if ( pdmR3QueueFlush(pCur) && pCur->pPendingHC) /* new items arrived while flushing. */ pdmR3QueueFlush(pCur); } } } /** * Process pending items in one queue. * * @returns Success indicator. * If false the item the consumer said "enough!". * @param pQueue The queue. */ static bool pdmR3QueueFlush(PPDMQUEUE pQueue) { PPDMQUEUEITEMCORE pItems = (PPDMQUEUEITEMCORE)ASMAtomicXchgPtr((void * volatile *)&pQueue->pPendingHC, NULL); AssertMsg(pItems || pQueue->pPendingGC, ("ERROR: can't be NULL now!\n")); /* * Reverse the list (it's inserted in LIFO order to avoid semaphores, remember). */ PPDMQUEUEITEMCORE pCur = pItems; pItems = NULL; while (pCur) { PPDMQUEUEITEMCORE pInsert = pCur; pCur = pCur->pNextHC; pInsert->pNextHC = pItems; pItems = pInsert; } /* * Do the same for any pending GC items. */ while (pQueue->pPendingGC) { PPDMQUEUEITEMCORE pInsert = (PPDMQUEUEITEMCORE)MMHyperGC2HC(pQueue->pVMHC, pQueue->pPendingGC); pQueue->pPendingGC = pInsert->pNextGC; pInsert->pNextGC = 0; pInsert->pNextHC = pItems; pItems = pInsert; } /* * Feed the items to the consumer function. */ Log2(("pdmR3QueueFlush: pQueue=%p enmType=%d pItems=%p\n", pQueue, pQueue->enmType, pItems)); switch (pQueue->enmType) { case PDMQUEUETYPE_DEV: while (pItems) { pCur = pItems; pItems = pItems->pNextHC; if (!pQueue->u.Dev.pfnCallback(pQueue->u.Dev.pDevIns, pCur)) break; pdmR3QueueFree(pQueue, pCur); } break; case PDMQUEUETYPE_DRV: while (pItems) { pCur = pItems; pItems = pItems->pNextHC; if (!pQueue->u.Drv.pfnCallback(pQueue->u.Drv.pDrvIns, pCur)) break; pdmR3QueueFree(pQueue, pCur); } break; case PDMQUEUETYPE_INTERNAL: while (pItems) { pCur = pItems; pItems = pItems->pNextHC; if (!pQueue->u.Int.pfnCallback(pQueue->pVMHC, pCur)) break; pdmR3QueueFree(pQueue, pCur); } break; case PDMQUEUETYPE_EXTERNAL: while (pItems) { pCur = pItems; pItems = pItems->pNextHC; if (!pQueue->u.Ext.pfnCallback(pQueue->u.Ext.pvUser, pCur)) break; pdmR3QueueFree(pQueue, pCur); } break; default: AssertMsgFailed(("Invalid queue type %d\n", pQueue->enmType)); break; } /* * Success? */ if (pItems) { /* * Shit, no! * 1. Insert pCur. * 2. Reverse the list. * 3. Insert the LIFO at the tail of the pending list. */ pCur->pNextHC = pItems; pItems = pCur; //pCur = pItems; pItems = NULL; while (pCur) { PPDMQUEUEITEMCORE pInsert = pCur; pCur = pCur->pNextHC; pInsert->pNextHC = pItems; pItems = pInsert; } if ( pQueue->pPendingHC || !ASMAtomicCmpXchgPtr((void * volatile *)&pQueue->pPendingHC, pItems, NULL)) { pCur = pQueue->pPendingHC; while (pCur->pNextHC) pCur = pCur->pNextHC; pCur->pNextHC = pItems; } return false; } return true; } /** * This is a worker function used by PDMQueueFlush to perform the * flush in ring-3. * * The queue which should be flushed is pointed to by either pQueueFlushGC, * pQueueFlushHC, or pQueueue. This function will flush that queue and * recalc the queue FF. * * @param pVM The VM handle. * @param pQueue The queue to flush. Only used in Ring-3. */ PDMR3DECL(void) PDMR3QueueFlushWorker(PVM pVM, PPDMQUEUE pQueue) { Assert(pVM->pdm.s.pQueueFlushHC || pVM->pdm.s.pQueueFlushGC || pQueue); VM_ASSERT_EMT(pVM); /* * Flush the queue. */ if (!pQueue) pQueue = pVM->pdm.s.pQueueFlushHC; if (!pQueue) pQueue = (PPDMQUEUE)MMHyperGC2HC(pVM, pVM->pdm.s.pQueueFlushGC); pVM->pdm.s.pQueueFlushHC = NULL; pVM->pdm.s.pQueueFlushGC = 0; if ( !pQueue || pdmR3QueueFlush(pQueue)) { /* * Recalc the FF. */ VM_FF_CLEAR(pVM, VM_FF_PDM_QUEUES); for (pQueue = pVM->pdm.s.pQueuesForced; pQueue; pQueue = pQueue->pNext) if ( pQueue->pPendingGC || pQueue->pPendingHC) { VM_FF_SET(pVM, VM_FF_PDM_QUEUES); break; } } } /** * Free an item. * * @param pQueue The queue. * @param pItem The item. */ static inline void pdmR3QueueFree(PPDMQUEUE pQueue, PPDMQUEUEITEMCORE pItem) { VM_ASSERT_EMT(pQueue->pVMHC); int i = pQueue->iFreeHead; int iNext = (i + 1) % (pQueue->cItems + PDMQUEUE_FREE_SLACK); pQueue->aFreeItems[i].pItemHC = pItem; if (pQueue->pVMGC) pQueue->aFreeItems[i].pItemGC = MMHyperHC2GC(pQueue->pVMHC, pItem); if (!ASMAtomicCmpXchgU32(&pQueue->iFreeHead, iNext, i)) AssertMsgFailed(("huh? i=%d iNext=%d iFreeHead=%d iFreeTail=%d\n", i, iNext, pQueue->iFreeHead, pQueue->iFreeTail)); } /** * Timer handler for PDM queues. * This is called by for a single queue. * * @param pVM VM handle. * @param pTimer Pointer to timer. * @param pvUser Pointer to the queue. */ static DECLCALLBACK(void) pdmR3QueueTimer(PVM pVM, PTMTIMER pTimer, void *pvUser) { PPDMQUEUE pQueue = (PPDMQUEUE)pvUser; Assert(pTimer == pQueue->pTimer); NOREF(pTimer); if ( pQueue->pPendingHC || pQueue->pPendingGC) pdmR3QueueFlush(pQueue); int rc = TMTimerSetMillies(pQueue->pTimer, pQueue->cMilliesInterval); AssertRC(rc); }