| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/arch/device.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_otbn.h" |
| #include "sw/device/lib/runtime/hart.h" |
| #include "sw/device/lib/runtime/ibex.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/check.h" |
| #include "sw/device/lib/testing/test_main.h" |
| |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| |
| /** |
| * Information about an embedded OTBN application image. |
| * |
| * All pointers reference data in the normal CPU address space. |
| */ |
| typedef struct otbn_app { |
| /** |
| * Start of OTBN instruction memory. |
| */ |
| const char *imem_start; |
| /** |
| * End of OTBN instruction memory. |
| */ |
| const char *imem_end; |
| /** |
| * Start of OTBN data memory. |
| */ |
| const char *dmem_start; |
| /** |
| * End of OTBN data memory. |
| */ |
| const char *dmem_end; |
| } otbn_app_t; |
| |
| /** |
| * A function (entry point) in an OTBN application. |
| */ |
| typedef struct otbn_func { |
| /** |
| * Application associated with this function. |
| */ |
| const otbn_app_t *app; |
| /** |
| * Address of the function entry point in normal CPU address space. |
| */ |
| const char *func; |
| } otbn_func_t; |
| |
| // Prepare info structs representing the barrett384 and err_test applications, |
| // and the wrap_barrett384() and wrap_err_test functions. The goal of these |
| // structs is to make the symbols for the application image available in a form |
| // that is easier to use. |
| // |
| // The symbols are generated by the linker script (the start/end symbols for |
| // instruction and data memories), as well as by the assembly sources (all |
| // global symbols, see `sw/otbn/code-snippets/barrett384.s`). |
| // Symbols are then prefixed with "_otbn_app_APPNAME_" by objcopy, as called |
| // from `util/otbn_build.py`. |
| // |
| // TODO: Think about macro-ifying or auto-generating some of this code in a |
| // future OTBN driver. |
| extern const char _otbn_app_barrett384__imem_start[]; |
| extern const char _otbn_app_barrett384__imem_end[]; |
| extern const char _otbn_app_barrett384__dmem_start[]; |
| extern const char _otbn_app_barrett384__dmem_end[]; |
| extern const char _otbn_app_barrett384_wrap_barrett384[]; |
| |
| static const otbn_app_t kOtbnAppBarrett384 = { |
| .imem_start = _otbn_app_barrett384__imem_start, |
| .imem_end = _otbn_app_barrett384__imem_end, |
| .dmem_start = _otbn_app_barrett384__dmem_start, |
| .dmem_end = _otbn_app_barrett384__dmem_end, |
| }; |
| |
| static const otbn_func_t kOtbnFuncBarrett384WrapBarrett384 = { |
| .app = &kOtbnAppBarrett384, |
| .func = _otbn_app_barrett384_wrap_barrett384, |
| }; |
| |
| extern const char _otbn_app_err_test__imem_start[]; |
| extern const char _otbn_app_err_test__imem_end[]; |
| extern const char _otbn_app_err_test__dmem_start[]; |
| extern const char _otbn_app_err_test__dmem_end[]; |
| extern const char _otbn_app_err_test_err_test[]; |
| |
| static const otbn_app_t kOtbnAppErrTest = { |
| .imem_start = _otbn_app_err_test__imem_start, |
| .imem_end = _otbn_app_err_test__imem_end, |
| .dmem_start = _otbn_app_err_test__dmem_start, |
| .dmem_end = _otbn_app_err_test__dmem_end, |
| }; |
| |
| static const otbn_func_t kOtbnFuncErrTestWrapErrTest = { |
| .app = &kOtbnAppErrTest, |
| .func = _otbn_app_err_test_err_test, |
| }; |
| |
| const test_config_t kTestConfig = { |
| .can_clobber_uart = false, |
| }; |
| |
| static dif_otbn_t otbn; |
| |
| /* |
| // Dump OTBN's memory. A development helper, unused otherwise. |
| static void otbn_dump_dmem() { |
| uint32_t data[8]; |
| for (int i = 0; i < dif_otbn_get_dmem_size_bytes(&otbn) / 32; ++i) { |
| dif_otbn_dmem_read(&otbn, i * 32, data, 32); |
| LOG_INFO("DMEM @%04d: 0x%08x%08x%08x%08x%08x%08x%08x%08x", i, data[7], |
| data[6], data[5], data[4], data[3], data[2], data[1], data[0]); |
| } |
| } |
| */ |
| |
| /** |
| * Load an application into OTBN |
| * |
| * Load the text and data segments into the instruction and data memories, |
| * respectively. |
| * |
| * @param app OTBN application to load |
| */ |
| static void load_app(const otbn_app_t *app) { |
| CHECK(app->imem_end >= app->imem_start); |
| const size_t imem_size = app->imem_end - app->imem_start; |
| |
| CHECK(app->dmem_end >= app->dmem_start); |
| const size_t dmem_size = app->dmem_end - app->dmem_start; |
| |
| LOG_INFO( |
| "Loading OTBN instruction memory image stored between address 0x%x and " |
| "%p (%d bytes)", |
| app->imem_start, app->imem_end, imem_size); |
| CHECK(imem_size > 0); |
| CHECK(imem_size % 4 == 0); |
| CHECK(dif_otbn_imem_write(&otbn, 0, app->imem_start, imem_size) == kDifOtbnOk, |
| "Unable to write IMEM application image (.text) to OTBN."); |
| |
| if (dmem_size > 0) { |
| LOG_INFO( |
| "Loading OTBN data memory image stored between address %p and %p (%d " |
| "bytes)", |
| app->dmem_start, app->dmem_end, dmem_size); |
| CHECK( |
| dif_otbn_dmem_write(&otbn, 0, app->dmem_start, dmem_size) == kDifOtbnOk, |
| "Unable to write DMEM application image (.data) to OTBN."); |
| } else { |
| LOG_INFO("No OTBN data memory image to load."); |
| } |
| } |
| |
| /** |
| * Call a function on OTBN |
| * |
| * Set the entry point (start address) of OTBN to the desired function, and |
| * starts the OTBN operation. |
| * |
| * @param func the function to be called |
| */ |
| static void call_function(const otbn_func_t *func) { |
| uint32_t start_address = func->func - func->app->imem_start; |
| LOG_INFO("Calling function at address 0x%x on OTBN.", start_address); |
| CHECK(dif_otbn_start(&otbn, start_address) == kDifOtbnOk); |
| } |
| |
| /** |
| * Busy wait for OTBN to be done with its operation. |
| */ |
| static void otbn_wait_for_done(void) { |
| bool busy = true; |
| while (busy) { |
| CHECK(dif_otbn_is_busy(&otbn, &busy) == kDifOtbnOk, |
| "Unable to get busy status from OTBN"); |
| } |
| } |
| |
| /** |
| * Initialize OTBN's data memory with zeros |
| */ |
| static void zero_dmem(void) { |
| int dmem_size_words = dif_otbn_get_dmem_size_bytes(&otbn) / sizeof(uint32_t); |
| for (int i = 0; i < dmem_size_words; ++i) { |
| const uint32_t zero = 0; |
| dif_otbn_result_t rv = |
| dif_otbn_dmem_write(&otbn, i * sizeof(uint32_t), &zero, sizeof(zero)); |
| CHECK(rv == kDifOtbnOk, "Error zeroing word %d in OTBN DMEM: %d", i, rv); |
| } |
| } |
| |
| /** |
| * Get OTBN error code, check this succeeds and code matches `expected_err_code` |
| */ |
| static void check_otbn_err_code(dif_otbn_err_code_t expected_err_code) { |
| LOG_INFO("Checking error code"); |
| dif_otbn_err_code_t otbn_err_code; |
| dif_otbn_result_t rv = dif_otbn_get_err_code(&otbn, &otbn_err_code); |
| CHECK(rv == kDifOtbnOk, "dif_otbn_get_err_code() failed: %d", rv); |
| CHECK(otbn_err_code == expected_err_code, |
| "dif_otbn_get_err_code() produced unexpected error code: %d", |
| otbn_err_code); |
| } |
| |
| /** |
| * Run a 384-bit Barrett Multiplication on OTBN and check its result. |
| * |
| * This test is not aiming to exhaustively test the Barrett multiplication |
| * itself, but test the interaction between device software and OTBN. As such, |
| * only trivial parameters are used. |
| * |
| * The code executed on OTBN can be found in sw/otbn/code-snippets/barrett384.s. |
| * The entry point wrap_barrett384() is called according to the calling |
| * convention described in the OTBN assembly code file. |
| */ |
| static void test_barrett384(void) { |
| enum { kDataSizeBytes = 48 }; |
| |
| zero_dmem(); |
| |
| load_app(&kOtbnAppBarrett384); |
| |
| // a, first operand |
| static const uint8_t a[kDataSizeBytes] = {10}; |
| |
| // b, second operand |
| static uint8_t b[kDataSizeBytes] = {20}; |
| |
| // m, modulus, max. length 384 bit with 2^384 > m > 2^383 |
| // We choose the modulus of P-384: m = 2**384 - 2**128 - 2**96 + 2**32 - 1 |
| static const uint8_t m[kDataSizeBytes] = { |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff}; |
| |
| // u, pre-computed Barrett constant (without u[384]/MSb of u which is always 1 |
| // for the allowed range but has to be set to 0 here). |
| // u has to be pre-calculated as u = floor(2^768/m). |
| static const uint8_t u[kDataSizeBytes] = { |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01}; |
| |
| // c, result, max. length 384 bit. |
| uint8_t c[kDataSizeBytes] = {0}; |
| |
| // c = (a * b) % m = (10 * 20) % m = 200 |
| static const uint8_t c_expected[kDataSizeBytes] = {200}; |
| |
| LOG_INFO("Writing input arguments to DMEM"); |
| // TODO: Use symbols from the application to load these parameters once they |
| // are available (#3998). |
| CHECK(dif_otbn_dmem_write(&otbn, /*offset_bytes=*/0, &a, sizeof(a)) == |
| kDifOtbnOk); |
| CHECK(dif_otbn_dmem_write(&otbn, /*offset_bytes=*/64, &b, sizeof(b)) == |
| kDifOtbnOk); |
| CHECK(dif_otbn_dmem_write(&otbn, /*offset_bytes=*/256, &m, sizeof(m)) == |
| kDifOtbnOk); |
| CHECK(dif_otbn_dmem_write(&otbn, /*offset_bytes=*/320, &u, sizeof(u)) == |
| kDifOtbnOk); |
| |
| int t_start = ibex_mcycle_read(); |
| |
| LOG_INFO("Calling wrap_barrett384()"); |
| call_function(&kOtbnFuncBarrett384WrapBarrett384); |
| |
| otbn_wait_for_done(); |
| |
| int t_end = ibex_mcycle_read(); |
| LOG_INFO("Function execution on OTBN took %d cycles (end-to-end).", |
| t_end - t_start); |
| |
| check_otbn_err_code(kDifOtbnErrCodeNoError); |
| |
| LOG_INFO("Reading back result (c)"); |
| dif_otbn_dmem_read(&otbn, 512, &c, sizeof(c)); |
| |
| for (int i = 0; i < sizeof(c); ++i) { |
| CHECK(c[i] == c_expected[i], |
| "Unexpected result c at byte %d: 0x%x (actual) != 0x%x (expected)", i, |
| c[i], c_expected[i]); |
| } |
| } |
| |
| /** |
| * Run err_test on OTBN and check it produces the expected error |
| * |
| * This test tries to load from an invalid address which should result in a |
| * kDifOtbnErrCodeBadDataAddr error code |
| * |
| * The code executed on OTBN can be found in sw/otbn/code-snippets/err_test.s. |
| * The entry point wrap_err_test() is called, no arguments are passed or results |
| * returned. |
| */ |
| static void test_err_test(void) { |
| load_app(&kOtbnAppErrTest); |
| |
| int t_start = ibex_mcycle_read(); |
| |
| LOG_INFO("Calling wrap_err_test()"); |
| call_function(&kOtbnFuncErrTestWrapErrTest); |
| |
| otbn_wait_for_done(); |
| |
| int t_end = ibex_mcycle_read(); |
| LOG_INFO("Function execution on OTBN took %d cycles (end-to-end).", |
| t_end - t_start); |
| |
| check_otbn_err_code(kDifOtbnErrCodeBadDataAddr); |
| } |
| |
| bool test_main() { |
| LOG_INFO("Running OTBN DIF test"); |
| |
| dif_otbn_config_t otbn_config = { |
| .base_addr = mmio_region_from_addr(TOP_EARLGREY_OTBN_BASE_ADDR), |
| }; |
| dif_otbn_result_t rv = dif_otbn_init(&otbn_config, &otbn); |
| CHECK(rv == kDifOtbnOk, "dif_otbn_init() failed: %d", rv); |
| |
| LOG_INFO("Running barrett384 code on OTBN"); |
| test_barrett384(); |
| |
| LOG_INFO("Running err_test code on OTBN"); |
| test_err_test(); |
| |
| return true; |
| } |