/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*-
 * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: *
 * Editor Settings: expandtabs and use 4 spaces for indentation */

/*
 * Copyright (c) 2014 Spectra Logic Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    substantially similar to the "NO WARRANTY" disclaimer below
 *    ("Disclaimer") and any redistribution must be conditioned upon
 *    including a substantially similar Disclaimer requirement for further
 *    binary redistribution.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 */

/*
 * Module Name:
 *
 *        creditor.c
 *
 * Abstract:
 *
 *        Likewise IO (LWIO) - SRV
 *
 *        Elements
 *
 *        Credit Management
 *
 * Authors: Justin Gibbs (justing@spectralogic.com)
 *
 */

#include "includes.h"

// A contiguous sequence of bits in the bitmap without wraps.
typedef struct _SRV_BITMAP_SEGMENT
{
    LONG lFirstBit;
    LONG lLastBit;
} SRV_BITMAP_SEGMENT;

// A logically contiguous sequence of bits in the sequence number window.
typedef struct _SRV_WINDOW_REGION
{
    SRV_BITMAP_SEGMENT Segment[2];
    LONG lNumSegments;
} SRV_WINDOW_REGION, *PSRV_WINDOW_REGION;

static
VOID
SrvCreditorGrantNewCredits(
    PSRV_CREDITOR pCreditor,
    USHORT        usCreditsRequested,
    PUSHORT       pusCreditsGranted
    );

static
NTSTATUS
SrvCreditorAcquireGlobalCredits(
    USHORT  usLocalCredit,
    USHORT  usCreditsRequested,
    PUSHORT pusCreditsGranted
    );

static
VOID
SrvCreditorReturnGlobalCredits(
    USHORT usCredits
    );

static
LONG
SrvCreditorWindowSize(
    IN PSRV_CREDITOR pCreditor
    )
{
    LONG lWindowSize = pCreditor->lWindowEnd - pCreditor->lWindowStart;

    if (pCreditor->lWindowStart > pCreditor->lWindowEnd)
    {
        lWindowSize += pCreditor->lWindowBitmapSize;
    }

    return lWindowSize;
}

NTSTATUS
SrvCreditorCreate(
    PSRV_CREDITOR* ppCreditor
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PSRV_CREDITOR pCreditor = NULL;
    USHORT usCreditsGranted = 0;

    ntStatus = SrvAllocateMemory(sizeof(SRV_CREDITOR), OUT_PPVOID(&pCreditor));
    BAIL_ON_NT_STATUS(ntStatus);

    pthread_mutex_init(&pCreditor->mutex, NULL);
    pCreditor->pMutex = &pCreditor->mutex;

    // Initialize bit map with sequence number 0 available to the client.
    ntStatus = SrvCreditorAcquireGlobalCredits(0, 1, &usCreditsGranted);
    BAIL_ON_NT_STATUS(ntStatus);

    pCreditor->ullWindowStartSeq = 0;
    pCreditor->lWindowStart = 0;
    pCreditor->lWindowEnd = 1;
    pCreditor->lWindowBitmapSize = gSrvElements.config.ulClientCreditLimit + 1;
    pCreditor->pWindowBitmap = bit_alloc(pCreditor->lWindowBitmapSize);
    if (pCreditor->pWindowBitmap == NULL)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        BAIL_ON_NT_STATUS(ntStatus);
    }
    bit_set(pCreditor->pWindowBitmap, 0);

cleanup:

    *ppCreditor = pCreditor;

    return ntStatus;

error:

    SrvCreditorFree(&pCreditor);

    goto cleanup;
}

// Advance the start position in the window bitmap to the bit representing
// the first unused sequence number.
static
LONG
SrvCreditorCloseWindow(
    IN OUT PSRV_CREDITOR pCreditor
    )
{
    LONG lFoundBit = -1;
    LONG lCreditsFreed = 0;
    ULONG64 ullLastWindowStartSeq = pCreditor->ullWindowStartSeq;

    if (pCreditor->lWindowStart > pCreditor->lWindowEnd)
    {
        bit_ffs_at(
            pCreditor->pWindowBitmap,
            pCreditor->lWindowStart,
            pCreditor->lWindowBitmapSize,
            &lFoundBit);
        if (lFoundBit == -1)
        {
            lCreditsFreed =
                pCreditor->lWindowBitmapSize - pCreditor->lWindowStart;
            pCreditor->lWindowStart = 0;
        }
    }

    if (lFoundBit == -1)
    {
        bit_ffs_at(
            pCreditor->pWindowBitmap,
            pCreditor->lWindowStart,
            pCreditor->lWindowEnd,
            &lFoundBit);
    }

    if (lFoundBit == -1)
    {
        lFoundBit = pCreditor->lWindowEnd;
    }

    lCreditsFreed += lFoundBit - pCreditor->lWindowStart;
    pCreditor->ullWindowStartSeq += lCreditsFreed;
    LWIO_ASSERT(pCreditor->ullWindowStartSeq > ullLastWindowStartSeq);
    pCreditor->lWindowStart = lFoundBit;

    pCreditor->usCreditsFree += lCreditsFreed;

    return lCreditsFreed;
}

// Grow the sequence number window by the given credit amount.
static
VOID
SrvCreditorOpenWindow(
    IN OUT PSRV_CREDITOR pCreditor,
    IN USHORT usNewCredit
    )
{
    LONG lWindowSize = SrvCreditorWindowSize(pCreditor);
    LONG lNewWindowEnd = pCreditor->lWindowEnd + usNewCredit;

    LWIO_ASSERT((lWindowSize + usNewCredit) < pCreditor->lWindowBitmapSize);

    if (lNewWindowEnd >= pCreditor->lWindowBitmapSize)
    {
        bit_nset(
            pCreditor->pWindowBitmap,
            pCreditor->lWindowEnd,
            pCreditor->lWindowBitmapSize - 1);
        lNewWindowEnd -= pCreditor->lWindowBitmapSize;
        pCreditor->lWindowEnd = 0;
    }

    if (pCreditor->lWindowEnd != lNewWindowEnd)
    {
        bit_nset(
            pCreditor->pWindowBitmap,
            pCreditor->lWindowEnd,
            lNewWindowEnd - 1);
        pCreditor->lWindowEnd = lNewWindowEnd;
    }
}

static
NTSTATUS
SrvCreditorSequenceToWindowRegion(
    IN PSRV_CREDITOR pCreditor,
    IN ULONG64 ullFirstSeq,
    IN USHORT usSequenceLen,
    IN OUT PSRV_WINDOW_REGION pWindowRegion
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG64 ullLastSeq = ullFirstSeq + usSequenceLen - 1;
    LONG lWindowSize = SrvCreditorWindowSize(pCreditor);

    // Verify requested sequence space is valid and within the current window.
    if (ullFirstSeq == UINT64_MAX ||
        ullLastSeq < ullFirstSeq ||
        ullFirstSeq < pCreditor->ullWindowStartSeq ||
        ullLastSeq >= (pCreditor->ullWindowStartSeq + lWindowSize))
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        BAIL_ON_NT_STATUS(ntStatus);
    }

    pWindowRegion->lNumSegments = 1;
    pWindowRegion->Segment[0].lFirstBit = 
        ullFirstSeq - pCreditor->ullWindowStartSeq + pCreditor->lWindowStart;
    if (pWindowRegion->Segment[0].lFirstBit >= pCreditor->lWindowBitmapSize)
    {
        pWindowRegion->Segment[0].lFirstBit -= pCreditor->lWindowBitmapSize;
    }
    
    pWindowRegion->Segment[0].lLastBit = 
        pWindowRegion->Segment[0].lFirstBit + usSequenceLen - 1;
    if (pWindowRegion->Segment[0].lLastBit >= pCreditor->lWindowBitmapSize)
    {
        pWindowRegion->lNumSegments++;
        pWindowRegion->Segment[1].lFirstBit = 0;
        pWindowRegion->Segment[1].lLastBit = 
            pWindowRegion->Segment[0].lLastBit - pCreditor->lWindowBitmapSize;
        pWindowRegion->Segment[0].lLastBit = pCreditor->lWindowBitmapSize - 1;
        LWIO_ASSERT(
            ((pWindowRegion->Segment[0].lLastBit -
              pWindowRegion->Segment[0].lFirstBit + 1) +
             (pWindowRegion->Segment[1].lLastBit -
              pWindowRegion->Segment[1].lFirstBit + 1)) == usSequenceLen);
    }
    else
    {
        LWIO_ASSERT(
            (pWindowRegion->Segment[0].lLastBit -
             pWindowRegion->Segment[0].lFirstBit + 1) == usSequenceLen);
    }

error:

    return ntStatus;
}

NTSTATUS
SrvCreditorUpdateCreditWindow(
    IN OUT PSRV_CREDITOR pCreditor,
    IN ULONG64 ullFirstSeq,
    IN USHORT usCreditCharge,
    IN USHORT usCreditsRequested,
    OUT PUSHORT pusCreditsGranted
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bInLock  = FALSE;
    SRV_WINDOW_REGION WindowRegion;
    LONG lWindowSize;
    LONG lFoundBit = -1;
    LONG i;
    USHORT usCreditsFreed = 0;
    USHORT usCreditsGranted = 0;
    
    // All commands consume at least one credit.
    LWIO_ASSERT(usCreditCharge >= 1);

    LWIO_LOCK_MUTEX(bInLock, &pCreditor->mutex);

    ntStatus = SrvCreditorSequenceToWindowRegion(
                    pCreditor,
                    ullFirstSeq,
                    usCreditCharge,
                    &WindowRegion);
    BAIL_ON_NT_STATUS(ntStatus);

    // Verify sequence numbers are available to the client and consume them.
    for (i = 0; i < WindowRegion.lNumSegments; i++)
    {
        bit_ffc_at(
            pCreditor->pWindowBitmap,
            WindowRegion.Segment[i].lFirstBit,
            WindowRegion.Segment[i].lLastBit + 1,
            &lFoundBit);
        if (lFoundBit != -1)
        {
            ntStatus = STATUS_INVALID_PARAMETER;
            BAIL_ON_NT_STATUS(ntStatus);
        }
        bit_nclear(
            pCreditor->pWindowBitmap,
            WindowRegion.Segment[i].lFirstBit,
            WindowRegion.Segment[i].lLastBit);
    }

    if (WindowRegion.Segment[0].lFirstBit == pCreditor->lWindowStart)
    {
        usCreditsFreed = SrvCreditorCloseWindow(pCreditor);
        LWIO_ASSERT(usCreditsFreed >= usCreditCharge);
    }

    // Don't allow the credit window to shrink below the floor value.
    lWindowSize = SrvCreditorWindowSize(pCreditor);
    if (lWindowSize < gSrvElements.config.ulClientCreditFloor)
    {
        usCreditsRequested = LW_MAX(
                                usCreditsRequested,
                                gSrvElements.config.ulClientCreditFloor -
                                lWindowSize);
    }

    SrvCreditorGrantNewCredits(
        pCreditor,
        usCreditsRequested,
        &usCreditsGranted);

    if (usCreditsGranted)
    {
        SrvCreditorOpenWindow(pCreditor, usCreditsGranted);
    }

error:

    LWIO_UNLOCK_MUTEX(bInLock, &pCreditor->mutex);

    *pusCreditsGranted = usCreditsGranted;

    return ntStatus;
}

static
VOID
SrvCreditorGrantNewCredits(
    PSRV_CREDITOR pCreditor,
    USHORT        usCreditsRequested,
    PUSHORT       pusCreditsGranted
    )
{
    LONG   lWindowSize               = SrvCreditorWindowSize(pCreditor);
    USHORT usLocalCredit             = lWindowSize + pCreditor->usCreditsFree;
    ULONG  ulCreditShortageSerialNum = 0;
    USHORT usCreditsFreed            = 0;
    USHORT usGlobalCreditsGranted    = 0;
    USHORT usCreditsGranted          = 0;
    USHORT usCreditAverage           = 0;

    ulCreditShortageSerialNum = gSrvElements.ulGlobalCreditShortageSerialNum;

    // For architectures where reading/writing a USHORT is not atomic...
    // Safe guard lockless read of potentially changing usClientCreditAverage.
    // Reading a too large value is guarded by ulClientCreditLimit.  The lower
    // bound is protected by ulClientCreditFloor. This limits the impact of
    // a worst case misread to incorrectly giving back all of the credit used
    // by the current command when it is not necessary,  but we will never
    // fully close the window.
    usCreditAverage = gSrvElements.usClientCreditAverage;
    usCreditAverage = LW_MIN(
                            usCreditAverage,
                            gSrvElements.config.ulClientCreditLimit);
    usCreditAverage = LW_MAX(
                            usCreditAverage,
                            gSrvElements.config.ulClientCreditFloor);

    if (pCreditor->ulCreditShortageSerialNum != ulCreditShortageSerialNum)
    {
        // Must shrink local credit to the global, per-client average,
        // before growing credit.
        if (usLocalCredit > usCreditAverage)
        {
            usCreditsFreed = usLocalCredit - usCreditAverage;
            usCreditsFreed = LW_MIN(usCreditsFreed, pCreditor->usCreditsFree);
            pCreditor->usCreditsFree -= usCreditsFreed;
            SrvCreditorReturnGlobalCredits(usCreditsFreed);

            usLocalCredit = lWindowSize + pCreditor->usCreditsFree;
        }

        if (usLocalCredit <= usCreditAverage)
        {
            // We are off probation.
            pCreditor->ulCreditShortageSerialNum = ulCreditShortageSerialNum;
        }
        else 
        {
            usCreditsRequested = 0;
        }
    }

    if (usCreditsRequested)
    {
        ULONG  ulCreditsStart = gSrvElements.config.ulClientCreditStart;
        USHORT usCreditsToAcquire = 0;

        // We can never exceed the maximum window size.
        usCreditsRequested = LW_MIN(
                                usCreditsRequested,
                                gSrvElements.config.ulClientCreditLimit -
                                lWindowSize);

        // Allow the client to reuse any local credit it already has.
        usCreditsGranted = LW_MIN(pCreditor->usCreditsFree, usCreditsRequested);
        pCreditor->usCreditsFree -= usCreditsGranted;
        usCreditsRequested -= usCreditsGranted;

        // If this doesn't meet the client's request, allow credit to
        // grow unrestricted up to ulCreditsStart, and then at a max of
        // LWIO_SRV_CREDIT_INCREMENT per round.
        if (usCreditsRequested)
        {
            usCreditsToAcquire = LW_MIN(
                                    usCreditsRequested,
                                    LWIO_SRV_CREDIT_INCREMENT);
            usCreditsRequested -= usCreditsToAcquire;
            if (usLocalCredit + usCreditsToAcquire < ulCreditsStart)
            {
                usCreditsToAcquire += LW_MIN(
                                        ulCreditsStart -
                                        (usLocalCredit + usCreditsToAcquire),
                                        usCreditsRequested);
            }

            // Ignore error. It's okay to ask for more credit than we have
            // available to currently lend.
            SrvCreditorAcquireGlobalCredits(
                usLocalCredit,
                usCreditsToAcquire,
                &usGlobalCreditsGranted);
            usCreditsGranted += usGlobalCreditsGranted;
        }
    }

    *pusCreditsGranted = usCreditsGranted;
}

VOID
SrvCreditorFree(
    PSRV_CREDITOR* ppCreditor
    )
{
    PSRV_CREDITOR pCreditor = *ppCreditor;

    if (pCreditor)
    {
        SrvCreditorReturnGlobalCredits(
            SrvCreditorWindowSize(pCreditor) + pCreditor->usCreditsFree);

        if (pCreditor->pMutex)
        {
            pthread_mutex_destroy(&pCreditor->mutex);
            pCreditor->pMutex = NULL;
        }

        if (pCreditor->pWindowBitmap)
        {
            bit_free(pCreditor->pWindowBitmap);
            pCreditor->pWindowBitmap = NULL;
        }

        SrvFreeMemory(pCreditor);
        *ppCreditor = NULL;
    }
}

static
VOID
SrvCreditReapCredit(
    VOID
    );

static
NTSTATUS
SrvCreditorAcquireGlobalCredits(
    USHORT  usLocalCredit,
    USHORT  usCreditsRequested,
    PUSHORT pusCreditsGranted
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN  bInLock = FALSE;
    USHORT   usCreditsGranted = 0;
    USHORT   usReserveCreditsGranted = 0;

    LWIO_LOCK_MUTEX(bInLock, gSrvElements.pMutex);

    usCreditsGranted = LW_MIN(
                            gSrvElements.ulGlobalCreditFree,
                            usCreditsRequested);
    
    gSrvElements.ulGlobalCreditFree -= usCreditsGranted;
    usCreditsRequested -= usCreditsGranted;

    if (usCreditsRequested != 0 &&
        usLocalCredit + usCreditsGranted <
        gSrvElements.config.ulClientCreditFloor)
    {
        usReserveCreditsGranted = LW_MIN(
                                        gSrvElements.ulGlobalReserveCreditFree,
                                        usCreditsRequested);
        gSrvElements.ulGlobalReserveCreditFree -= usReserveCreditsGranted;
        usCreditsGranted += usReserveCreditsGranted;
    }

    if (usReserveCreditsGranted ||
        (usCreditsGranted < usCreditsRequested &&
         (usLocalCredit + usCreditsGranted <
          gSrvElements.usClientCreditAverage)))
    {
        SrvCreditReapCredit();
    }

    if (usCreditsGranted == 0)
    {
        ntStatus = STATUS_INSUFF_SERVER_RESOURCES;
        BAIL_ON_NT_STATUS(ntStatus);
    }

cleanup:

    LWIO_UNLOCK_MUTEX(bInLock, gSrvElements.pMutex);

    *pusCreditsGranted = usCreditsGranted;

    return ntStatus;

error:

    goto cleanup;
}

static
VOID
SrvCreditorReturnGlobalCredits(
    USHORT usCredits
    )
{
    BOOLEAN bInLock = FALSE;
    USHORT  usReserveDebt = 0;

    LWIO_LOCK_MUTEX(bInLock, gSrvElements.pMutex);

    /* Return to the reserve pool first. */
    usReserveDebt = gSrvElements.config.ulGlobalCreditReserve -
                    gSrvElements.ulGlobalReserveCreditFree;
    usReserveDebt = LW_MIN(usReserveDebt, usCredits);
    gSrvElements.ulGlobalReserveCreditFree += usReserveDebt;
    usCredits -= usReserveDebt;

    gSrvElements.ulGlobalCreditFree += usCredits;

    LWIO_UNLOCK_MUTEX(bInLock, gSrvElements.pMutex);
}

static
VOID
SrvCreditReaperWorkItem(
    PLW_WORK_ITEM pWorkItem,
    PVOID pContext
    );

NTSTATUS
SrvCreditReaperInit(
    PSRV_CREDIT_REAPER pCreditReaper
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    ntStatus = LwRtlCreateWorkItem(
                    gSrvElements.ThreadPool,
                    &pCreditReaper->pWorkItem,
                    SrvCreditReaperWorkItem,
                    pCreditReaper);
    BAIL_ON_NT_STATUS(ntStatus);

cleanup:

    return ntStatus;

error:

    goto cleanup;
}

static
NTSTATUS
SrvCreditReaperReapConnection(
    PSRV_RESOURCE pResource,
    PVOID         pUserData,
    PBOOLEAN      pbContinue
    )
{
    PLWIO_SRV_CONNECTION pConnection    = NULL;
    PSRV_CREDITOR        pCreditor      = NULL;
    USHORT               usCreditsFreed = 0;
    BOOLEAN              bInLock        = FALSE;

    pConnection = LW_STRUCT_FROM_FIELD(
                        pResource,
                        LWIO_SRV_CONNECTION,
                        resource);
    pCreditor = pConnection->pCreditor;

    LWIO_LOCK_MUTEX(bInLock, &pCreditor->mutex);
    usCreditsFreed = pCreditor->usCreditsFree;
    pCreditor->usCreditsFree = 0;
    LWIO_UNLOCK_MUTEX(bInLock, &pCreditor->mutex);

    if (usCreditsFreed)
    {
        SrvCreditorReturnGlobalCredits(usCreditsFreed);
    }

    *pbContinue = TRUE;

    return STATUS_SUCCESS;
}

static
VOID
SrvCreditReaperWorkItem(
    PLW_WORK_ITEM pWorkItem,
    PVOID pContext
    )
{
    PSRV_CREDIT_REAPER pReaper = pContext;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    BOOLEAN bInLock = FALSE;

    pReaper->ulCreditShortageSerialNum =
        gSrvElements.ulGlobalCreditShortageSerialNum;

    ntStatus = SrvElementsEnumResources(
                    SRV_RESOURCE_TYPE_CONNECTION,
                    SrvCreditReaperReapConnection,
                    NULL);
    BAIL_ON_NT_STATUS(ntStatus);

error:

    LWIO_LOCK_MUTEX(bInLock, gSrvElements.pMutex);
    LWIO_LOG_WARNING(
        "Credit Reaping Complete. Global Credit %d:%d",
        gSrvElements.ulGlobalCreditFree,
        gSrvElements.ulGlobalReserveCreditFree);
    if (pReaper->ulCreditShortageSerialNum !=
        gSrvElements.ulGlobalCreditShortageSerialNum)
    {
        /*
         * Reaping was requested again after we started.
         * Loop again after yeilding to other work items.
         */
        LwRtlScheduleWorkItem(pWorkItem, 0);
    }
    else
    {
        pReaper->bScheduled = FALSE;
    }
    LWIO_UNLOCK_MUTEX(bInLock, gSrvElements.pMutex);
}

VOID
SrvCreditReaperShutdown(
    PSRV_CREDIT_REAPER pCreditReaper
    )
{
    LwRtlFreeWorkItem(&pCreditReaper->pWorkItem);
}

static
VOID
SrvCreditReapCredit(
    VOID
    )
{
    gSrvElements.ulGlobalCreditShortageSerialNum++;
    if (gSrvElements.CreditReaper.bScheduled == FALSE)
    {
        LWIO_LOG_WARNING(
            "Credit Reaper Starting. Global Credit %d:%d",
            gSrvElements.ulGlobalCreditFree,
            gSrvElements.ulGlobalReserveCreditFree);
        gSrvElements.CreditReaper.bScheduled = TRUE;
        LwRtlScheduleWorkItem(gSrvElements.CreditReaper.pWorkItem, 0);
    }
}
