diff options
author | Havard Skinnemoen <hskinnemoen@google.com> | 2020-10-23 14:06:35 -0700 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2020-10-27 11:10:10 +0000 |
commit | 326ccfe240ca9ef4f659a241b39390fa956e999b (patch) | |
tree | ae846cdf53e92e1d0d629d1f5a5e7a230effda7b /tests/qtest | |
parent | 7d378ed6e3b4a26f4da887fcccc4c6f1db3dcd42 (diff) | |
download | qemu-326ccfe240ca9ef4f659a241b39390fa956e999b.zip |
hw/misc: Add npcm7xx random number generator
The RNG module returns a byte of randomness when the Data Valid bit is
set.
This implementation ignores the prescaler setting, and loads a new value
into RNGD every time RNGCS is read while the RNG is enabled and random
data is available.
A qtest featuring some simple randomness tests is included.
Reviewed-by: Tyrone Ting <kfting@nuvoton.com>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Havard Skinnemoen <hskinnemoen@google.com>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'tests/qtest')
-rw-r--r-- | tests/qtest/meson.build | 5 | ||||
-rw-r--r-- | tests/qtest/npcm7xx_rng-test.c | 278 |
2 files changed, 282 insertions, 1 deletions
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 6c9d3426d1..43e71a3d5b 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -133,7 +133,10 @@ qtests_sparc64 = \ (config_all_devices.has_key('CONFIG_ISA_TESTDEV') ? ['endianness-test'] : []) + \ ['prom-env-test', 'boot-serial-test'] -qtests_npcm7xx = ['npcm7xx_timer-test', 'npcm7xx_watchdog_timer-test'] +qtests_npcm7xx = \ + ['npcm7xx_rng-test', + 'npcm7xx_timer-test', + 'npcm7xx_watchdog_timer-test'] qtests_arm = \ (config_all_devices.has_key('CONFIG_PFLASH_CFI02') ? ['pflash-cfi02-test'] : []) + \ (config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \ diff --git a/tests/qtest/npcm7xx_rng-test.c b/tests/qtest/npcm7xx_rng-test.c new file mode 100644 index 0000000000..da6e639bf6 --- /dev/null +++ b/tests/qtest/npcm7xx_rng-test.c @@ -0,0 +1,278 @@ +/* + * QTest testcase for the Nuvoton NPCM7xx Random Number Generator + * + * Copyright 2020 Google LLC + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "qemu/osdep.h" + +#include <math.h> + +#include "libqtest-single.h" +#include "qemu/bitops.h" + +#define RNG_BASE_ADDR 0xf000b000 + +/* Control and Status Register */ +#define RNGCS 0x00 +# define DVALID BIT(1) /* Data Valid */ +# define RNGE BIT(0) /* RNG Enable */ +/* Data Register */ +#define RNGD 0x04 +/* Mode Register */ +#define RNGMODE 0x08 +# define ROSEL_NORMAL (2) /* RNG only works in this mode */ + +/* Number of bits to collect for randomness tests. */ +#define TEST_INPUT_BITS (128) + +static void rng_writeb(unsigned int offset, uint8_t value) +{ + writeb(RNG_BASE_ADDR + offset, value); +} + +static uint8_t rng_readb(unsigned int offset) +{ + return readb(RNG_BASE_ADDR + offset); +} + +/* Disable RNG and set normal ring oscillator mode. */ +static void rng_reset(void) +{ + rng_writeb(RNGCS, 0); + rng_writeb(RNGMODE, ROSEL_NORMAL); +} + +/* Reset RNG and then enable it. */ +static void rng_reset_enable(void) +{ + rng_reset(); + rng_writeb(RNGCS, RNGE); +} + +/* Wait until Data Valid bit is set. */ +static bool rng_wait_ready(void) +{ + /* qemu_guest_getrandom may fail. Assume it won't fail 10 times in a row. */ + int retries = 10; + + while (retries-- > 0) { + if (rng_readb(RNGCS) & DVALID) { + return true; + } + } + + return false; +} + +/* + * Perform a frequency (monobit) test, as defined by NIST SP 800-22, on the + * sequence in buf and return the P-value. This represents the probability of a + * truly random sequence having the same proportion of zeros and ones as the + * sequence in buf. + * + * An RNG which always returns 0x00 or 0xff, or has some bits stuck at 0 or 1, + * will fail this test. However, an RNG which always returns 0x55, 0xf0 or some + * other value with an equal number of zeroes and ones will pass. + */ +static double calc_monobit_p(const uint8_t *buf, unsigned int len) +{ + unsigned int i; + double s_obs; + int sn = 0; + + for (i = 0; i < len; i++) { + /* + * Each 1 counts as 1, each 0 counts as -1. + * s = cp - (8 - cp) = 2 * cp - 8 + */ + sn += 2 * ctpop8(buf[i]) - 8; + } + + s_obs = abs(sn) / sqrt(len * BITS_PER_BYTE); + + return erfc(s_obs / sqrt(2)); +} + +/* + * Perform a runs test, as defined by NIST SP 800-22, and return the P-value. + * This represents the probability of a truly random sequence having the same + * number of runs (i.e. uninterrupted sequences of identical bits) as the + * sequence in buf. + */ +static double calc_runs_p(const unsigned long *buf, unsigned int nr_bits) +{ + unsigned int j; + unsigned int k; + int nr_ones = 0; + int vn_obs = 0; + double pi; + + g_assert(nr_bits % BITS_PER_LONG == 0); + + for (j = 0; j < nr_bits / BITS_PER_LONG; j++) { + nr_ones += __builtin_popcountl(buf[j]); + } + pi = (double)nr_ones / nr_bits; + + for (k = 0; k < nr_bits - 1; k++) { + vn_obs += !(test_bit(k, buf) ^ test_bit(k + 1, buf)); + } + vn_obs += 1; + + return erfc(fabs(vn_obs - 2 * nr_bits * pi * (1.0 - pi)) + / (2 * sqrt(2 * nr_bits) * pi * (1.0 - pi))); +} + +/* + * Verifies that DVALID is clear, and RNGD reads zero, when RNGE is cleared, + * and DVALID eventually becomes set when RNGE is set. + */ +static void test_enable_disable(void) +{ + /* Disable: DVALID should not be set, and RNGD should read zero */ + rng_reset(); + g_assert_cmphex(rng_readb(RNGCS), ==, 0); + g_assert_cmphex(rng_readb(RNGD), ==, 0); + + /* Enable: DVALID should be set, but we can't make assumptions about RNGD */ + rng_writeb(RNGCS, RNGE); + g_assert_true(rng_wait_ready()); + g_assert_cmphex(rng_readb(RNGCS), ==, DVALID | RNGE); + + /* Disable: DVALID should not be set, and RNGD should read zero */ + rng_writeb(RNGCS, 0); + g_assert_cmphex(rng_readb(RNGCS), ==, 0); + g_assert_cmphex(rng_readb(RNGD), ==, 0); +} + +/* + * Verifies that the RNG only produces data when RNGMODE is set to 'normal' + * ring oscillator mode. + */ +static void test_rosel(void) +{ + rng_reset_enable(); + g_assert_true(rng_wait_ready()); + rng_writeb(RNGMODE, 0); + g_assert_false(rng_wait_ready()); + rng_writeb(RNGMODE, ROSEL_NORMAL); + g_assert_true(rng_wait_ready()); + rng_writeb(RNGMODE, 0); + g_assert_false(rng_wait_ready()); +} + +/* + * Verifies that a continuous sequence of bits collected after enabling the RNG + * satisfies a monobit test. + */ +static void test_continuous_monobit(void) +{ + uint8_t buf[TEST_INPUT_BITS / BITS_PER_BYTE]; + unsigned int i; + + rng_reset_enable(); + for (i = 0; i < sizeof(buf); i++) { + g_assert_true(rng_wait_ready()); + buf[i] = rng_readb(RNGD); + } + + g_assert_cmpfloat(calc_monobit_p(buf, sizeof(buf)), >, 0.01); +} + +/* + * Verifies that a continuous sequence of bits collected after enabling the RNG + * satisfies a runs test. + */ +static void test_continuous_runs(void) +{ + union { + unsigned long l[TEST_INPUT_BITS / BITS_PER_LONG]; + uint8_t c[TEST_INPUT_BITS / BITS_PER_BYTE]; + } buf; + unsigned int i; + + rng_reset_enable(); + for (i = 0; i < sizeof(buf); i++) { + g_assert_true(rng_wait_ready()); + buf.c[i] = rng_readb(RNGD); + } + + g_assert_cmpfloat(calc_runs_p(buf.l, sizeof(buf) * BITS_PER_BYTE), >, 0.01); +} + +/* + * Verifies that the first data byte collected after enabling the RNG satisfies + * a monobit test. + */ +static void test_first_byte_monobit(void) +{ + /* Enable, collect one byte, disable. Repeat until we have 100 bits. */ + uint8_t buf[TEST_INPUT_BITS / BITS_PER_BYTE]; + unsigned int i; + + rng_reset(); + for (i = 0; i < sizeof(buf); i++) { + rng_writeb(RNGCS, RNGE); + g_assert_true(rng_wait_ready()); + buf[i] = rng_readb(RNGD); + rng_writeb(RNGCS, 0); + } + + g_assert_cmpfloat(calc_monobit_p(buf, sizeof(buf)), >, 0.01); +} + +/* + * Verifies that the first data byte collected after enabling the RNG satisfies + * a runs test. + */ +static void test_first_byte_runs(void) +{ + /* Enable, collect one byte, disable. Repeat until we have 100 bits. */ + union { + unsigned long l[TEST_INPUT_BITS / BITS_PER_LONG]; + uint8_t c[TEST_INPUT_BITS / BITS_PER_BYTE]; + } buf; + unsigned int i; + + rng_reset(); + for (i = 0; i < sizeof(buf); i++) { + rng_writeb(RNGCS, RNGE); + g_assert_true(rng_wait_ready()); + buf.c[i] = rng_readb(RNGD); + rng_writeb(RNGCS, 0); + } + + g_assert_cmpfloat(calc_runs_p(buf.l, sizeof(buf) * BITS_PER_BYTE), >, 0.01); +} + +int main(int argc, char **argv) +{ + int ret; + + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + qtest_add_func("npcm7xx_rng/enable_disable", test_enable_disable); + qtest_add_func("npcm7xx_rng/rosel", test_rosel); + qtest_add_func("npcm7xx_rng/continuous/monobit", test_continuous_monobit); + qtest_add_func("npcm7xx_rng/continuous/runs", test_continuous_runs); + qtest_add_func("npcm7xx_rng/first_byte/monobit", test_first_byte_monobit); + qtest_add_func("npcm7xx_rng/first_byte/runs", test_first_byte_runs); + + qtest_start("-machine npcm750-evb"); + ret = g_test_run(); + qtest_end(); + + return ret; +} |