| /* |
| * QTests for the Xilinx Versal True Random Number Generator device |
| * |
| * Copyright (c) 2023 Advanced Micro Devices, Inc. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "libqtest-single.h" |
| |
| /* Base Address */ |
| #define TRNG_BASEADDR (0xf1230000) |
| |
| /* TRNG_INT_CTRL */ |
| #define R_TRNG_INT_CTRL (0x0000) |
| #define TRNG_INT_CTRL_CERTF_RST_MASK (1 << 5) |
| #define TRNG_INT_CTRL_DTF_RST_MASK (1 << 4) |
| #define TRNG_INT_CTRL_DONE_RST_MASK (1 << 3) |
| #define TRNG_INT_CTRL_CERTF_EN_MASK (1 << 2) |
| #define TRNG_INT_CTRL_DTF_EN_MASK (1 << 1) |
| #define TRNG_INT_CTRL_DONE_EN_MASK (1) |
| |
| /* TRNG_STATUS */ |
| #define R_TRNG_STATUS (0x0004) |
| #define TRNG_STATUS_QCNT_SHIFT (9) |
| #define TRNG_STATUS_QCNT_MASK (7 << TRNG_STATUS_QCNT_SHIFT) |
| #define TRNG_STATUS_CERTF_MASK (1 << 3) |
| #define TRNG_STATUS_DTF_MASK (1 << 1) |
| #define TRNG_STATUS_DONE_MASK (1) |
| |
| /* TRNG_CTRL */ |
| #define R_TRNG_CTRL (0x0008) |
| #define TRNG_CTRL_PERSODISABLE_MASK (1 << 10) |
| #define TRNG_CTRL_SINGLEGENMODE_MASK (1 << 9) |
| #define TRNG_CTRL_PRNGMODE_MASK (1 << 7) |
| #define TRNG_CTRL_TSTMODE_MASK (1 << 6) |
| #define TRNG_CTRL_PRNGSTART_MASK (1 << 5) |
| #define TRNG_CTRL_PRNGXS_MASK (1 << 3) |
| #define TRNG_CTRL_TRSSEN_MASK (1 << 2) |
| #define TRNG_CTRL_QERTUEN_MASK (1 << 1) |
| #define TRNG_CTRL_PRNGSRST_MASK (1) |
| |
| /* TRNG_EXT_SEED_0 ... _11 */ |
| #define R_TRNG_EXT_SEED_0 (0x0040) |
| #define R_TRNG_EXT_SEED_11 (R_TRNG_EXT_SEED_0 + 4 * 11) |
| |
| /* TRNG_PER_STRNG_0 ... 11 */ |
| #define R_TRNG_PER_STRNG_0 (0x0080) |
| #define R_TRNG_PER_STRNG_11 (R_TRNG_PER_STRNG_0 + 4 * 11) |
| |
| /* TRNG_CORE_OUTPUT */ |
| #define R_TRNG_CORE_OUTPUT (0x00c0) |
| |
| /* TRNG_RESET */ |
| #define R_TRNG_RESET (0x00d0) |
| #define TRNG_RESET_VAL_MASK (1) |
| |
| /* TRNG_OSC_EN */ |
| #define R_TRNG_OSC_EN (0x00d4) |
| #define TRNG_OSC_EN_VAL_MASK (1) |
| |
| /* TRNG_TRNG_ISR, _IMR, _IER, _IDR */ |
| #define R_TRNG_ISR (0x00e0) |
| #define R_TRNG_IMR (0x00e4) |
| #define R_TRNG_IER (0x00e8) |
| #define R_TRNG_IDR (0x00ec) |
| #define TRNG_IRQ_SLVERR_MASK (1 << 1) |
| #define TRNG_IRQ_CORE_INT_MASK (1) |
| |
| /* |
| * End test with a formatted error message, by embedding the message |
| * in a GError. |
| */ |
| #define TRNG_FAILED(FMT, ...) \ |
| do { \ |
| g_autoptr(GError) err = g_error_new( \ |
| g_quark_from_static_string(trng_qname), 0, \ |
| FMT, ## __VA_ARGS__); \ |
| g_assert_no_error(err); \ |
| } while (0) |
| |
| static const gchar trng_qname[] = "xlnx-versal-trng-test"; |
| |
| static const uint32_t prng_seed[12] = { |
| 0x01234567, 0x12345678, 0x23456789, 0x3456789a, 0x456789ab, 0x56789abc, |
| 0x76543210, 0x87654321, 0x98765432, 0xa9876543, 0xba987654, 0xfedcba98, |
| }; |
| |
| static const uint32_t pers_str[12] = { |
| 0x76543210, 0x87654321, 0x98765432, 0xa9876543, 0xba987654, 0xfedcba98, |
| 0x01234567, 0x12345678, 0x23456789, 0x3456789a, 0x456789ab, 0x56789abc, |
| }; |
| |
| static void trng_test_start(void) |
| { |
| qtest_start("-machine xlnx-versal-virt"); |
| } |
| |
| static void trng_test_stop(void) |
| { |
| qtest_end(); |
| } |
| |
| static void trng_test_set_uint_prop(const char *name, uint64_t value) |
| { |
| const char *path = "/machine/xlnx-versal/trng"; |
| QDict *response; |
| |
| response = qmp("{ 'execute': 'qom-set'," |
| " 'arguments': {" |
| " 'path': %s," |
| " 'property': %s," |
| " 'value': %llu" |
| "} }", path, |
| name, (unsigned long long)value); |
| g_assert(qdict_haskey(response, "return")); |
| qobject_unref(response); |
| } |
| |
| static void trng_write(unsigned ra, uint32_t val) |
| { |
| writel(TRNG_BASEADDR + ra, val); |
| } |
| |
| static uint32_t trng_read(unsigned ra) |
| { |
| return readl(TRNG_BASEADDR + ra); |
| } |
| |
| static void trng_bit_set(unsigned ra, uint32_t bits) |
| { |
| trng_write(ra, (trng_read(ra) | bits)); |
| } |
| |
| static void trng_bit_clr(unsigned ra, uint32_t bits) |
| { |
| trng_write(ra, (trng_read(ra) & ~bits)); |
| } |
| |
| static void trng_ctrl_set(uint32_t bits) |
| { |
| trng_bit_set(R_TRNG_CTRL, bits); |
| } |
| |
| static void trng_ctrl_clr(uint32_t bits) |
| { |
| trng_bit_clr(R_TRNG_CTRL, bits); |
| } |
| |
| static uint32_t trng_status(void) |
| { |
| return trng_read(R_TRNG_STATUS); |
| } |
| |
| static unsigned trng_qcnt(void) |
| { |
| uint32_t sta = trng_status(); |
| |
| return (sta & TRNG_STATUS_QCNT_MASK) >> TRNG_STATUS_QCNT_SHIFT; |
| } |
| |
| static const char *trng_info(void) |
| { |
| uint32_t sta = trng_status(); |
| uint32_t ctl = trng_read(R_TRNG_CTRL); |
| |
| static char info[64]; |
| |
| snprintf(info, sizeof(info), "; status=0x%x, ctrl=0x%x", sta, ctl); |
| return info; |
| } |
| |
| static void trng_check_status(uint32_t status_mask, const char *act) |
| { |
| uint32_t clear_mask = 0; |
| uint32_t status; |
| |
| /* |
| * Only selected bits are events in R_TRNG_STATUS, and |
| * clear them needs to go through R_INT_CTRL. |
| */ |
| if (status_mask & TRNG_STATUS_CERTF_MASK) { |
| clear_mask |= TRNG_INT_CTRL_CERTF_RST_MASK; |
| } |
| if (status_mask & TRNG_STATUS_DTF_MASK) { |
| clear_mask |= TRNG_INT_CTRL_DTF_RST_MASK; |
| } |
| if (status_mask & TRNG_STATUS_DONE_MASK) { |
| clear_mask |= TRNG_INT_CTRL_DONE_RST_MASK; |
| } |
| |
| status = trng_status(); |
| if ((status & status_mask) != status_mask) { |
| TRNG_FAILED("%s: Status bitmask 0x%x failed to be 1%s", |
| act, status_mask, trng_info()); |
| } |
| |
| /* Remove event */ |
| trng_bit_set(R_TRNG_INT_CTRL, clear_mask); |
| |
| if (!!(trng_read(R_TRNG_STATUS) & status_mask)) { |
| TRNG_FAILED("%s: Event 0x%0x stuck at 1 after clear: %s", |
| act, status_mask, trng_info()); |
| } |
| } |
| |
| static void trng_check_done_status(const char *act) |
| { |
| trng_check_status(TRNG_STATUS_DONE_MASK, act); |
| } |
| |
| static void trng_check_dtf_status(void) |
| { |
| trng_check_status(TRNG_STATUS_DTF_MASK, "DTF injection"); |
| } |
| |
| static void trng_check_certf_status(void) |
| { |
| trng_check_status(TRNG_STATUS_CERTF_MASK, "CERTF injection"); |
| } |
| |
| static void trng_reset(void) |
| { |
| trng_write(R_TRNG_RESET, TRNG_RESET_VAL_MASK); |
| trng_write(R_TRNG_RESET, 0); |
| } |
| |
| static void trng_load(unsigned r0, const uint32_t *b384) |
| { |
| static const uint32_t zero[12] = { 0 }; |
| unsigned k; |
| |
| if (!b384) { |
| b384 = zero; |
| } |
| |
| for (k = 0; k < 12; k++) { |
| trng_write(r0 + 4 * k, b384[k]); |
| } |
| } |
| |
| static void trng_reseed(const uint32_t *seed) |
| { |
| const char *act; |
| uint32_t ctl; |
| |
| ctl = TRNG_CTRL_PRNGSTART_MASK | |
| TRNG_CTRL_PRNGXS_MASK | |
| TRNG_CTRL_TRSSEN_MASK; |
| |
| trng_ctrl_clr(ctl | TRNG_CTRL_PRNGMODE_MASK); |
| |
| if (seed) { |
| trng_load(R_TRNG_EXT_SEED_0, seed); |
| act = "Reseed PRNG"; |
| ctl &= ~TRNG_CTRL_TRSSEN_MASK; |
| } else { |
| trng_write(R_TRNG_OSC_EN, TRNG_OSC_EN_VAL_MASK); |
| act = "Reseed TRNG"; |
| ctl &= ~TRNG_CTRL_PRNGXS_MASK; |
| } |
| |
| trng_ctrl_set(ctl); |
| trng_check_done_status(act); |
| trng_ctrl_clr(TRNG_CTRL_PRNGSTART_MASK); |
| } |
| |
| static void trng_generate(bool auto_enb) |
| { |
| uint32_t ctl; |
| |
| ctl = TRNG_CTRL_PRNGSTART_MASK | TRNG_CTRL_SINGLEGENMODE_MASK; |
| trng_ctrl_clr(ctl); |
| |
| if (auto_enb) { |
| ctl &= ~TRNG_CTRL_SINGLEGENMODE_MASK; |
| } |
| |
| trng_ctrl_set(ctl | TRNG_CTRL_PRNGMODE_MASK); |
| |
| trng_check_done_status("Generate"); |
| g_assert(trng_qcnt() != 7); |
| } |
| |
| static size_t trng_collect(uint32_t *rnd, size_t cnt) |
| { |
| size_t i; |
| |
| for (i = 0; i < cnt; i++) { |
| if (trng_qcnt() == 0) { |
| return i; |
| } |
| |
| rnd[i] = trng_read(R_TRNG_CORE_OUTPUT); |
| } |
| |
| return i; |
| } |
| |
| static void trng_test_autogen(void) |
| { |
| const size_t cnt = 512 / 32; |
| uint32_t rng[cnt], prng[cnt]; |
| size_t n; |
| |
| trng_reset(); |
| |
| /* PRNG run #1 */ |
| trng_reseed(prng_seed); |
| trng_generate(true); |
| |
| n = trng_collect(prng, cnt); |
| if (n != cnt) { |
| TRNG_FAILED("PRNG_1 Auto-gen test failed: expected = %u, got = %u", |
| (unsigned)cnt, (unsigned)n); |
| } |
| |
| /* TRNG, should not match PRNG */ |
| trng_reseed(NULL); |
| trng_generate(true); |
| |
| n = trng_collect(rng, cnt); |
| if (n != cnt) { |
| TRNG_FAILED("TRNG Auto-gen test failed: expected = %u, got = %u", |
| (unsigned)cnt, (unsigned)n); |
| } |
| |
| /* PRNG #2: should matches run #1 */ |
| trng_reseed(prng_seed); |
| trng_generate(true); |
| |
| n = trng_collect(rng, cnt); |
| if (n != cnt) { |
| TRNG_FAILED("PRNG_2 Auto-gen test failed: expected = %u, got = %u", |
| (unsigned)cnt, (unsigned)n); |
| } |
| |
| if (memcmp(rng, prng, sizeof(rng))) { |
| TRNG_FAILED("PRNG_2 Auto-gen test failed: does not match PRNG_1"); |
| } |
| } |
| |
| static void trng_test_oneshot(void) |
| { |
| const size_t cnt = 512 / 32; |
| uint32_t rng[cnt]; |
| size_t n; |
| |
| trng_reset(); |
| |
| /* PRNG run #1 */ |
| trng_reseed(prng_seed); |
| trng_generate(false); |
| |
| n = trng_collect(rng, cnt); |
| if (n == cnt) { |
| TRNG_FAILED("PRNG_1 One-shot gen test failed"); |
| } |
| |
| /* TRNG, should not match PRNG */ |
| trng_reseed(NULL); |
| trng_generate(false); |
| |
| n = trng_collect(rng, cnt); |
| if (n == cnt) { |
| TRNG_FAILED("TRNG One-shot test failed"); |
| } |
| } |
| |
| static void trng_test_per_str(void) |
| { |
| const size_t cnt = 512 / 32; |
| uint32_t rng[cnt], prng[cnt]; |
| size_t n; |
| |
| trng_reset(); |
| |
| /* #1: disabled */ |
| trng_ctrl_set(TRNG_CTRL_PERSODISABLE_MASK); |
| trng_reseed(prng_seed); |
| trng_ctrl_clr(TRNG_CTRL_PERSODISABLE_MASK); |
| |
| trng_generate(true); |
| n = trng_collect(prng, cnt); |
| g_assert_cmpuint(n, ==, cnt); |
| |
| /* #2: zero string should match personalization disabled */ |
| trng_load(R_TRNG_PER_STRNG_0, NULL); |
| trng_reseed(prng_seed); |
| |
| trng_generate(true); |
| n = trng_collect(rng, cnt); |
| g_assert_cmpuint(n, ==, cnt); |
| |
| if (memcmp(rng, prng, sizeof(rng))) { |
| TRNG_FAILED("Failed: PER_DISABLE != PER_STRNG_ALL_ZERO"); |
| } |
| |
| /* #3: non-zero string should not match personalization disabled */ |
| trng_load(R_TRNG_PER_STRNG_0, pers_str); |
| trng_reseed(prng_seed); |
| |
| trng_generate(true); |
| n = trng_collect(rng, cnt); |
| g_assert_cmpuint(n, ==, cnt); |
| |
| if (!memcmp(rng, prng, sizeof(rng))) { |
| TRNG_FAILED("Failed: PER_DISABLE == PER_STRNG_NON_ZERO"); |
| } |
| } |
| |
| static void trng_test_forced_prng(void) |
| { |
| const char *prop = "forced-prng"; |
| const uint64_t seed = 0xdeadbeefbad1bad0ULL; |
| |
| const size_t cnt = 512 / 32; |
| uint32_t rng[cnt], prng[cnt]; |
| size_t n; |
| |
| trng_reset(); |
| trng_test_set_uint_prop(prop, seed); |
| |
| /* TRNG run #1 */ |
| trng_reset(); |
| trng_reseed(NULL); |
| trng_generate(true); |
| |
| n = trng_collect(prng, cnt); |
| g_assert_cmpuint(n, ==, cnt); |
| |
| /* TRNG run #2 should match run #1 */ |
| trng_reset(); |
| trng_reseed(NULL); |
| trng_generate(true); |
| |
| n = trng_collect(rng, cnt); |
| g_assert_cmpuint(n, ==, cnt); |
| |
| if (memcmp(rng, prng, sizeof(rng))) { |
| TRNG_FAILED("Forced-prng test failed: results do not match"); |
| } |
| } |
| |
| static void trng_test_fault_events(void) |
| { |
| const char *prop = "fips-fault-events"; |
| |
| trng_reset(); |
| |
| /* Fault events only when TRSS is enabled */ |
| trng_write(R_TRNG_OSC_EN, TRNG_OSC_EN_VAL_MASK); |
| trng_ctrl_set(TRNG_CTRL_TRSSEN_MASK); |
| |
| trng_test_set_uint_prop(prop, TRNG_STATUS_CERTF_MASK); |
| trng_check_certf_status(); |
| |
| trng_test_set_uint_prop(prop, TRNG_STATUS_DTF_MASK); |
| trng_check_dtf_status(); |
| |
| trng_reset(); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int rc; |
| |
| g_test_init(&argc, &argv, NULL); |
| |
| #define TRNG_TEST_ADD(n) \ |
| qtest_add_func("/hw/misc/xlnx-versal-trng/" #n, trng_test_ ## n); |
| TRNG_TEST_ADD(autogen); |
| TRNG_TEST_ADD(oneshot); |
| TRNG_TEST_ADD(per_str); |
| TRNG_TEST_ADD(forced_prng); |
| TRNG_TEST_ADD(fault_events); |
| #undef TRNG_TEST_ADD |
| |
| trng_test_start(); |
| rc = g_test_run(); |
| trng_test_stop(); |
| |
| return rc; |
| } |