/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/uio.h>
#include <sys/stream.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/strsun.h>
#include <sys/kmem.h>
#include <sys/atomic.h>
#include <sys/random.h>
#include <sys/crypto/common.h>
#include <sys/crypto/spi.h>
#include <sys/n2rng.h>

#define	IDENT_N2RNG		"SUNW_N2_Random_Number_Generator"

#define	N2RNG_PROVIDER2N2RNG(x)	(((n2rng_provider_private_t *)x)->mp_n2rng)


static void n2rng_provider_status(crypto_provider_handle_t, uint_t *);

static int n2rng_random_number(crypto_provider_handle_t, crypto_session_id_t,
			uchar_t *, size_t, crypto_req_handle_t);

static int ext_info(crypto_provider_handle_t, crypto_provider_ext_info_t *,
			crypto_req_handle_t);

void n2rng_ksinit(n2rng_t *n2rng);
void n2rng_ksdeinit(n2rng_t *n2rng);

static int fips_init(n2rng_t *n2rng);
static void fips_fini(n2rng_t *n2rng);
int fips_random(n2rng_t *n2rng, uint8_t *out, size_t nbytes);


static crypto_control_ops_t n2rng_control_ops = {
	n2rng_provider_status
};


static crypto_random_number_ops_t n2rng_rng_ops = {
	NULL,		/* seed_random */
	n2rng_random_number
};

static crypto_provider_management_ops_t n2rng_extinfo_op = {
	ext_info,	/* ext_info */
	NULL,		/* init_token */
	NULL, 		/* init_pin */
	NULL,		/* set_pin */
};

static crypto_ops_t n2rng_ops = {
	&n2rng_control_ops,
	NULL,				/* digest_ops */
	NULL,				/* cipher_ops */
	NULL,				/* mac_ops */
	NULL,				/* sign_ops */
	NULL,				/* verify_ops */
	NULL,				/* dual_ops */
	NULL,				/* cipher_mac_ops */
	&n2rng_rng_ops,			/* rng_ops */
	NULL,				/* session_ops */
	NULL,				/* object_ops */
	NULL,				/* key_ops */
	&n2rng_extinfo_op,		/* management_ops */
	NULL,				/* ctx_ops */
	NULL				/* mech_ops */
};

static crypto_provider_info_t n2rng_prov_info = {
	CRYPTO_SPI_VERSION_2,
	NULL,				/* pi_provider_description */
	CRYPTO_HW_PROVIDER,
	NULL,				/* pi_provider_dev */
	NULL,				/* pi_provider_handle */
	&n2rng_ops,
	0,				/* number of mechanisms */
	NULL,				/* mechanism table */
	0,				/* pi_logical_provider_count */
	NULL				/* pi_logical_providers */
};

static void
strncpy_spacepad(uchar_t *s1, char *s2, int n)
{
	int s2len = strlen(s2);

	(void) strncpy((char *)s1, s2, n);
	if (s2len < n)
		(void) memset(s1 + s2len, ' ', n - s2len);
}

/*ARGSUSED*/
static int
ext_info(crypto_provider_handle_t prov, crypto_provider_ext_info_t *ext_info,
    crypto_req_handle_t cfreq)
{
#define	BUFSZ	64
	n2rng_t	*n2rng = (n2rng_t *)prov;
	char	buf[BUFSZ];

	/* handle info common to logical and hardware provider */

	/* Manufacturer ID */
	strncpy_spacepad(ext_info->ei_manufacturerID, N2RNG_MANUFACTURER_ID,
	    CRYPTO_EXT_SIZE_MANUF);

	/* Model */
	strncpy_spacepad(ext_info->ei_model, "0", CRYPTO_EXT_SIZE_MODEL);

	/* Token flags */
	ext_info->ei_flags = CRYPTO_EXTF_RNG | CRYPTO_EXTF_SO_PIN_LOCKED |
	    CRYPTO_EXTF_WRITE_PROTECTED;

	ext_info->ei_max_session_count = CRYPTO_EFFECTIVELY_INFINITE;
	ext_info->ei_max_pin_len = 0;
	ext_info->ei_min_pin_len = 0;
	ext_info->ei_total_public_memory = CRYPTO_UNAVAILABLE_INFO;
	ext_info->ei_free_public_memory = CRYPTO_UNAVAILABLE_INFO;
	ext_info->ei_total_private_memory = CRYPTO_UNAVAILABLE_INFO;
	ext_info->ei_free_private_memory = CRYPTO_UNAVAILABLE_INFO;

	/* Time. No need to be supplied for token without a clock */
	ext_info->ei_time[0] = '\000';

	/* handle hardware provider specific fields */

	/* Token label */
	(void) snprintf(buf, BUFSZ, "%s/%d SUNW_N2_RNG",
	    ddi_driver_name(n2rng->n_dip),
	    ddi_get_instance(n2rng->n_dip));

	/* Serial number */
	strncpy_spacepad(ext_info->ei_serial_number,
	    "0",
	    CRYPTO_EXT_SIZE_SERIAL);

	/* Version info */
	ext_info->ei_hardware_version.cv_major = 0;
	ext_info->ei_hardware_version.cv_minor = 0;
	ext_info->ei_firmware_version.cv_major = 0;
	ext_info->ei_firmware_version.cv_minor = 0;

	buf[BUFSZ - 1] = '\000';
	/* set the token label */
	strncpy_spacepad(ext_info->ei_label, buf, CRYPTO_EXT_SIZE_LABEL);

#undef	BUFSZ

	return (CRYPTO_SUCCESS);
}


/*
 * Setup and also register to kCF
 */
int
n2rng_init(n2rng_t *n2rng)
{
	int		ret;
	char		ID[64];
	dev_info_t	*dip;

	dip = n2rng->n_dip;

	/* initialize kstats */
	n2rng_ksinit(n2rng);

	/* initialize the FIPS data and mutexes */
	ret = fips_init(n2rng);
	if (ret) {
		n2rng_ksdeinit(n2rng);
		return (DDI_FAILURE);
	}

	/* register with the crypto framework */
	/* Be careful not to exceed 32 chars */
	(void) sprintf(ID, "%s/%d %s",
	    ddi_driver_name(dip), ddi_get_instance(dip),
	    IDENT_N2RNG);
	n2rng_prov_info.pi_provider_description = ID;
	n2rng_prov_info.pi_provider_dev.pd_hw = dip;
	n2rng_prov_info.pi_provider_handle = n2rng;
	ret = crypto_register_provider(&n2rng_prov_info, &n2rng->n_prov);
	if (ret != CRYPTO_SUCCESS) {
		cmn_err(CE_WARN,
		    "crypto_register_provider() failed (%d)", ret);
		fips_fini(n2rng);
		n2rng_ksdeinit(n2rng);
		return (DDI_FAILURE);
	}

	crypto_provider_notification(n2rng->n_prov, CRYPTO_PROVIDER_READY);

	return (DDI_SUCCESS);
}


/*
 * Unregister from kCF and cleanup
 */
int
n2rng_uninit(n2rng_t *n2rng)
{
	/*
	 * Unregister from kCF.
	 * This needs to be done at the beginning of detach.
	 */
	if (n2rng->n_prov != NULL) {
		if (crypto_unregister_provider(n2rng->n_prov) !=
		    CRYPTO_SUCCESS) {
			n2rng_error(n2rng, "unable to unregister from kcf");
			return (DDI_FAILURE);
		}
		n2rng->n_prov = NULL;
	}

	fips_fini(n2rng);


	/* deinitialize kstats */
	n2rng_ksdeinit(n2rng);

	return (DDI_SUCCESS);
}

/*
 * At this time there are no periodic health checks.  If the health
 * check done at attrach time fails, the driver does not even attach.
 * So there are no failure conditions to report, and this provider is
 * never busy.
 */
/* ARGSUSED */
static void
n2rng_provider_status(crypto_provider_handle_t provider, uint_t *status)
{
	*status = CRYPTO_PROVIDER_READY;
}

/*ARGSUSED*/
static int
n2rng_random_number(crypto_provider_handle_t provider,
	crypto_session_id_t sess, unsigned char *buf, size_t buflen,
	crypto_req_handle_t cfreq)
{
	n2rng_t		*n2rng = (n2rng_t *)provider;
	int		rv;

	rv = fips_random(n2rng, buf, buflen);

	atomic_add_64(&n2rng->n_stats[DS_RNGBYTES], buflen);
	atomic_inc_64(&n2rng->n_stats[DS_RNGJOBS]);

	return (rv);
}

static int
fips_init(n2rng_t *n2rng)
{
	int		i;
	int		rv;

	n2rng->n_frs.fips_round_robin_j = 0;
	for (i = 0; i < N2RNG_FIPS_INSTANCES; i++) {
		rv = n2rng_fips_random_init(n2rng, &n2rng->n_frs.fipsarray[i]);
		if (rv) {
			/* finalize all the FIPS structures allocated so far */
			for (--i; i >= 0; --i) {
				n2rng_fips_random_fini(
				    &n2rng->n_frs.fipsarray[i]);
			}
			return (rv);
		}
	}
	return (0);
}


static void
fips_fini(n2rng_t *n2rng)
{
	int		i;

	for (i = 0; i < N2RNG_FIPS_INSTANCES; i++) {
		n2rng_fips_random_fini(&n2rng->n_frs.fipsarray[i]);
	}
}