#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/crypto/dca.h>
#include <sys/atomic.h>
static int dca_rngstart(dca_t *, dca_request_t *);
static void dca_rngdone(dca_request_t *, int);
static void dca_random_done();
int dca_random_buffer(dca_t *dca, caddr_t buf, int len);
int dca_random_init();
void dca_random_fini();
int
dca_rng(dca_t *dca, uchar_t *buf, size_t len, crypto_req_handle_t req)
{
dca_request_t *reqp;
int rv;
crypto_data_t *data;
if ((reqp = dca_getreq(dca, MCR2, 1)) == NULL) {
dca_error(dca, "unable to allocate request for RNG");
return (CRYPTO_HOST_MEMORY);
}
reqp->dr_kcf_req = req;
data = &reqp->dr_ctx.in_dup;
data->cd_format = CRYPTO_DATA_RAW;
data->cd_offset = 0;
data->cd_length = 0;
data->cd_raw.iov_base = (char *)buf;
data->cd_raw.iov_len = len;
reqp->dr_out = data;
reqp->dr_in = NULL;
rv = dca_rngstart(dca, reqp);
if (rv != CRYPTO_QUEUED) {
if (reqp->destroy)
dca_destroyreq(reqp);
else
dca_freereq(reqp);
}
return (rv);
}
int
dca_rngstart(dca_t *dca, dca_request_t *reqp)
{
uint16_t cmd;
size_t len;
uint16_t chunk;
crypto_data_t *out = reqp->dr_out;
if (dca->dca_flags & DCA_RNGSHA1) {
reqp->dr_job_stat = DS_RNGSHA1JOBS;
reqp->dr_byte_stat = DS_RNGSHA1BYTES;
cmd = CMD_RNGSHA1;
} else {
reqp->dr_job_stat = DS_RNGJOBS;
reqp->dr_byte_stat = DS_RNGBYTES;
cmd = CMD_RNGDIRECT;
}
len = out->cd_raw.iov_len - out->cd_length;
len = min(len, MAXPACKET & ~0xf);
chunk = ROUNDUP(len, sizeof (uint32_t));
if ((len < dca_mindma) ||
dca_sgcheck(dca, reqp->dr_out, DCA_SG_WALIGN)) {
reqp->dr_flags |= DR_SCATTER;
}
if (!(reqp->dr_flags & DR_SCATTER)) {
if (dca_bindchains(reqp, 0, len) != DDI_SUCCESS) {
return (CRYPTO_DEVICE_ERROR);
}
}
reqp->dr_in_paddr = 0;
reqp->dr_in_next = 0;
reqp->dr_in_len = 0;
if (reqp->dr_flags & DR_SCATTER) {
reqp->dr_out_paddr = reqp->dr_obuf_head.dc_buffer_paddr;
reqp->dr_out_next = reqp->dr_obuf_head.dc_next_paddr;
if (chunk > reqp->dr_obuf_head.dc_buffer_length)
reqp->dr_out_len = reqp->dr_obuf_head.dc_buffer_length;
else
reqp->dr_out_len = chunk;
}
reqp->dr_param.dp_rng.dr_chunklen = len;
reqp->dr_pkt_length = (uint16_t)chunk;
reqp->dr_callback = dca_rngdone;
PUTCTX16(reqp, CTX_LENGTH, CTX_RNG_LENGTH);
PUTCTX16(reqp, CTX_CMD, cmd);
return (dca_start(dca, reqp, MCR2, 1));
}
void
dca_rngdone(dca_request_t *reqp, int errno)
{
if (errno == CRYPTO_SUCCESS) {
if (reqp->dr_flags & DR_SCATTER) {
(void) ddi_dma_sync(reqp->dr_obuf_dmah, 0,
reqp->dr_out_len, DDI_DMA_SYNC_FORKERNEL);
if (dca_check_dma_handle(reqp->dr_dca,
reqp->dr_obuf_dmah, DCA_FM_ECLASS_NONE) !=
DDI_SUCCESS) {
reqp->destroy = TRUE;
errno = CRYPTO_DEVICE_ERROR;
goto errout;
}
errno = dca_scatter(reqp->dr_obuf_kaddr,
reqp->dr_out, reqp->dr_param.dp_rng.dr_chunklen, 0);
if (errno != CRYPTO_SUCCESS) {
goto errout;
}
} else {
reqp->dr_out->cd_length +=
reqp->dr_param.dp_rng.dr_chunklen;
}
if (reqp->dr_out->cd_length < reqp->dr_out->cd_raw.iov_len) {
errno = dca_rngstart(reqp->dr_dca, reqp);
if (errno == CRYPTO_QUEUED) {
return;
}
}
}
errout:
if (reqp->dr_kcf_req) {
crypto_op_notification(reqp->dr_kcf_req, errno);
} else {
dca_random_done(reqp->dr_dca);
}
DBG(NULL, DINTR,
"dca_rngdone: returning %d to the kef via crypto_op_notification",
errno);
if (reqp->destroy)
dca_destroyreq(reqp);
else
dca_freereq(reqp);
}
#define RANDOM_BUFFER_SIZE (1<<15)
#define DCA_RANDOM_MAX_WAIT 10000
int
dca_random_init(dca_t *dca)
{
mutex_init(&dca->dca_random_lock, NULL, MUTEX_DRIVER, NULL);
if ((dca->dca_buf1 = kmem_alloc(RANDOM_BUFFER_SIZE, KM_SLEEP)) ==
NULL) {
mutex_destroy(&dca->dca_random_lock);
return (CRYPTO_FAILED);
}
if ((dca->dca_buf2 = kmem_alloc(RANDOM_BUFFER_SIZE, KM_SLEEP)) ==
NULL) {
mutex_destroy(&dca->dca_random_lock);
kmem_free(dca->dca_buf1, RANDOM_BUFFER_SIZE);
return (CRYPTO_FAILED);
}
return (CRYPTO_SUCCESS);
}
void
dca_random_fini(dca_t *dca)
{
kmem_free(dca->dca_buf1, RANDOM_BUFFER_SIZE);
kmem_free(dca->dca_buf2, RANDOM_BUFFER_SIZE);
dca->dca_buf1 = dca->dca_buf2 = dca->dca_buf_ptr = NULL;
(void) mutex_destroy(&dca->dca_random_lock);
}
int
dca_random_buffer(dca_t *dca, caddr_t buf, int len)
{
int rv;
int i, j;
char *fill_buf;
mutex_enter(&dca->dca_random_lock);
if (dca->dca_buf_ptr == NULL) {
if (dca->dca_buf1 == NULL || dca->dca_buf2 == NULL) {
mutex_exit(&dca->dca_random_lock);
return (CRYPTO_FAILED);
}
if (dca_rng(dca, (uchar_t *)dca->dca_buf1, RANDOM_BUFFER_SIZE,
NULL) != CRYPTO_QUEUED) {
mutex_exit(&dca->dca_random_lock);
return (CRYPTO_FAILED);
}
atomic_or_32(&dca->dca_random_filling, 0x1);
dca->dca_buf_ptr = dca->dca_buf2;
dca->dca_index = RANDOM_BUFFER_SIZE;
}
i = 0;
while (i < len) {
if (dca->dca_index >= RANDOM_BUFFER_SIZE) {
j = 0;
while (dca->dca_random_filling) {
delay(drv_usectohz(100));
if (j++ >= DCA_RANDOM_MAX_WAIT)
break;
}
DBG(NULL, DENTRY, "dca_random_buffer: j: %d", j);
if (j > DCA_RANDOM_MAX_WAIT) {
mutex_exit(&dca->dca_random_lock);
return (CRYPTO_FAILED);
}
if (dca->dca_buf_ptr == dca->dca_buf1) {
dca->dca_buf_ptr = dca->dca_buf2;
fill_buf = dca->dca_buf1;
} else {
dca->dca_buf_ptr = dca->dca_buf1;
fill_buf = dca->dca_buf2;
}
atomic_or_32(&dca->dca_random_filling, 0x1);
dca->dca_index = 0;
if ((rv = dca_rng(dca, (uchar_t *)fill_buf,
RANDOM_BUFFER_SIZE, NULL)) != CRYPTO_QUEUED) {
mutex_exit(&dca->dca_random_lock);
return (rv);
}
}
if (dca->dca_buf_ptr[dca->dca_index] != '\0')
buf[i++] = dca->dca_buf_ptr[dca->dca_index];
dca->dca_index++;
}
mutex_exit(&dca->dca_random_lock);
DBG(NULL, DENTRY, "dca_random_buffer: i: %d", i);
return (CRYPTO_SUCCESS);
}
static void
dca_random_done(dca_t *dca)
{
DBG(NULL, DENTRY, "dca_random_done");
atomic_and_32(&dca->dca_random_filling, 0x0);
}