/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/devops.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/stat.h>
#include <sys/open.h>
#include <sys/file.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cpuvar.h>
#include <sys/disp.h>
#include <sys/hsvc.h>
#include <sys/machsystm.h>
#include <sys/param.h>
#include <sys/hypervisor_api.h>
#include <sys/n2rng.h>
/*
* There are 3 noise cells each with its own oscillator, and each
* oscillator can be set to 4 different bias setttings. The bias
* setting controls the nominal frequency of the oscillator. The 3
* and 4 and hardcoded throughout this file.
*/
#define BITS_IN(type) (8 * sizeof (type))
#define EXTRACTBIT64(val, bit) (((val) >> (bit)) & 1UL)
/*
* Policy settings
*/
/* Log2 of the number of bits */
#define SETTLECYCLES 1000000
#define NORMAL_BYPASS 1
#define NUMOSC 3
#define LOG2_DATA_WORDS 15
#define DATA_WORDS (1 << LOG2_DATA_WORDS)
#define ENTROPY_PASS_VALUE 150000000ULL
/*
* There is a hardware bug that causes the RNG_DATA register to
* occasionally be read one cycle before the specifed time.
* LOGIC_TEST_EXPECTED_M1 is the value one cycle before
* LOGIC_TEST_CYCLES. And there is a second bug that causes the read
* to be delayed. We have seen delays of about 150 cycles, but do not
* know that maximum that could possibly occur.
*
* We collect LOGIC_TEST_WORDS words using a diagnostic read with all
* entropy turned off. The first one we skip, becuase we have no
* knowledge of the time since the last read. We check that the
* remaining values fall in the window of values that should occur
* between LOGIC_TEST_CYCLES - 1 and LOGIC_TEST_CYCLES +
* LOGIC_TEST_BUG_MAX. As further protecion against false positives,
* we report success if the the number of mismatches does not exceed
* LOGIC_TEST_ERRORS_ALLOWED.
*
* When running on maramba systems, delays as high as 20000 were observed
* LOGIC_TEST_BUG_MAX was increased to twice this observed value since all
* that matters is that the hardware is indeed generating the expected values
* in diag mode. The code was also modified to exit as soon as the required
* number of matches is detected.
*/
#define LOGIC_TEST_CYCLES 38859
#define LOGIC_TEST_EXPECTED_M1 0xb8820c7bd387e32cULL
#define LOGIC_TEST_BUG_MAX 40000
#define LOGIC_TEST_WORDS 8 /* includes first one, unused */
#define LOGIC_TEST_ERRORS_ALLOWED 1
#define LOGIC_TEST_MATCHES_NEEDED (LOGIC_TEST_WORDS - 1 - \
LOGIC_TEST_ERRORS_ALLOWED)
#define RNG_POLY 0x231dcee91262b8a3ULL
#define ENTDIVISOR (((1ULL << LOG_VAL_SCALE) + 500ULL) / 1000ULL)
#define ENCODEBIAS(osc, bias) (((bias) & 0x3) << (2 * (osc)))
#define EXTRACTBIAS(blob, osc) (((blob) >> (2 * (osc))) & 0x3)
extern int n2rng_herr2kerr(uint64_t hv_errcode);
/*
* Each value is a representation of the polynomail bit_i * x^i, where
* i=0 corresponds to the least significant bit of the word. The
* modulus polynomial is x^64 + the interpretation of poly. Out is
* set to in * x^exp mod moduluspolynomial. This corresponds to
* running the LFSR exp cycles. This implemenation directly simulates
* the lfsr. It's running time is O(exp), but the constant is small.
* (This code was taken verbatim from Legion.)
*/
static void
lfsr64_adv_seq(uint64_t poly, uint64_t in, uint64_t exp, uint64_t *out)
{
int i;
uint64_t res = in;
for (i = 0; i < exp; i++) {
if (res & 0x8000000000000000ULL) {
res = (res << 1) ^ poly;
} else {
res <<= 1;
}
}
*out = res;
}
int
n2rng_logic_test(n2rng_t *n2rng, int rngid)
{
n2rng_setup_t logictest;
uint64_t buffer[LOGIC_TEST_WORDS];
uint64_t reg;
int rv;
int i, j;
int correctcount = 0;
rng_entry_t *rng = &n2rng->n_ctl_data->n_rngs[rngid];
int cycles[LOGIC_TEST_WORDS] =
{0, 0, 0, 0, 0, 0, 0, 0};
/*
* This test runs the RNG with no entropy for
* LOGIC_TEST_CYCLES cycles. Ideally the value would be be
* LOGIC_TEST_RESULT, but because of the RNG bug, the actual
* register read may be delayed by upto LOGIC_TEST_BUG_MAX
* cycles. So we simulate over that window, and a match
* occurs, we report success.
*/
logictest.ctlwds[0].word = 0;
logictest.ctlwds[0].fields.rnc_anlg_sel = N2RNG_NOANALOGOUT;
logictest.ctlwds[1] = logictest.ctlwds[0];
logictest.ctlwds[2] = logictest.ctlwds[0];
logictest.ctlwds[3] = logictest.ctlwds[0];
logictest.ctlwds[3].fields.rnc_mode = 1;
logictest.ctlwds[3].fields.rnc_cnt = LOGIC_TEST_CYCLES - 2;
/* read LOGIC_TEST_WORDS 64-bit words */
rv = n2rng_collect_diag_bits(n2rng, rngid, &logictest, buffer,
LOGIC_TEST_WORDS * sizeof (uint64_t),
&rng->n_preferred_config, rng->n_rng_state);
if (rv) {
cmn_err(CE_WARN, "n2rng: n2rng_collect_diag_bits failed with "
"0x%x on rng(%d)", rv, rngid);
return (rv);
}
reg = LOGIC_TEST_EXPECTED_M1;
for (i = 0; i <= LOGIC_TEST_BUG_MAX; i++) {
for (j = 1; j < LOGIC_TEST_WORDS; ++j) {
if (buffer[j] == reg) {
++correctcount;
cycles[j] = i;
}
}
/* exit loop if we have already found enough matches */
if (correctcount >= LOGIC_TEST_MATCHES_NEEDED) {
break;
}
/* advance reg by one step */
lfsr64_adv_seq(RNG_POLY, reg, 1, &reg);
}
if (correctcount < LOGIC_TEST_MATCHES_NEEDED) {
/*
* Don't log a warning here since the calling routine will
* retry and log it's own warning if the retry fails.
*/
DBG2(n2rng, DHEALTH, "n2rng: logic error on rng(%d), only %d "
"matches found", rngid, correctcount);
for (i = 0; i < LOGIC_TEST_WORDS; i++) {
DBG3(n2rng, DHEALTH, "buffer[%d] %016llx, cycles = %d",
i, buffer[i], cycles[i]);
}
return (EIO);
} else {
DBG3(n2rng, DHEALTH, "n2rng: rng(%d) logic test passed, "
"%d matches in %d cycles", rngid, correctcount, i);
for (i = 0; i < LOGIC_TEST_WORDS; i++) {
DBG3(n2rng, DCHATTY, "buffer[%d] %016llx, cycles = %d",
i, buffer[i], cycles[i]);
}
}
return (0);
}
/*
* gets the metric for the specified state.
*/
int
n2rng_collect_metrics(n2rng_t *n2rng, int rngid, n2rng_setup_t *setupp,
n2rng_setup_t *exit_setupp,
uint64_t exit_state, n2rng_osc_perf_t *metricp)
{
int rv;
int bufsize;
uint64_t *buffer = NULL;
bufsize = DATA_WORDS * sizeof (uint64_t);
buffer = (uint64_t *)contig_mem_alloc_align(bufsize,
CONTIG_ALIGNMENT);
if (buffer == NULL) {
return (ENOMEM);
}
rv = n2rng_collect_diag_bits(n2rng, rngid, setupp, buffer, bufsize,
exit_setupp, exit_state);
if (rv) {
cmn_err(CE_WARN,
"n2rng: n2rng_collect_bits returns 0x%x", rv);
} else {
n2rng_renyi_entropy(buffer, LOG2_DATA_WORDS, metricp);
}
contig_mem_free(buffer, bufsize);
return (rv);
}
/*
* Fills in table with the performance of each oscillator at each
* bias setting. A particular datum goes in table[osc][bias].
*/
int
collect_rng_perf(n2rng_t *n2rng, int rngid, n2rng_osc_perf_table_t ptable)
{
int bias;
int osc;
n2rng_setup_t rngstate;
int rv;
rng_entry_t *rng = &n2rng->n_ctl_data->n_rngs[rngid];
rngstate.ctlwds[0].word = 0;
rngstate.ctlwds[0].fields.rnc_anlg_sel = N2RNG_NOANALOGOUT;
rngstate.ctlwds[1] = rngstate.ctlwds[0];
rngstate.ctlwds[2] = rngstate.ctlwds[0];
rngstate.ctlwds[3] = rngstate.ctlwds[0];
for (osc = 0; osc < N2RNG_NOSC; osc++) {
rngstate.ctlwds[3].fields.rnc_selbits = 1 << osc;
for (bias = 0; bias < N2RNG_NBIASES; bias++) {
rngstate.ctlwds[3].fields.rnc_vcoctl = bias;
rv = n2rng_collect_metrics(n2rng, rngid, &rngstate,
&rng->n_preferred_config, rng->n_rng_state,
&(ptable[osc][bias]));
if (rv) {
return (rv);
}
}
}
return (rv);
}
/*
* The following 2 functions test the performance of each noise cell
* and select the bias settings. They implement the following
* policies:
*
* 1. No two cells may be set to the same bias. (Cells with the same bias,
* which controls frequency, may beat together, with long
* runs of no entropy as a pair when they are nearly synchronized.)
* 2. The entropy of each cell is determined (for now) by the Renyi H2
* entropy of a collection of samples of raw bits.
* 3. The selected configuration is the one that has the largest total
* entropy, computed as stated above.
* 4. The delay is hard coded.
*/
/*
* Finds the preferred configuration from perf data. Sets the
* preferred configuration in the n2rng structure.
*/
int
n2rng_noise_gen_preferred(n2rng_t *n2rng, int rngid)
{
int rv;
int rventropy = 0; /* EIO if entropy is too low */
int b0, b1, b2;
int osc;
int bset;
n2rng_osc_perf_t *candidates[N2RNG_NOSC];
uint64_t bestcellentropy[N2RNG_NOSC] = {0};
uint64_t bestentropy = 0;
n2rng_ctl_t rng_ctl = {0};
int i;
rng_entry_t *rng = &n2rng->n_ctl_data->n_rngs[rngid];
rv = collect_rng_perf(n2rng, rngid, rng->n_perftable);
if (rv) {
return (rv);
}
/*
* bset is the bias setting of all 3 oscillators packed into a
* word, 2 bits for each: b2:b1:b0. First we set up an
* arbitrary assignment, because in an earlier version of
* this code, there were cases where the assignment would
* never happen. Also, that way we don't need to prove
* assignment to prove we never have uninitalized variables,
* and hence it might avoid lint warnings.
*
* This block of code picks the "best" setting of the biases,
* where "best" is defined by the rules in the big comment
* block above.
*
* There are only 24 possible combinations such that no two
* oscillators get the same bias. We just do a brute force
* exhaustive search of the entire space.
*/
bset = ENCODEBIAS(2, 2) | ENCODEBIAS(1, 1) | ENCODEBIAS(0, 0);
for (b0 = 0; b0 < N2RNG_NBIASES; b0++) {
candidates[0] = &rng->n_perftable[0][b0];
for (b1 = 0; b1 < N2RNG_NBIASES; b1++) {
if (b0 == b1) continue;
candidates[1] = &rng->n_perftable[1][b1];
for (b2 = 0; b2 < N2RNG_NBIASES; b2++) {
uint64_t totalentropy = 0;
if (b0 == b2 || b1 == b2) continue;
candidates[2] = &rng->n_perftable[2][b2];
for (i = 0; i < N2RNG_NOSC; i++) {
totalentropy += candidates[i]->H2;
}
if (totalentropy > bestentropy) {
bestentropy = totalentropy;
bset = ENCODEBIAS(0, b0) |
ENCODEBIAS(1, b1) |
ENCODEBIAS(2, b2);
for (i = 0; i < N2RNG_NOSC; i++) {
bestcellentropy[i] =
candidates[i]->H2;
}
}
}
}
}
if (bestentropy < ENTROPY_PASS_VALUE) {
cmn_err(CE_WARN,
"n2rng: RNG hardware producing insufficient "
"entropy (producing %ld, need %lld)",
bestentropy, ENTROPY_PASS_VALUE);
rventropy = EIO;
}
/*
* Set up fields of control words that will be the same for all
* osciallators and for final value that selects all
* oscillators.
*/
rng_ctl.fields.rnc_cnt = n2rng->n_ctl_data->n_accumulate_cycles;
rng_ctl.fields.rnc_mode = 1; /* set normal mode */
rng_ctl.fields.rnc_anlg_sel = N2RNG_NOANALOGOUT;
/*
* Now set the oscillator biases.
*/
for (osc = 0; osc < N2RNG_NOSC; osc++) {
rng_ctl.fields.rnc_selbits = 1 << osc;
rng_ctl.fields.rnc_vcoctl = EXTRACTBIAS(bset, osc);
rng->n_preferred_config.ctlwds[osc] = rng_ctl;
}
rng_ctl.fields.rnc_cnt = n2rng->n_ctl_data->n_accumulate_cycles;
rng_ctl.fields.rnc_vcoctl = 0;
rng_ctl.fields.rnc_selbits = 0x7;
rng->n_preferred_config.ctlwds[3] = rng_ctl;
if (rventropy == 0) {
/* Save bias and entropy results for kstats */
for (i = 0; i < N2RNG_NOSC; i++) {
rng->n_bias_info[i].bias =
(uint64_t)EXTRACTBIAS(bset, i);
rng->n_bias_info[i].entropy =
(uint64_t)(bestcellentropy[i] / ENTDIVISOR);
DBG4(n2rng, DCHATTY,
"n2rng_noise_gen_preferred: rng %d cell %d bias "
"%ld: %ld", rngid, i, rng->n_bias_info[i].bias,
rng->n_bias_info[i].entropy);
}
} else {
/* Clear bias and entropy results for kstats */
for (i = 0; i < N2RNG_NOSC; i++) {
rng->n_bias_info[i].bias = 0;
rng->n_bias_info[i].entropy = 0;
}
}
return (rv ? rv : rventropy);
}
/*
* Do a logic test, then find and set the best bias confuration
* (failing if insufficient entropy is generated, then set state to
* configured. This function should only be called when running in
* the control domain.
*/
int
n2rng_do_health_check(n2rng_t *n2rng, int rngid)
{
int rv = EIO;
rng_entry_t *rng = &n2rng->n_ctl_data->n_rngs[rngid];
int attempts;
for (attempts = 0;
(attempts < RNG_MAX_LOGIC_TEST_ATTEMPTS) && rv; attempts++) {
rv = n2rng_logic_test(n2rng, rngid);
}
if (rv) {
cmn_err(CE_WARN, "n2rng: n2rng_logic_test failed %d attempts",
RNG_MAX_LOGIC_TEST_ATTEMPTS);
goto errorexit;
} else if (attempts > 1) {
DBG1(n2rng, DHEALTH,
"n2rng: n2rng_logic_test failed %d attempts",
attempts - 1);
goto errorexit;
}
rv = n2rng_noise_gen_preferred(n2rng, rngid);
if (rv) {
DBG0(n2rng, DHEALTH,
"n2rng: n2rng_noise_gen_preferred failed");
goto errorexit;
}
/* Push the selected config into HW */
rv = n2rng_collect_diag_bits(n2rng, rngid, NULL, NULL, 0,
&rng->n_preferred_config, CTL_STATE_CONFIGURED);
if (rv) {
DBG0(n2rng, DHEALTH,
"n2rng: n2rng_collect_diag_bits failed");
goto errorexit;
}
return (rv);
errorexit:
/* Push the selected config into HW with an error state */
(void) n2rng_collect_diag_bits(n2rng, rngid, NULL, NULL, 0,
&rng->n_preferred_config, CTL_STATE_ERROR);
return (rv);
}