/* $Id: req.cpp $ */
/** @file
 * IPRT - Request packets
 */

/*
 * Copyright (C) 2006-2011 Oracle Corporation
 *
 * 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 (GPL) 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.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <iprt/req.h>
#include "internal/iprt.h"

#include <iprt/assert.h>
#include <iprt/asm.h>
#include <iprt/string.h>
#include <iprt/time.h>
#include <iprt/semaphore.h>
#include <iprt/thread.h>
#include <iprt/log.h>
#include <iprt/mem.h>

#include "internal/req.h"
#include "internal/magics.h"


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/


/**
 * Allocate a new request from the heap.
 *
 * @returns IPRT status code.
 * @param   enmType         The reques type.
 * @param   fPoolOrQueue    The owner type.
 * @param   pvOwner         The owner.
 * @param   phReq           Where to return the request handle.
 */
DECLHIDDEN(int) rtReqAlloc(RTREQTYPE enmType, bool fPoolOrQueue, void *pvOwner, PRTREQ *phReq)
{
    PRTREQ pReq = (PRTREQ)RTMemAllocZ(sizeof(*pReq));
    if (RT_UNLIKELY(!pReq))
        return VERR_NO_MEMORY;

    /*
     * Create the semaphore used for waiting.
     */
    int rc = RTSemEventCreate(&pReq->EventSem);
    AssertRCReturnStmt(rc, RTMemFree(pReq), rc);

    /*
     * Initialize the packet and return it.
     */
    pReq->u32Magic          = RTREQ_MAGIC;
    pReq->fEventSemClear    = true;
    pReq->fSignalPushBack   = true;
    pReq->fPoolOrQueue      = fPoolOrQueue;
    pReq->iStatusX          = VERR_RT_REQUEST_STATUS_STILL_PENDING;
    pReq->enmState          = RTREQSTATE_ALLOCATED;
    pReq->pNext             = NULL;
    pReq->uOwner.pv         = pvOwner;
    pReq->fFlags            = RTREQFLAGS_IPRT_STATUS;
    pReq->enmType           = enmType;
    pReq->cRefs             = 1;

    *phReq = pReq;
    return VINF_SUCCESS;
}


/**
 * Re-initializes a request when it's being recycled.
 *
 * @returns IRPT status code, the request is freed on failure.
 * @param   pReq                The request.
 * @param   enmType             The request type.
 */
DECLHIDDEN(int) rtReqReInit(PRTREQINT pReq, RTREQTYPE enmType)
{
    Assert(pReq->u32Magic == RTREQ_MAGIC);
    Assert(pReq->enmType  == RTREQTYPE_INVALID);
    Assert(pReq->enmState == RTREQSTATE_FREE);
    Assert(pReq->cRefs    == 0);

    /*
     * Make sure the event sem is not signaled.
     */
    if (!pReq->fEventSemClear)
    {
        int rc = RTSemEventWait(pReq->EventSem, 0);
        if (rc != VINF_SUCCESS && rc != VERR_TIMEOUT)
        {
            /*
             * This shall not happen, but if it does we'll just destroy
             * the semaphore and create a new one.
             */
            AssertMsgFailed(("rc=%Rrc from RTSemEventWait(%#x).\n", rc, pReq->EventSem));
            RTSemEventDestroy(pReq->EventSem);
            rc = RTSemEventCreate(&pReq->EventSem);
            if (RT_FAILURE(rc))
            {
                AssertRC(rc);
                pReq->EventSem = NIL_RTSEMEVENT;
                rtReqFreeIt(pReq);
                return rc;
            }
        }
        pReq->fEventSemClear = true;
    }
    else
        Assert(RTSemEventWait(pReq->EventSem, 0) == VERR_TIMEOUT);

    /*
     * Initialize the packet and return it.
     */
    ASMAtomicWriteNullPtr(&pReq->pNext);
    pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
    pReq->enmState = RTREQSTATE_ALLOCATED;
    pReq->fFlags   = RTREQFLAGS_IPRT_STATUS;
    pReq->enmType  = enmType;
    pReq->cRefs    = 1;
    return VINF_SUCCESS;
}


RTDECL(uint32_t) RTReqRetain(PRTREQ hReq)
{
    PRTREQINT pReq = hReq;
    AssertPtrReturn(pReq, UINT32_MAX);
    AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);

    return ASMAtomicIncU32(&pReq->cRefs);
}
RT_EXPORT_SYMBOL(RTReqRetain);


/**
 * Frees a request.
 *
 * @param   pReq                The request.
 */
DECLHIDDEN(void) rtReqFreeIt(PRTREQINT pReq)
{
    Assert(pReq->u32Magic == RTREQ_MAGIC);
    Assert(pReq->cRefs == 0);

    pReq->u32Magic     = RTREQ_MAGIC_DEAD;
    RTSemEventDestroy(pReq->EventSem);
    pReq->EventSem     = NIL_RTSEMEVENT;
    RTSemEventMultiDestroy(pReq->hPushBackEvt);
    pReq->hPushBackEvt = NIL_RTSEMEVENTMULTI;
    RTMemFree(pReq);
}


RTDECL(uint32_t) RTReqRelease(PRTREQ hReq)
{
    /*
     * Ignore NULL and validate the request.
     */
    if (!hReq)
        return 0;
    PRTREQINT pReq = hReq;
    AssertPtrReturn(pReq, UINT32_MAX);
    AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);

    /*
     * Drop a reference, recycle the request when we reach 0.
     */
    uint32_t cRefs = ASMAtomicDecU32(&pReq->cRefs);
    if (cRefs == 0)
    {
        /*
         * Check packet state.
         */
        switch (pReq->enmState)
        {
            case RTREQSTATE_ALLOCATED:
            case RTREQSTATE_COMPLETED:
                break;
            default:
                AssertMsgFailed(("Invalid state %d!\n", pReq->enmState));
                return 0;
        }

        /*
         * Make it a free packet and put it into one of the free packet lists.
         */
        pReq->enmState = RTREQSTATE_FREE;
        pReq->iStatusX = VERR_RT_REQUEST_STATUS_FREED;
        pReq->enmType  = RTREQTYPE_INVALID;

        bool fRecycled;
        if (pReq->fPoolOrQueue)
            fRecycled = rtReqPoolRecycle(pReq->uOwner.hPool, pReq);
        else
            fRecycled = rtReqQueueRecycle(pReq->uOwner.hQueue, pReq);
        if (!fRecycled)
            rtReqFreeIt(pReq);
    }

    return cRefs;
}
RT_EXPORT_SYMBOL(RTReqRelease);


RTDECL(int) RTReqSubmit(PRTREQ hReq, RTMSINTERVAL cMillies)
{
    LogFlow(("RTReqSubmit: hReq=%p cMillies=%d\n", hReq, cMillies));

    /*
     * Verify the supplied package.
     */
    PRTREQINT pReq = hReq;
    AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
    AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
    AssertMsgReturn(pReq->enmState == RTREQSTATE_ALLOCATED, ("%d\n", pReq->enmState), VERR_RT_REQUEST_STATE);
    AssertMsgReturn(pReq->uOwner.hQueue && !pReq->pNext && pReq->EventSem != NIL_RTSEMEVENT,
                    ("Invalid request package! Anyone cooking their own packages???\n"),
                    VERR_RT_REQUEST_INVALID_PACKAGE);
    AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
                    ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
                     pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
                    VERR_RT_REQUEST_INVALID_TYPE);

    /*
     * Insert it.  Donate the caller's reference if RTREQFLAGS_NO_WAIT is set,
     * otherwise retain another reference for the queue.
     */
    pReq->uSubmitNanoTs = RTTimeNanoTS();
    pReq->enmState      = RTREQSTATE_QUEUED;
    unsigned fFlags = ((RTREQ volatile *)pReq)->fFlags;                    /* volatile paranoia */
    if (!(fFlags & RTREQFLAGS_NO_WAIT))
        RTReqRetain(pReq);

    if (!pReq->fPoolOrQueue)
        rtReqQueueSubmit(pReq->uOwner.hQueue, pReq);
    else
        rtReqPoolSubmit(pReq->uOwner.hPool, pReq);

    /*
     * Wait and return.
     */
    int rc = VINF_SUCCESS;
    if (!(fFlags & RTREQFLAGS_NO_WAIT))
        rc = RTReqWait(pReq, cMillies);

    LogFlow(("RTReqSubmit: returns %Rrc\n", rc));
    return rc;
}
RT_EXPORT_SYMBOL(RTReqSubmit);


RTDECL(int) RTReqWait(PRTREQ hReq, RTMSINTERVAL cMillies)
{
    LogFlow(("RTReqWait: hReq=%p cMillies=%d\n", hReq, cMillies));

    /*
     * Verify the supplied package.
     */
    PRTREQINT pReq = hReq;
    AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
    AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
    AssertMsgReturn(   pReq->enmState != RTREQSTATE_QUEUED
                    || pReq->enmState != RTREQSTATE_PROCESSING
                    || pReq->enmState != RTREQSTATE_COMPLETED,
                    ("Invalid state %d\n", pReq->enmState),
                    VERR_RT_REQUEST_STATE);
    AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
                    ("Invalid request package! Anyone cooking their own packages???\n"),
                    VERR_RT_REQUEST_INVALID_PACKAGE);
    AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
                    ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
                     pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
                    VERR_RT_REQUEST_INVALID_TYPE);

    /*
     * Wait on the package.
     */
    int rc;
    if (cMillies != RT_INDEFINITE_WAIT)
        rc = RTSemEventWait(pReq->EventSem, cMillies);
    else
    {
        do
        {
            rc = RTSemEventWait(pReq->EventSem, RT_INDEFINITE_WAIT);
            Assert(rc != VERR_TIMEOUT);
        } while (pReq->enmState != RTREQSTATE_COMPLETED);
    }
    if (rc == VINF_SUCCESS)
        ASMAtomicXchgSize(&pReq->fEventSemClear, true);
    if (pReq->enmState == RTREQSTATE_COMPLETED)
        rc = VINF_SUCCESS;
    LogFlow(("RTReqWait: returns %Rrc\n", rc));
    Assert(rc != VERR_INTERRUPTED);
    Assert(pReq->cRefs >= 1);
    return rc;
}
RT_EXPORT_SYMBOL(RTReqWait);


RTDECL(int) RTReqGetStatus(PRTREQ hReq)
{
    PRTREQINT pReq = hReq;
    AssertPtrReturn(pReq, VERR_INVALID_POINTER);
    AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_POINTER);
    return pReq->iStatusX;
}
RT_EXPORT_SYMBOL(RTReqGetStatus);



/**
 * Process one request.
 *
 * @returns IPRT status code.
 *
 * @param   pReq        Request packet to process.
 */
DECLHIDDEN(int) rtReqProcessOne(PRTREQINT pReq)
{
    LogFlow(("rtReqProcessOne: pReq=%p type=%d fFlags=%#x\n", pReq, pReq->enmType, pReq->fFlags));

    /*
     * Process the request.
     */
    Assert(pReq->enmState == RTREQSTATE_QUEUED);
    pReq->enmState = RTREQSTATE_PROCESSING;
    int     rcRet = VINF_SUCCESS;           /* the return code of this function. */
    int     rcReq = VERR_NOT_IMPLEMENTED;   /* the request status. */
    switch (pReq->enmType)
    {
        /*
         * A packed down call frame.
         */
        case RTREQTYPE_INTERNAL:
        {
            uintptr_t *pauArgs = &pReq->u.Internal.aArgs[0];
            union
            {
                PFNRT pfn;
                DECLCALLBACKMEMBER(int, pfn00)(void);
                DECLCALLBACKMEMBER(int, pfn01)(uintptr_t);
                DECLCALLBACKMEMBER(int, pfn02)(uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn03)(uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn04)(uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn05)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn06)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn07)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn08)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn09)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn10)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn11)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
                DECLCALLBACKMEMBER(int, pfn12)(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t);
            } u;
            u.pfn = pReq->u.Internal.pfn;
#ifndef RT_ARCH_X86
            switch (pReq->u.Internal.cArgs)
            {
                case 0:  rcRet = u.pfn00(); break;
                case 1:  rcRet = u.pfn01(pauArgs[0]); break;
                case 2:  rcRet = u.pfn02(pauArgs[0], pauArgs[1]); break;
                case 3:  rcRet = u.pfn03(pauArgs[0], pauArgs[1], pauArgs[2]); break;
                case 4:  rcRet = u.pfn04(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3]); break;
                case 5:  rcRet = u.pfn05(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4]); break;
                case 6:  rcRet = u.pfn06(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5]); break;
                case 7:  rcRet = u.pfn07(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6]); break;
                case 8:  rcRet = u.pfn08(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7]); break;
                case 9:  rcRet = u.pfn09(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8]); break;
                case 10: rcRet = u.pfn10(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9]); break;
                case 11: rcRet = u.pfn11(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10]); break;
                case 12: rcRet = u.pfn12(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10], pauArgs[11]); break;
                default:
                    AssertReleaseMsgFailed(("cArgs=%d\n", pReq->u.Internal.cArgs));
                    rcRet = rcReq = VERR_INTERNAL_ERROR;
                    break;
            }
#else /* RT_ARCH_X86 */
            size_t cbArgs = pReq->u.Internal.cArgs * sizeof(uintptr_t);
# ifdef __GNUC__
            __asm__ __volatile__("movl  %%esp, %%edx\n\t"
                                 "subl  %2, %%esp\n\t"
                                 "andl  $0xfffffff0, %%esp\n\t"
                                 "shrl  $2, %2\n\t"
                                 "movl  %%esp, %%edi\n\t"
                                 "rep movsl\n\t"
                                 "movl  %%edx, %%edi\n\t"
                                 "call  *%%eax\n\t"
                                 "mov   %%edi, %%esp\n\t"
                                 : "=a" (rcRet),
                                   "=S" (pauArgs),
                                   "=c" (cbArgs)
                                 : "0" (u.pfn),
                                   "1" (pauArgs),
                                   "2" (cbArgs)
                                 : "edi", "edx");
# else
            __asm
            {
                xor     edx, edx        /* just mess it up. */
                mov     eax, u.pfn
                mov     ecx, cbArgs
                shr     ecx, 2
                mov     esi, pauArgs
                mov     ebx, esp
                sub     esp, cbArgs
                and     esp, 0xfffffff0
                mov     edi, esp
                rep movsd
                call    eax
                mov     esp, ebx
                mov     rcRet, eax
            }
# endif
#endif /* RT_ARCH_X86 */
            if ((pReq->fFlags & (RTREQFLAGS_RETURN_MASK)) == RTREQFLAGS_VOID)
                rcRet = VINF_SUCCESS;
            rcReq = rcRet;
            break;
        }

        default:
            AssertMsgFailed(("pReq->enmType=%d\n", pReq->enmType));
            rcReq = VERR_NOT_IMPLEMENTED;
            break;
    }

    /*
     * Complete the request and then release our request handle reference.
     */
    pReq->iStatusX = rcReq;
    pReq->enmState = RTREQSTATE_COMPLETED;
    if (pReq->fFlags & RTREQFLAGS_NO_WAIT)
        LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc (no wait)\n",
                 pReq, rcReq, rcRet));
    else
    {
        /* Notify the waiting thread. */
        LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc - notifying waiting thread\n",
                 pReq, rcReq, rcRet));
        ASMAtomicXchgSize(&pReq->fEventSemClear, false);
        int rc2 = RTSemEventSignal(pReq->EventSem);
        if (rc2 != VINF_SUCCESS)
        {
            AssertRC(rc2);
            rcRet = rc2;
        }
    }
    RTReqRelease(pReq);
    return rcRet;
}

