/* * 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 #include #include #include #include #include #include #include /* * 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); } } }