root/usr/src/uts/common/crypto/api/kcf_cbufcall.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * crypto_bufcall(9F) group of routines.
 */

#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/callb.h>
#include <sys/ksynch.h>
#include <sys/systm.h>
#include <sys/taskq_impl.h>
#include <sys/crypto/api.h>
#include <sys/crypto/sched_impl.h>

/*
 * All pending crypto bufcalls are put on a list. cbuf_list_lock
 * protects changes to this list.
 *
 * The following locking order is maintained in the code - The
 * global cbuf_list_lock followed by the individual lock
 * in a crypto bufcall structure (kc_lock).
 */
kmutex_t        cbuf_list_lock;
kcondvar_t      cbuf_list_cv;   /* cv the service thread waits on */
static kcf_cbuf_elem_t *cbuf_list_head;
static kcf_cbuf_elem_t *cbuf_list_tail;

/*
 * Allocate and return a handle to be used for crypto_bufcall().
 * Can be called from user context only.
 */
crypto_bc_t
crypto_bufcall_alloc(void)
{
        kcf_cbuf_elem_t *cbufp;

        cbufp = kmem_zalloc(sizeof (kcf_cbuf_elem_t), KM_SLEEP);
        mutex_init(&cbufp->kc_lock, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&cbufp->kc_cv, NULL, CV_DEFAULT, NULL);
        cbufp->kc_state = CBUF_FREE;

        return (cbufp);
}

/*
 * Free the handle if possible. Returns CRYPTO_SUCCESS if the handle
 * is freed. Else it returns CRYPTO_BUSY.
 *
 * The client should do a crypto_unbufcall() if it receives a
 * CRYPTO_BUSY.
 *
 * Can be called both from user and interrupt context.
 */
int
crypto_bufcall_free(crypto_bc_t bc)
{
        kcf_cbuf_elem_t *cbufp = (kcf_cbuf_elem_t *)bc;

        mutex_enter(&cbufp->kc_lock);
        if (cbufp->kc_state != CBUF_FREE) {
                mutex_exit(&cbufp->kc_lock);
                return (CRYPTO_BUSY);
        }
        mutex_exit(&cbufp->kc_lock);

        mutex_destroy(&cbufp->kc_lock);
        cv_destroy(&cbufp->kc_cv);
        kmem_free(cbufp, sizeof (kcf_cbuf_elem_t));

        return (CRYPTO_SUCCESS);
}

/*
 * Schedule func() to be called when queue space is available to
 * submit a crypto request.
 *
 * Can be called both from user and interrupt context.
 */
int
crypto_bufcall(crypto_bc_t bc, void (*func)(void *arg), void *arg)
{
        kcf_cbuf_elem_t *cbufp;

        cbufp = (kcf_cbuf_elem_t *)bc;
        if (cbufp == NULL || func == NULL) {
                return (CRYPTO_ARGUMENTS_BAD);
        }

        mutex_enter(&cbuf_list_lock);
        mutex_enter(&cbufp->kc_lock);
        if (cbufp->kc_state != CBUF_FREE) {
                mutex_exit(&cbufp->kc_lock);
                mutex_exit(&cbuf_list_lock);
                return (CRYPTO_BUSY);
        }

        cbufp->kc_state = CBUF_WAITING;
        cbufp->kc_func = func;
        cbufp->kc_arg = arg;
        cbufp->kc_prev = cbufp->kc_next = NULL;

        if (cbuf_list_head == NULL) {
                cbuf_list_head = cbuf_list_tail = cbufp;
        } else {
                cbuf_list_tail->kc_next = cbufp;
                cbufp->kc_prev = cbuf_list_tail;
                cbuf_list_tail = cbufp;
        }

        /*
         * Signal the crypto_bufcall_service thread to start
         * working on this crypto bufcall request.
         */
        cv_signal(&cbuf_list_cv);
        mutex_exit(&cbufp->kc_lock);
        mutex_exit(&cbuf_list_lock);

        return (CRYPTO_SUCCESS);
}

/*
 * Cancel a pending crypto bufcall request. If the bufcall
 * is currently executing, we wait till it is complete.
 *
 * Can only be called from user context.
 */
int
crypto_unbufcall(crypto_bc_t bc)
{
        kcf_cbuf_elem_t *cbufp = (kcf_cbuf_elem_t *)bc;

        mutex_enter(&cbuf_list_lock);
        mutex_enter(&cbufp->kc_lock);

        if (cbufp->kc_state == CBUF_WAITING) {
                kcf_cbuf_elem_t *nextp = cbufp->kc_next;
                kcf_cbuf_elem_t *prevp = cbufp->kc_prev;

                if (nextp != NULL)
                        nextp->kc_prev = prevp;
                else
                        cbuf_list_tail = prevp;

                if (prevp != NULL)
                        prevp->kc_next = nextp;
                else
                        cbuf_list_head = nextp;
                cbufp->kc_state = CBUF_FREE;
        } else if (cbufp->kc_state == CBUF_RUNNING) {
                mutex_exit(&cbuf_list_lock);
                /*
                 * crypto_bufcall_service thread is working
                 * on this element. We will wait for that
                 * thread to signal us when done.
                 */
                while (cbufp->kc_state == CBUF_RUNNING)
                        cv_wait(&cbufp->kc_cv, &cbufp->kc_lock);
                mutex_exit(&cbufp->kc_lock);

                return (CRYPTO_SUCCESS);
        }

        mutex_exit(&cbufp->kc_lock);
        mutex_exit(&cbuf_list_lock);

        return (CRYPTO_SUCCESS);
}

/*
 * We sample the number of jobs. We do not hold the lock
 * as it is not necessary to get the exact count.
 */
#define KCF_GSWQ_AVAIL  (gswq->gs_maxjobs - gswq->gs_njobs)

/*
 * One queue space each for init, update, and final.
 */
#define GSWQ_MINFREE    3

/*
 * Go through the list of crypto bufcalls and do the necessary
 * callbacks.
 */
static void
kcf_run_cbufcalls(void)
{
        kcf_cbuf_elem_t *cbufp;
        int count;

        mutex_enter(&cbuf_list_lock);

        /*
         * Get estimate of available queue space from KCF_GSWQ_AVAIL.
         * We can call 'n' crypto bufcall callback functions where
         * n * GSWQ_MINFREE <= available queue space.
         *
         * TO DO - Extend the check to taskqs of hardware providers.
         * For now, we handle only the software providers.
         */
        count = KCF_GSWQ_AVAIL;
        while ((cbufp = cbuf_list_head) != NULL) {
                if (GSWQ_MINFREE <= count) {
                        count -= GSWQ_MINFREE;
                        mutex_enter(&cbufp->kc_lock);
                        cbuf_list_head = cbufp->kc_next;
                        cbufp->kc_state = CBUF_RUNNING;
                        mutex_exit(&cbufp->kc_lock);
                        mutex_exit(&cbuf_list_lock);

                        (*cbufp->kc_func)(cbufp->kc_arg);

                        mutex_enter(&cbufp->kc_lock);
                        cbufp->kc_state = CBUF_FREE;
                        cv_broadcast(&cbufp->kc_cv);
                        mutex_exit(&cbufp->kc_lock);

                        mutex_enter(&cbuf_list_lock);
                } else {
                        /*
                         * There is not enough queue space in this
                         * round. We bail out and try again
                         * later.
                         */
                        break;
                }
        }
        if (cbuf_list_head == NULL)
                cbuf_list_tail = NULL;

        mutex_exit(&cbuf_list_lock);
}

/*
 * Background processing of crypto bufcalls.
 */
void
crypto_bufcall_service(void)
{
        callb_cpr_t     cprinfo;

        CALLB_CPR_INIT(&cprinfo, &cbuf_list_lock, callb_generic_cpr,
            "crypto_bufcall_service");

        mutex_enter(&cbuf_list_lock);

        for (;;) {
                if (cbuf_list_head != NULL && KCF_GSWQ_AVAIL >= GSWQ_MINFREE) {
                        mutex_exit(&cbuf_list_lock);
                        kcf_run_cbufcalls();
                        mutex_enter(&cbuf_list_lock);
                }

                if (cbuf_list_head != NULL) {
                        /*
                         * Wait 30 seconds for queue space to become available.
                         * This number is reasonable as it does not cause
                         * much CPU overhead. We could wait on a condition
                         * variable and the global software dequeue routine can
                         * signal us. But, it adds overhead to that routine
                         * which we want to avoid. Also, the client is prepared
                         * to wait any way.
                         */
                        CALLB_CPR_SAFE_BEGIN(&cprinfo);
                        mutex_exit(&cbuf_list_lock);
                        delay(30 * drv_usectohz(1000000));
                        mutex_enter(&cbuf_list_lock);
                        CALLB_CPR_SAFE_END(&cprinfo, &cbuf_list_lock);
                }

                /* Wait for new work to arrive */
                if (cbuf_list_head == NULL) {
                        CALLB_CPR_SAFE_BEGIN(&cprinfo);
                        cv_wait(&cbuf_list_cv, &cbuf_list_lock);
                        CALLB_CPR_SAFE_END(&cprinfo, &cbuf_list_lock);
                }
        }
}