/*
* 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
* 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
*/
/*
*/
/*
* Niagara 2 Random Number Generator (RNG) driver
*/
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#include <sys/hypervisor_api.h>
static int n2rng_suspend(n2rng_t *);
static int n2rng_resume(n2rng_t *);
static uint64_t sticks_per_usec(void);
u_longlong_t gettick(void);
static int n2rng_init_ctl(n2rng_t *);
static void n2rng_uninit_ctl(n2rng_t *);
static int n2rng_config(n2rng_t *);
static void n2rng_config_task(void * targ);
/*
* Device operations.
*/
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
nodev, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
n2rng_attach, /* devo_attach */
n2rng_detach, /* devo_detach */
nodev, /* devo_reset */
NULL, /* devo_cb_ops */
NULL, /* devo_bus_ops */
ddi_power, /* devo_power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
/*
* Module linkage.
*/
&mod_driverops, /* drv_modops */
"N2 RNG Driver", /* drv_linkinfo */
&devops, /* drv_dev_ops */
};
MODREV_1, /* ml_rev */
&modldrv, /* ml_linkage */
};
/*
* Driver globals Soft state.
*/
/*
* Hypervisor NCS services information.
*/
/*
* HV API versions supported by this driver.
*/
};
/*
* DDI entry points.
*/
int
_init(void)
{
int rv;
if (rv != 0) {
/* this should *never* happen! */
return (rv);
}
/* cleanup here */
return (rv);
}
return (0);
}
int
_fini(void)
{
int rv;
if (rv == 0) {
/* cleanup here */
}
return (rv);
}
int
{
}
static int
{
int instance;
int rv;
int version;
/*
* Only instance 0 of n2rng driver is allowed.
*/
if (instance != 0) {
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_RESUME:
instance);
return (DDI_FAILURE);
}
return (n2rng_resume(n2rng));
case DDI_ATTACH:
break;
default:
return (DDI_FAILURE);
}
if (rv != DDI_SUCCESS) {
return (DDI_FAILURE);
}
n2rng->n_timeout_id = 0;
/* Determine binding type */
strlen(N2RNG_BINDNAME_N2)) == 0) {
/*
* Niagara 2
*/
strlen(N2RNG_BINDNAME_VF)) == 0) {
/*
* Victoria Falls
*/
strlen(N2RNG_BINDNAME_KT)) == 0) {
/*
* Rainbow Falls
*/
} else {
"unable to determine n2rng (cpu) binding (%s)",
goto errorexit;
}
/* Negotiate HV api version number */
if (rv == 0)
break;
}
"%s: cannot negotiate hypervisor services "
"group: 0x%lx major: %ld minor: %ld errno: %d",
}
goto errorexit;
}
/*
* Verify that we are running version 2.0 or later api on multiple
* rng systems.
*/
}
/* Initialize ctl structure if runnning in the control domain */
"control structures");
goto errorexit;
}
/* Allocate single thread task queue for rng diags and registration */
TASKQ_DEFAULTPRI, 0);
goto errorexit;
}
/* Dispatch task to configure the RNG and register with KCF */
goto errorexit;
}
return (DDI_SUCCESS);
/* Wait for pending config tasks to complete and delete the taskq */
}
(void) n2rng_uninit(n2rng);
if (ncs_hsvc_available == B_TRUE) {
}
return (DDI_FAILURE);
}
static int
{
int instance;
int rv;
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_SUSPEND:
return (n2rng_suspend(n2rng));
case DDI_DETACH:
break;
default:
return (DDI_FAILURE);
}
/* Destroy task queue first to insure configuration has completed */
}
/* Untimeout pending config retry operations */
n2rng->n_timeout_id = 0;
if (tid) {
"id = %x", tid);
}
/* unregister with KCF---also tears down FIPS state */
if (ncs_hsvc_available == B_TRUE) {
}
return (rv);
}
/*ARGSUSED*/
static int
{
/* unregister with KCF---also tears down FIPS state */
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
{
/* Assume clock is same speed and all data structures are intact */
/* Re-configure the RNG hardware and register with KCF */
return (n2rng_config(n2rng));
}
/*
* Map hypervisor error code to solaris. Only
* H_ENORADDR, H_EBADALIGN, H_EWOULDBLOCK, and EIO
* are meaningful to this device. Any other error
* codes are mapped EINVAL.
*/
int
{
int s_errcode;
switch (hv_errcode) {
case H_EWOULDBLOCK:
break;
case H_EIO:
break;
case H_EBUSY:
break;
case H_EOK:
s_errcode = 0;
break;
case H_ENOACCESS:
break;
case H_ENORADDR:
case H_EBADALIGN:
default:
break;
}
return (s_errcode);
}
/*
* Waits approximately delay_sticks counts of the stick register.
* Times shorter than one sys clock tick (10ms on most systems) are
* done by busy waiting.
*/
void
{
/*CONSTCOND*/
while (1) {
if (sticks_to_wait <= 0) {
return;
}
if (sys_ticks_to_wait > 0) {
/* sleep */
} else if (usecs_to_wait > 0) {
/* busy wait */
}
}
}
static void
{
switch (hverr) {
case H_EBADALIGN:
"n2rng: internal alignment "
"problem");
break;
case H_ENORADDR:
"invalid address");
break;
case H_ENOACCESS:
break;
case H_EWOULDBLOCK:
break;
default:
"n2rng: %s "
"unexpectedly "
break;
}
}
/*
* Collects a buffer full of bits, using the specified setup. numbytes
* must be a multiple of 8. If a sub-operation fails with EIO (handle
* mismatch), returns EIO. If collect_setupp is NULL, the current
* setup is used. If exit_setupp is NULL, the control configuratin
* and state are not set at exit. WARNING: the buffer must be 8-byte
* aligned and in contiguous physical addresses. Contiguousness is
* not checked!
*/
int
{
int rv;
int override_rv = 0;
int i;
int numchunks;
int busycount = 0;
int blockcount = 0;
return (EINVAL);
}
return (EINVAL);
}
/*
* Use setupbuffer[0] if it is contiguous, otherwise
* setupbuffer[1].
*/
/*
* If a non-null collect_setupp pointer has been provided,
* push the specified setup into the hardware.
*/
if (collect_setupp != NULL) {
/* copy the specified state to the aligned buffer */
rnglooping = B_TRUE;
while (rnglooping) {
switch (hverr) {
case H_EOK:
break;
case H_EIO: /* control yanked from us */
case H_ENOACCESS: /* We are not control domain */
return (rv);
case H_EWOULDBLOCK:
/* Data currently not available, try again */
if (++blockcount > RNG_MAX_BLOCK_ATTEMPTS) {
"n2rng_collect_diag_bits(1) : "
"exceeded block count of %d",
return (rv);
} else {
}
break;
case H_EBUSY:
/*
* A control write is already in progress.
* Note: This shouldn't happen since
* n2rng_ctl_write() waits for the
* write to complete.
*/
if (++busycount > RNG_MAX_BUSY_ATTEMPTS) {
"n2rng_collect_diag_bits(1): "
"exceeded busy count of %d",
return (rv);
} else {
}
break;
default:
override_rv = rv;
goto restore_state;
}
} /* while (rnglooping) */
} /* if (collect_setupp != NULL) */
/* If the caller asks for some bytes, collect the data */
if (numbytes > 0) {
for (i = 0; i < numchunks; i++) {
numbytes - i * (RNG_DIAG_CHUNK_SIZE *
sizeof (uint64_t)) :
RNG_DIAG_CHUNK_SIZE * sizeof (uint64_t);
/* try until we successfully read a word of data */
rnglooping = B_TRUE;
busycount = 0;
blockcount = 0;
while (rnglooping) {
RNG_DIAG_CHUNK_SIZE * i),
thisnumbytes, &tdelta);
switch (hverr) {
case H_EOK:
break;
case H_EIO:
case H_ENOACCESS:
return (rv);
case H_EWOULDBLOCK:
/* Data not available, try again */
if (++blockcount >
"n2rng_collect_diag_bits"
"(2): exceeded block count"
" of %d",
return (rv);
} else {
}
break;
default:
"hv_rng_data_read_diag");
override_rv = rv;
goto restore_state;
}
} /* while (!rnglooping) */
} /* for */
}
/* restore the preferred configuration and set exit state */
if (exit_setupp != NULL) {
*setupcontigp = *exit_setupp;
rnglooping = B_TRUE;
busycount = 0;
blockcount = 0;
while (rnglooping) {
&tdelta);
switch (hverr) {
case H_EOK:
case H_EIO: /* control yanked from us */
case H_EINVAL: /* some external error, probably */
case H_ENOACCESS: /* We are not control domain */
break;
case H_EWOULDBLOCK:
/* Data currently not available, try again */
if (++blockcount > RNG_MAX_BLOCK_ATTEMPTS) {
"n2rng_collect_diag_bits(3): "
"exceeded block count of %d",
return (rv);
} else {
}
break;
case H_EBUSY:
/*
* A control write is already in progress.
* Note: This shouldn't happen since
* n2rng_ctl_write() waits for the
* write to complete.
*/
if (++busycount > RNG_MAX_BUSY_ATTEMPTS) {
"n2rng_collect_diag_bits(3): "
"exceeded busy count of %d",
return (rv);
} else {
}
break;
default:
break;
}
} /* while */
} /* if */
/*
* override_rv takes care of the case where we abort becuase
* of some error, but still want to restore the peferred state
* and return the first error, even if other error occur.
*/
}
int
{
int failcount = 0;
int blockcount = 0;
for (i = 0; i < num_w; i++) {
rnglooping = B_TRUE;
while (rnglooping) {
switch (hverr) {
case H_EOK:
failcount = 0;
break;
case H_EIO:
/*
* Either a health check is in progress, or
* the watchdog timer has expired while running
* hv api version 2.0 or higher with health
* checks enabled.
*/
/*
* A health check is in progress.
* Wait RNG_RETRY_HLCHK_USECS and fail
* after RNG_MAX_DATA_READ_ATTEMPTS
* failures.
*/
if (++failcount >
"n2rng_getentropy: exceeded"
"EIO count of %d on cpu %d",
goto exitpoint;
} else {
}
} else {
/*
* Just return the error. If a flurry of
* random data requests happen to occur
* during a health check, there are
* multiple levels of defense:
* - 2.0 HV provides random data pool
* - FIPS algorithm tolerates failures
* - Software failover
* - Automatic configuration retries
* - Hardware failover on some systems
*/
goto exitpoint;
}
break;
case H_EWOULDBLOCK:
/* Data currently not available, try again */
if (++blockcount > RNG_MAX_BLOCK_ATTEMPTS) {
"n2rng_getentropy: "
"exceeded block count of %d",
goto exitpoint;
} else {
}
break;
default:
goto exitpoint;
}
} /* while */
} /* for */
return (rv);
}
{
/* Call correct hv function based on api version */
if (rv == 0) {
}
} else {
*wdelta = 0;
}
return (rv);
}
{
int busycount = 0;
int blockcount = 0;
/*
* Use setupbuffer[0] if it is contiguous, otherwise
* setupbuffer[1].
*/
while (rnglooping) {
switch (rv) {
case H_EOK:
break;
case H_EWOULDBLOCK:
/* Data currently not available, try again */
if (++blockcount > RNG_MAX_BLOCK_ATTEMPTS) {
"exceeded block count of %d",
return (rv);
} else {
}
break;
case H_EBUSY:
/* Control write still pending, try again */
if (++busycount > RNG_MAX_BUSY_ATTEMPTS) {
"exceeded busy count of %d",
return (rv);
} else {
}
break;
default:
}
} /* while (rnglooping) */
return (rv);
}
{
/* Call correct hv function based on api version */
/* Wait for control registers to be written */
}
} else {
}
return (rv);
}
{
/* Call correct hv function based on api version */
if (*tdelta == 0) {
}
} else {
}
return (rv);
}
{
/* Call correct hv function based on api version */
/*
* Attempt to read control registers with invalid ID and data
* just to see if we get an access error
*/
} else {
}
return (rv);
}
/*
* n2rng_config_retry()
*
* Schedule a timed call to n2rng_config() if one is not already pending
*/
void
{
/* Check if a config retry is already pending */
if (n2rng->n_timeout_id) {
} else {
}
}
static uint64_t
sticks_per_usec(void)
{
delay(2);
}
static int
{
int rv;
int hverr;
int rngid;
int blockcount = 0;
/* Attempt to gain diagnostic control */
do {
if ((hverr == H_EWOULDBLOCK) &&
(++blockcount > RNG_MAX_BUSY_ATTEMPTS)) {
"count of %d", RNG_MAX_BUSY_ATTEMPTS);
return (rv);
} else {
}
} while (hverr == H_EWOULDBLOCK);
/*
* If attempt fails with EPERM, the driver is not running in the
* control domain
*/
"n2rng_init_ctl: Running in guest domain");
return (DDI_SUCCESS);
}
/* Allocate control stucture only used in control domain */
/*
* If running with an API version less than 2.0 default to one rng.
* Otherwise get number of rngs from device properties.
*/
} else {
N2RNG_PROP_NUM_UNITS, 0);
return (DDI_FAILURE);
}
}
/* Allocate space for all rng entries */
sizeof (rng_entry_t), KM_SLEEP);
/* Get accumulate cycles from .conf file. */
/* Get health check frequency from .conf file */
/* API versions prior to 2.0 do not support health checks */
"version %d.%d does not support health checks",
}
/* Calculate watchdog timeout value */
} else {
}
/*
* Set some plausible state into the preferred configuration.
* The intent is that the health check will immediately overwrite it.
*/
}
"n2rng_init_ctl: Running in control domain with %d rng device%s",
"n2rng_init_ctl: n_sticks_per_usec = %ld, n_hc_secs = %d",
"n2rng_init_ctl: n_watchdog_cycles = %ld, "
return (DDI_SUCCESS);
}
static void
{
if (n2rng->n_ctl_data) {
sizeof (rng_entry_t));
}
}
}
/*
* n2rng_config_test()
*
* Attempt read random data to see if the rng is configured.
*/
int
{
int rv = 0;
int failcount = 0;
int blockcount = 0;
while (rnglooping) {
switch (hverr) {
case H_EOK:
failcount = 0;
break;
case H_EIO:
/*
* A health check is in progress.
* Wait RNG_RETRY_HLCHK_USECS and fail
* after RNG_MAX_DATA_READ_ATTEMPTS
* failures.
*/
if (++failcount > RNG_MAX_DATA_READ_ATTEMPTS) {
goto exitpoint;
} else {
}
break;
case H_EWOULDBLOCK:
/* Data currently not available, try again */
if (++blockcount > RNG_MAX_BLOCK_ATTEMPTS) {
"exceeded block count of %d",
goto exitpoint;
} else {
}
break;
case H_ENOACCESS:
/* An rng error has occured during health check */
goto exitpoint;
default:
goto exitpoint;
}
} /* while */
return (rv);
}
/*
* n2rng_config()
*
* Run health check on the RNG hardware
* Configure the RNG hardware
* Register with crypto framework
*/
static int
{
int rv;
int rngid;
/*
* Run health checks and configure rngs if running in control domain,
* otherwise just check if at least one rng is available.
*/
if (n2rng_iscontrol(n2rng)) {
rngid++) {
/* Only test rngs that have not already failed */
continue;
}
/*
* Since api versions prior to 2.0 do not
* support multiple rngs, bind to the current
* processor for the entire health check
* process.
*/
"Configuring single rng from cpu %d",
} else {
}
switch (rv) {
case 0:
/*
* Successful, increment online count if
* necessary
*/
"passed health checks", rngid);
rng->n_rng_state =
}
break;
default:
/*
* Health checks failed, decrement online
* count if necessary
*/
"failed health checks", rngid);
}
break;
}
}
/* Check if all rngs have failed */
goto errorexit;
} else {
}
} else {
/* Running in guest domain, just check if rng is configured */
switch (rv) {
case 0:
break;
case EIO:
/* Don't set configured to force a retry */
break;
default:
goto errorexit;
}
}
/*
* Initialize FIPS state and register with KCF if we have at least one
* RNG configured. Otherwise schedule a retry if all rngs have not
* failed.
*/
if (n2rng_isconfigured(n2rng)) {
goto errorexit;
}
/*
* Schedule a retry if running in the control domain and a
* health check time has been specified.
*/
if (n2rng_iscontrol(n2rng) &&
}
} else if (!n2rng_isfailed(n2rng)) {
/* Schedule a retry if one is not already pending */
}
return (DDI_SUCCESS);
/* Unregister from kCF if we are registered */
(void) n2rng_unregister_provider(n2rng);
return (DDI_FAILURE);
}
/*
* n2rng_config_task()
*
* Call n2rng_config() from the task queue or after a timeout, ignore result.
*/
static void
{
n2rng->n_timeout_id = 0;
(void) n2rng_config(n2rng);
}