kcf_cbufcall.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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);
}
}
}