/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2014 QLogic Corporation
 * The contents of this file are subject to the terms of the
 * QLogic End User License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.qlogic.com/Resources/Documents/DriverDownloadHelp/
 * QLogic_End_User_Software_License.txt
 * See the License for the specific language governing permissions
 * and limitations under the License.
 */

#include "bnxe.h"


typedef struct _BnxeWorkItem
{
    s_list_entry_t link;
    void *         pWorkData;
    u32_t          workDataLen;
    u32_t          delayMs;
    void (*pWorkCbkCopy)(um_device_t *, void *, u32_t);
    void (*pWorkCbkNoCopy)(um_device_t *, void *);
    void (*pWorkCbkGeneric)(um_device_t *);
} BnxeWorkItem;


static void BnxeWorkQueueInstanceWaitAndDestroy(BnxeWorkQueueInstance * pWorkq)
{
    if (pWorkq->pTaskq)
    {
        ddi_taskq_wait(pWorkq->pTaskq);
        ddi_taskq_destroy(pWorkq->pTaskq);
        mutex_destroy(&pWorkq->workQueueMutex);
    }

    memset(pWorkq, 0, sizeof(BnxeWorkQueueInstance));
}


boolean_t BnxeWorkQueueInit(um_device_t * pUM)
{
    pUM->workqs.instq.pUM = pUM;

    strcpy(pUM->workqs.instq.taskqName, pUM->devName);
    strcat(pUM->workqs.instq.taskqName, "_inst_q");

    mutex_init(&pUM->workqs.instq.workQueueMutex, NULL,
               MUTEX_DRIVER, DDI_INTR_PRI(pUM->intrPriority));

    if ((pUM->workqs.instq.pTaskq =
         ddi_taskq_create(pUM->pDev,
                          pUM->workqs.instq.taskqName,
                          1,
                          TASKQ_DEFAULTPRI,
                          0)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to create the workqs instq");
        return B_FALSE;
    }

    pUM->workqs.instq.pUM = pUM;

    strcpy(pUM->workqs.delayq.taskqName, pUM->devName);
    strcat(pUM->workqs.delayq.taskqName, "_delay_q");

    mutex_init(&pUM->workqs.delayq.workQueueMutex, NULL,
               MUTEX_DRIVER, DDI_INTR_PRI(pUM->intrPriority));

    if ((pUM->workqs.delayq.pTaskq =
         ddi_taskq_create(pUM->pDev,
                          pUM->workqs.delayq.taskqName,
                          16, /* XXX Is this enough? */
                          TASKQ_DEFAULTPRI,
                          0)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to create the workqs delayq");
        BnxeWorkQueueInstanceWaitAndDestroy(&pUM->workqs.instq);
        return B_FALSE;
    }

    pUM->workqs.delayq.pUM = pUM;

    return B_TRUE;
}


void BnxeWorkQueueWaitAndDestroy(um_device_t * pUM)
{
    BnxeWorkQueueInstanceWaitAndDestroy(&pUM->workqs.instq);
    BnxeWorkQueueInstanceWaitAndDestroy(&pUM->workqs.delayq);
}


static void BnxeWorkQueueDispatch(void * pArg)
{
    BnxeWorkQueueInstance * pWorkq = (BnxeWorkQueueInstance *)pArg;
    um_device_t * pUM = (um_device_t *)pWorkq->pUM;
    BnxeWorkItem * pWorkItem;

    mutex_enter(&pWorkq->workQueueMutex);
    pWorkItem = (BnxeWorkItem *)s_list_pop_head(&pWorkq->workQueue);
    mutex_exit(&pWorkq->workQueueMutex);

    if (pWorkItem == NULL)
    {
        BnxeLogWarn(pUM, "Work item is NULL!");
        pWorkq->workItemError++;
        return;
    }

    if ((pWorkItem->pWorkCbkCopy == NULL) &&
        (pWorkItem->pWorkCbkNoCopy == NULL) &&
        (pWorkItem->pWorkCbkGeneric == NULL))
    {
        BnxeLogWarn(pUM, "Work item callback is NULL!");
        pWorkq->workItemError++;
        goto BnxeWorkQueueDispatch_done;
    }

    if (pWorkItem->delayMs > 0)
    {
        /* this only occurs when processing the delayq */
        drv_usecwait(pWorkItem->delayMs * 1000);
    }

    if (pWorkItem->pWorkCbkCopy)
    {
        pWorkItem->pWorkCbkCopy(pUM,
                                pWorkItem->pWorkData,
                                pWorkItem->workDataLen);
    }
    else if (pWorkItem->pWorkCbkNoCopy)
    {
        pWorkItem->pWorkCbkNoCopy(pUM,
                                  pWorkItem->pWorkData);
    }
    else /* (pWorkItem->pWorkCbkGeneric) */
    {
        pWorkItem->pWorkCbkGeneric(pUM);
    }

    pWorkq->workItemComplete++;

BnxeWorkQueueDispatch_done:

    kmem_free(pWorkItem, (sizeof(BnxeWorkItem) + pWorkItem->workDataLen));
}


static void BnxeWorkQueueTrigger(um_device_t *           pUM,
                                 BnxeWorkQueueInstance * pWorkq)
{
    if (pUM->chipStarted)
    {
        ddi_taskq_dispatch(pWorkq->pTaskq,
                           BnxeWorkQueueDispatch,
                           (void *)pWorkq,
                           DDI_NOSLEEP);
    }
    else
    {
        BnxeLogInfo(pUM, "Delaying WorkQ item since chip not yet started.");
    }
}


void BnxeWorkQueueStartPending(um_device_t * pUM)
{
    u32_t cnt;

    if (!pUM->chipStarted)
    {
        BnxeLogWarn(pUM, "Triggering WorkQs and chip not started!");
        return;
    }

    mutex_enter(&pUM->workqs.instq.workQueueMutex);
    cnt = s_list_entry_cnt(&pUM->workqs.instq.workQueue);
    mutex_exit(&pUM->workqs.instq.workQueueMutex);

    if (cnt)
    {
        BnxeWorkQueueTrigger(pUM, &pUM->workqs.instq);
    }

    mutex_enter(&pUM->workqs.delayq.workQueueMutex);
    cnt = s_list_entry_cnt(&pUM->workqs.delayq.workQueue);
    mutex_exit(&pUM->workqs.delayq.workQueueMutex);

    if (cnt)
    {
        BnxeWorkQueueTrigger(pUM, &pUM->workqs.delayq);
    }
}


boolean_t BnxeWorkQueueAdd(um_device_t * pUM,
                           void (*pWorkCbkCopy)(um_device_t *, void *, u32_t),
                           void * pWorkData,
                           u32_t  workDataLen)
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc((sizeof(BnxeWorkItem) + workDataLen),
                                 KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = (pWorkItem + 1);
    pWorkItem->workDataLen     = workDataLen;
    pWorkItem->pWorkCbkCopy    = pWorkCbkCopy;
    pWorkItem->pWorkCbkNoCopy  = NULL;
    pWorkItem->pWorkCbkGeneric = NULL;
    pWorkItem->delayMs         = 0;

    memcpy(pWorkItem->pWorkData, pWorkData, workDataLen);

    mutex_enter(&pUM->workqs.instq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.instq.workQueue, &pWorkItem->link);
    pUM->workqs.instq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.instq.workQueue) >
        pUM->workqs.instq.highWater)
    {
        pUM->workqs.instq.highWater =
            s_list_entry_cnt(&pUM->workqs.instq.workQueue);
    }

    mutex_exit(&pUM->workqs.instq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.instq);

    return B_TRUE;
}


boolean_t BnxeWorkQueueAddNoCopy(um_device_t * pUM,
                                 void (*pWorkCbkNoCopy)(um_device_t *, void *),
                                 void * pWorkData)
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc(sizeof(BnxeWorkItem), KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = pWorkData;
    pWorkItem->workDataLen     = 0;
    pWorkItem->pWorkCbkCopy    = NULL;
    pWorkItem->pWorkCbkNoCopy  = pWorkCbkNoCopy;
    pWorkItem->pWorkCbkGeneric = NULL;
    pWorkItem->delayMs         = 0;

    mutex_enter(&pUM->workqs.instq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.instq.workQueue, &pWorkItem->link);
    pUM->workqs.instq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.instq.workQueue) >
        pUM->workqs.instq.highWater)
    {
        pUM->workqs.instq.highWater =
            s_list_entry_cnt(&pUM->workqs.instq.workQueue);
    }

    mutex_exit(&pUM->workqs.instq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.instq);

    return B_TRUE;
}


boolean_t BnxeWorkQueueAddGeneric(um_device_t * pUM,
                                  void (*pWorkCbkGeneric)(um_device_t *))
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc(sizeof(BnxeWorkItem), KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = NULL;
    pWorkItem->workDataLen     = 0;
    pWorkItem->pWorkCbkCopy    = NULL;
    pWorkItem->pWorkCbkNoCopy  = NULL;
    pWorkItem->pWorkCbkGeneric = pWorkCbkGeneric;
    pWorkItem->delayMs         = 0;

    mutex_enter(&pUM->workqs.instq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.instq.workQueue, &pWorkItem->link);
    pUM->workqs.instq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.instq.workQueue) >
        pUM->workqs.instq.highWater)
    {
        pUM->workqs.instq.highWater =
            s_list_entry_cnt(&pUM->workqs.instq.workQueue);
    }

    mutex_exit(&pUM->workqs.instq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.instq);

    return B_TRUE;
}


boolean_t BnxeWorkQueueAddDelay(um_device_t * pUM,
                                void (*pWorkCbkCopy)(um_device_t *, void *, u32_t),
                                void * pWorkData,
                                u32_t  workDataLen,
                                u32_t  delayMs)
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc((sizeof(BnxeWorkItem) + workDataLen),
                                 KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = (pWorkItem + 1);
    pWorkItem->workDataLen     = workDataLen;
    pWorkItem->pWorkCbkCopy    = pWorkCbkCopy;
    pWorkItem->pWorkCbkNoCopy  = NULL;
    pWorkItem->pWorkCbkGeneric = NULL;
    pWorkItem->delayMs         = delayMs;

    memcpy(pWorkItem->pWorkData, pWorkData, workDataLen);

    mutex_enter(&pUM->workqs.delayq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.delayq.workQueue, &pWorkItem->link);
    pUM->workqs.delayq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.delayq.workQueue) >
        pUM->workqs.delayq.highWater)
    {
        pUM->workqs.delayq.highWater =
            s_list_entry_cnt(&pUM->workqs.delayq.workQueue);
    }

    mutex_exit(&pUM->workqs.delayq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.delayq);

    return B_TRUE;
}


boolean_t BnxeWorkQueueAddDelayNoCopy(um_device_t * pUM,
                                      void (*pWorkCbkNoCopy)(um_device_t *, void *),
                                      void * pWorkData,
                                      u32_t  delayMs)
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc(sizeof(BnxeWorkItem), KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = pWorkData;
    pWorkItem->workDataLen     = 0;
    pWorkItem->pWorkCbkCopy    = NULL;
    pWorkItem->pWorkCbkNoCopy  = pWorkCbkNoCopy;
    pWorkItem->pWorkCbkGeneric = NULL;
    pWorkItem->delayMs         = delayMs;

    mutex_enter(&pUM->workqs.delayq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.delayq.workQueue, &pWorkItem->link);
    pUM->workqs.delayq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.delayq.workQueue) >
        pUM->workqs.delayq.highWater)
    {
        pUM->workqs.delayq.highWater =
            s_list_entry_cnt(&pUM->workqs.delayq.workQueue);
    }

    mutex_exit(&pUM->workqs.delayq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.delayq);

    return B_TRUE;
}


boolean_t BnxeWorkQueueAddDelayGeneric(um_device_t * pUM,
                                       void (*pWorkCbkGeneric)(um_device_t *),
                                       u32_t delayMs)
{
    BnxeWorkItem * pWorkItem;

    if ((pWorkItem = kmem_zalloc(sizeof(BnxeWorkItem), KM_NOSLEEP)) == NULL)
    {
        BnxeLogWarn(pUM, "Failed to allocate memory for work item!");
        return B_FALSE;
    }

    pWorkItem->pWorkData       = NULL;
    pWorkItem->workDataLen     = 0;
    pWorkItem->pWorkCbkCopy    = NULL;
    pWorkItem->pWorkCbkNoCopy  = NULL;
    pWorkItem->pWorkCbkGeneric = pWorkCbkGeneric;
    pWorkItem->delayMs         = delayMs;

    mutex_enter(&pUM->workqs.delayq.workQueueMutex);

    s_list_push_tail(&pUM->workqs.delayq.workQueue, &pWorkItem->link);
    pUM->workqs.delayq.workItemQueued++;
    if (s_list_entry_cnt(&pUM->workqs.delayq.workQueue) >
        pUM->workqs.delayq.highWater)
    {
        pUM->workqs.delayq.highWater =
            s_list_entry_cnt(&pUM->workqs.delayq.workQueue);
    }

    mutex_exit(&pUM->workqs.delayq.workQueueMutex);

    BnxeWorkQueueTrigger(pUM, &pUM->workqs.delayq);

    return B_TRUE;
}