#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>
#define BITS_IN(type) (8 * sizeof (type))
#define EXTRACTBIT64(val, bit) (((val) >> (bit)) & 1UL)
#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
#define LOGIC_TEST_CYCLES 38859
#define LOGIC_TEST_EXPECTED_M1 0xb8820c7bd387e32cULL
#define LOGIC_TEST_BUG_MAX 40000
#define LOGIC_TEST_WORDS 8
#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);
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};
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;
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;
}
}
if (correctcount >= LOGIC_TEST_MATCHES_NEEDED) {
break;
}
lfsr64_adv_seq(RNG_POLY, reg, 1, ®);
}
if (correctcount < LOGIC_TEST_MATCHES_NEEDED) {
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);
}
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);
}
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);
}
int
n2rng_noise_gen_preferred(n2rng_t *n2rng, int rngid)
{
int rv;
int rventropy = 0;
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 = 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;
}
rng_ctl.fields.rnc_cnt = n2rng->n_ctl_data->n_accumulate_cycles;
rng_ctl.fields.rnc_mode = 1;
rng_ctl.fields.rnc_anlg_sel = N2RNG_NOANALOGOUT;
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) {
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 {
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);
}
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;
}
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:
(void) n2rng_collect_diag_bits(n2rng, rngid, NULL, NULL, 0,
&rng->n_preferred_config, CTL_STATE_ERROR);
return (rv);
}