| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include <assert.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| |
| #include "sw/device/lib/base/math.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_alert_handler.h" |
| #include "sw/device/lib/dif/dif_flash_ctrl.h" |
| #include "sw/device/lib/dif/dif_i2c.h" |
| #include "sw/device/lib/dif/dif_rstmgr.h" |
| #include "sw/device/lib/dif/dif_rv_core_ibex.h" |
| #include "sw/device/lib/dif/dif_rv_plic.h" |
| #include "sw/device/lib/dif/dif_spi_device.h" |
| #include "sw/device/lib/dif/dif_spi_host.h" |
| #include "sw/device/lib/dif/dif_usbdev.h" |
| #include "sw/device/lib/runtime/irq.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/alert_handler_testutils.h" |
| #include "sw/device/lib/testing/flash_ctrl_testutils.h" |
| #include "sw/device/lib/testing/rv_plic_testutils.h" |
| #include "sw/device/lib/testing/test_framework/FreeRTOSConfig.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| |
| #include "alert_handler_regs.h" |
| #include "hw/top_earlgrey/sw/autogen/top_earlgrey.h" |
| #include "i2c_regs.h" |
| #include "spi_device_regs.h" |
| #include "spi_host_regs.h" |
| #include "sw/device/lib/testing/autogen/isr_testutils.h" |
| #include "usbdev_regs.h" |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| static dif_rv_plic_t plic; |
| static dif_alert_handler_t alert_handler; |
| static dif_rstmgr_t rstmgr; |
| static dif_spi_device_handle_t spi_dev; |
| static dif_spi_host_t spi_host0; |
| static dif_spi_host_t spi_host1; |
| static dif_usbdev_t usbdev; |
| static dif_i2c_t i2c0; |
| static dif_i2c_t i2c1; |
| static dif_i2c_t i2c2; |
| static dif_rv_core_ibex_t ibex; |
| static dif_flash_ctrl_state_t flash_ctrl; |
| static const uint32_t kPlicTarget = kTopEarlgreyPlicTargetIbex0; |
| |
| static plic_isr_ctx_t plic_ctx = { |
| .rv_plic = &plic, |
| .hart_id = kPlicTarget, |
| }; |
| |
| // Depends on the clock domain, sometimes alert handler will trigger a spurious |
| // alert after the alert timeout. (Issue #2321) |
| // So we allow class A interrupt to fire after the real timeout interrupt is |
| // triggered. |
| static alert_handler_isr_ctx_t alert_handler_ctx = { |
| .alert_handler = &alert_handler, |
| .plic_alert_handler_start_irq_id = kTopEarlgreyPlicIrqIdAlertHandlerClassa, |
| .expected_irq = kDifAlertHandlerIrqClassb, |
| .is_only_irq = false, |
| }; |
| |
| /** |
| * Initialize the peripherals used in this test. |
| */ |
| static void init_peripherals(void) { |
| mmio_region_t base_addr = |
| mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR); |
| CHECK_DIF_OK(dif_rv_plic_init(base_addr, &plic)); |
| |
| base_addr = mmio_region_from_addr(TOP_EARLGREY_ALERT_HANDLER_BASE_ADDR); |
| CHECK_DIF_OK(dif_alert_handler_init(base_addr, &alert_handler)); |
| |
| CHECK_DIF_OK(dif_rstmgr_init( |
| mmio_region_from_addr(TOP_EARLGREY_RSTMGR_AON_BASE_ADDR), &rstmgr)); |
| |
| CHECK_DIF_OK(dif_usbdev_init( |
| mmio_region_from_addr(TOP_EARLGREY_USBDEV_BASE_ADDR), &usbdev)); |
| |
| CHECK_DIF_OK(dif_spi_device_init( |
| mmio_region_from_addr(TOP_EARLGREY_SPI_DEVICE_BASE_ADDR), &spi_dev.dev)); |
| |
| CHECK_DIF_OK(dif_spi_host_init( |
| mmio_region_from_addr(TOP_EARLGREY_SPI_HOST0_BASE_ADDR), &spi_host0)); |
| |
| CHECK_DIF_OK(dif_spi_host_init( |
| mmio_region_from_addr(TOP_EARLGREY_SPI_HOST1_BASE_ADDR), &spi_host1)); |
| |
| CHECK_DIF_OK( |
| dif_i2c_init(mmio_region_from_addr(TOP_EARLGREY_I2C0_BASE_ADDR), &i2c0)); |
| |
| CHECK_DIF_OK( |
| dif_i2c_init(mmio_region_from_addr(TOP_EARLGREY_I2C1_BASE_ADDR), &i2c1)); |
| |
| CHECK_DIF_OK( |
| dif_i2c_init(mmio_region_from_addr(TOP_EARLGREY_I2C2_BASE_ADDR), &i2c2)); |
| |
| mmio_region_t ibex_addr = |
| mmio_region_from_addr(TOP_EARLGREY_RV_CORE_IBEX_CFG_BASE_ADDR); |
| CHECK_DIF_OK(dif_rv_core_ibex_init(ibex_addr, &ibex)); |
| |
| CHECK_DIF_OK(dif_flash_ctrl_init_state( |
| &flash_ctrl, |
| mmio_region_from_addr(TOP_EARLGREY_FLASH_CTRL_CORE_BASE_ADDR))); |
| |
| // Enable all the alert_handler interrupts used in this test. |
| rv_plic_testutils_irq_range_enable(&plic, kPlicTarget, |
| kTopEarlgreyPlicIrqIdAlertHandlerClassa, |
| kTopEarlgreyPlicIrqIdAlertHandlerClassd); |
| } |
| |
| // List of alerts |
| static const uint32_t spidev_alerts[] = { |
| kTopEarlgreyAlertIdSpiDeviceFatalFault}; |
| static const uint32_t spihost0_alerts[] = { |
| kTopEarlgreyAlertIdSpiHost0FatalFault}; |
| static const uint32_t spihost1_alerts[] = { |
| kTopEarlgreyAlertIdSpiHost1FatalFault}; |
| static const uint32_t usbdev_alerts[] = {kTopEarlgreyAlertIdUsbdevFatalFault}; |
| static const uint32_t i2c0_alerts[] = {kTopEarlgreyAlertIdI2c0FatalFault}; |
| static const uint32_t i2c1_alerts[] = {kTopEarlgreyAlertIdI2c1FatalFault}; |
| static const uint32_t i2c2_alerts[] = {kTopEarlgreyAlertIdI2c2FatalFault}; |
| |
| static const uint32_t num_spihost0_alerts = ARRAYSIZE(spihost0_alerts); |
| static const uint32_t num_spihost1_alerts = ARRAYSIZE(spihost1_alerts); |
| static const uint32_t num_usbdev_alerts = ARRAYSIZE(usbdev_alerts); |
| static const uint32_t num_spidev_alerts = ARRAYSIZE(spidev_alerts); |
| static const uint32_t num_i2c0_alerts = ARRAYSIZE(i2c0_alerts); |
| static const uint32_t num_i2c1_alerts = ARRAYSIZE(i2c1_alerts); |
| static const uint32_t num_i2c2_alerts = ARRAYSIZE(i2c2_alerts); |
| |
| static const size_t num_alerts = |
| num_spihost0_alerts + num_spihost1_alerts + num_usbdev_alerts + |
| num_i2c0_alerts + num_i2c1_alerts + num_i2c2_alerts + num_spidev_alerts; |
| |
| /** |
| * A structure to keep the info for peripheral IPs |
| */ |
| typedef struct test { |
| /** |
| * Name of the peripheral. |
| */ |
| const char *name; |
| /** |
| * Base address for the peripheral. |
| */ |
| uintptr_t base; |
| /** |
| * Handle to the DIF object for this peripheral. |
| */ |
| void *dif; |
| /** |
| * List of Alert IDs for the peripheral |
| */ |
| const uint32_t *alert_ids; |
| /** |
| * number of alerts for the peripheral |
| */ |
| const uint32_t num_alert_peri; |
| /** |
| * The index of this device in the reset manager. |
| */ |
| uint32_t reset_index; |
| } test_t; |
| |
| // The array of the peripherals |
| static const test_t kPeripherals[] = { |
| { |
| .name = "SPI_HOST0", |
| .base = TOP_EARLGREY_SPI_HOST0_BASE_ADDR, |
| .dif = &spi_host0, |
| .alert_ids = spihost0_alerts, |
| .num_alert_peri = num_spihost0_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsSpiHost0, |
| }, |
| { |
| .name = "SPI_HOST1", |
| .base = TOP_EARLGREY_SPI_HOST1_BASE_ADDR, |
| .dif = &spi_host1, |
| .alert_ids = spihost1_alerts, |
| .num_alert_peri = num_spihost1_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsSpiHost1, |
| }, |
| { |
| .name = "USB", |
| .base = TOP_EARLGREY_USBDEV_BASE_ADDR, |
| .dif = &usbdev, |
| .alert_ids = usbdev_alerts, |
| .num_alert_peri = num_usbdev_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsUsb, |
| }, |
| { |
| .name = "SPI_DEVICE", |
| .base = TOP_EARLGREY_SPI_DEVICE_BASE_ADDR, |
| .dif = &spi_dev.dev, |
| .alert_ids = spidev_alerts, |
| .num_alert_peri = num_spidev_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsSpiDevice, |
| }, |
| { |
| .name = "I2C0", |
| .base = TOP_EARLGREY_I2C0_BASE_ADDR, |
| .dif = &i2c0, |
| .alert_ids = i2c0_alerts, |
| .num_alert_peri = num_i2c0_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsI2c0, |
| }, |
| { |
| .name = "I2C1", |
| .base = TOP_EARLGREY_I2C1_BASE_ADDR, |
| .dif = &i2c1, |
| .alert_ids = i2c1_alerts, |
| .num_alert_peri = num_i2c1_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsI2c1, |
| }, |
| { |
| .name = "I2C2", |
| .base = TOP_EARLGREY_I2C2_BASE_ADDR, |
| .dif = &i2c2, |
| .alert_ids = i2c2_alerts, |
| .num_alert_peri = num_i2c2_alerts, |
| .reset_index = kTopEarlgreyResetManagerSwResetsI2c2, |
| }, |
| }; |
| |
| /** |
| * Configure and enable alerts of `num_peripheral` peripherals starting from the |
| * `first_peripheral`. |
| * @param num_peripherals The number of peripherals, of which alerts will be |
| * enabled |
| * @param first_peripheral The address of the first peripheral in the range |
| * @param ping_timeout The timeout value for the ping timer object. |
| */ |
| static void alert_handler_config_peripherals(uint32_t num_peripherals, |
| const test_t *first_peripheral, |
| uint32_t ping_timeout) { |
| dif_alert_handler_alert_t alerts[num_alerts]; |
| dif_alert_handler_class_t alert_classes[num_alerts]; |
| |
| // Enable all alerts coming from the chosen peripherals |
| // Configure them as Class A. |
| size_t array_idx = 0; |
| test_t *peri_ptr; |
| for (int ii = 0; ii < num_peripherals; ii++) { |
| peri_ptr = (test_t *)first_peripheral + ii; |
| for (int jj = 0; jj < peri_ptr->num_alert_peri; jj++) { |
| alerts[array_idx] = peri_ptr->alert_ids[jj]; |
| alert_classes[array_idx] = kDifAlertHandlerClassA; |
| array_idx++; |
| } |
| } |
| |
| // Enable the alert_ping_fail local alert and configure that as Class B. |
| dif_alert_handler_local_alert_t loc_alerts[] = { |
| kDifAlertHandlerLocalAlertAlertPingFail}; |
| dif_alert_handler_class_t loc_alert_classes[] = {kDifAlertHandlerClassB}; |
| |
| dif_alert_handler_escalation_phase_t esc_phases[] = { |
| {.phase = kDifAlertHandlerClassStatePhase0, |
| .signal = 0, |
| .duration_cycles = 2000}}; |
| |
| dif_alert_handler_class_config_t class_config = { |
| .auto_lock_accumulation_counter = kDifToggleDisabled, |
| .accumulator_threshold = 0, |
| .irq_deadline_cycles = 10000, |
| .escalation_phases = esc_phases, |
| .escalation_phases_len = ARRAYSIZE(esc_phases), |
| .crashdump_escalation_phase = kDifAlertHandlerClassStatePhase1, |
| }; |
| |
| dif_alert_handler_class_config_t class_configs[] = {class_config, |
| class_config}; |
| |
| dif_alert_handler_class_t classes[] = {kDifAlertHandlerClassA, |
| kDifAlertHandlerClassB}; |
| dif_alert_handler_config_t config = { |
| .alerts = alerts, |
| .alert_classes = alert_classes, |
| .alerts_len = array_idx, |
| .local_alerts = loc_alerts, |
| .local_alert_classes = loc_alert_classes, |
| .local_alerts_len = ARRAYSIZE(loc_alerts), |
| .classes = classes, |
| .class_configs = class_configs, |
| .classes_len = ARRAYSIZE(class_configs), |
| .ping_timeout = ping_timeout, |
| }; |
| |
| alert_handler_testutils_configure_all(&alert_handler, config, |
| kDifToggleEnabled); |
| // Enables alert handler irq. |
| CHECK_DIF_OK(dif_alert_handler_irq_set_enabled( |
| &alert_handler, kDifAlertHandlerIrqClassa, kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_alert_handler_irq_set_enabled( |
| &alert_handler, kDifAlertHandlerIrqClassb, kDifToggleEnabled)); |
| } |
| |
| /** |
| * A utility function to wait enough until the alert handler pings a peripheral |
| * alert |
| */ |
| void wait_enough_for_alert_ping() { |
| // wait enough |
| if (kDeviceType == kDeviceFpgaCw310) { |
| // NUM_ALERTS*2*margin_of_safety*(2**DW)*(1/kClockFreqPeripheralHz) |
| // (2**6)*2*4*(2**16)*(400ns) = 12.8s |
| busy_spin_micros(1000 * 12800); |
| } else if (kDeviceType == kDeviceSimDV) { |
| // NUM_ALERTS*2*margin_of_safety*(2**DW)*(1/kClockFreqPeripheralHz) |
| // (2**6)*2*4*(2**3)*(40ns) = 160us |
| busy_spin_micros(160); |
| } else { |
| // Verilator |
| // NUM_ALERTS*2*margin_of_safety*(2**DW)*(1/kClockFreqPeripheralHz) |
| // (2**6)*2*4*(2**16)*(8us) = 256s |
| // This seems to be impractical for the current clock frequency config |
| // of the Verilator tests (kClockFreqPeripheralHz = 125K). |
| LOG_FATAL("SUPPORTED PLATFORMS: DV and FPGA"); |
| LOG_FATAL("TO SUPPORT THE PLATFORM %d, COMPUTE THE RIGHT WAIT-TIME", |
| kDeviceType); |
| } |
| } |
| |
| /** |
| * Get `num` distinct random numbers in the range [0, `max`] from |
| * RV_CORE_IBEX_RND_DATA. |
| * |
| * @param ibex The Ibex DIF object. |
| * @param num The number of random numbers to get. |
| * @param[out] rnd_buf Pointer to the buffer to write the random numbers to. |
| * @param max The maximum random value returned. |
| */ |
| static void get_rand_words(dif_rv_core_ibex_t *ibex, int num, uint32_t *rnd_buf, |
| uint32_t max) { |
| uint32_t rnd_word; |
| for (int i = 0; i < num; ++i) { |
| bool found = false; |
| while (found == false) { |
| // Get a new random number. |
| CHECK_DIF_OK(dif_rv_core_ibex_read_rnd_data(ibex, &rnd_word)); |
| rnd_word = rnd_word % max; |
| // Check if the number is unique. |
| found = true; |
| for (int j = 0; j < i; ++j) { |
| if (rnd_buf[j] == rnd_word) { |
| // Start over. |
| found = false; |
| break; |
| } |
| } |
| } |
| // Add the number to the buffer. |
| rnd_buf[i] = rnd_word; |
| } |
| } |
| |
| enum { |
| // The counter ID for the non-volatile counter keeping the test steps. |
| kCounterTestSteps = 0, |
| }; |
| |
| /** |
| * External ISR. |
| * |
| * Handles all peripheral interrupts on Ibex. PLIC asserts an external interrupt |
| * line to the CPU, which results in a call to this OTTF ISR. This ISR |
| * overrides the default OTTF implementation. |
| */ |
| void ottf_external_isr(void) { |
| // We don't expect any interrupt to be fired. |
| // If an interrupt is fired, the test will be ended. |
| CHECK(false, "Unexpected external interrupt triggered."); |
| // top_earlgrey_plic_peripheral_t peripheral_serviced; |
| // dif_alert_handler_irq_t irq_serviced; |
| // isr_testutils_alert_handler_isr(plic_ctx, alert_handler_ctx, |
| // &peripheral_serviced, &irq_serviced); |
| // CHECK(peripheral_serviced == kTopEarlgreyPlicPeripheralAlertHandler, |
| // "Interurpt from unexpected peripheral: %d", peripheral_serviced); |
| } |
| |
| bool test_main(void) { |
| init_peripherals(); |
| |
| // A generic loop variable |
| int ii; |
| // To keep the test results |
| bool is_cause; |
| // To keep the random wait time |
| uint32_t rnd_wait_time; |
| |
| // The test consists of multiple test phases |
| // Each test phase consists of ARRAYSIZE(kPeripherals) steps |
| // At every step, a specific peripheral is tested |
| size_t test_phase; |
| size_t test_step_cnt; |
| size_t peri_idx; |
| |
| // Need a NVM counter to keep the test-step info |
| // between resets on the FPGA. |
| if (kDeviceType == kDeviceFpgaCw310) { |
| // Enable flash access |
| flash_ctrl_testutils_default_region_access(&flash_ctrl, |
| /*rd_en*/ true, |
| /*prog_en*/ true, |
| /*erase_en*/ true, |
| /*scramble_en*/ false, |
| /*ecc_en*/ false, |
| /*he_en*/ false); |
| |
| test_step_cnt = flash_ctrl_testutils_counter_get(kCounterTestSteps); |
| if (test_step_cnt == 256) { |
| flash_ctrl_testutils_counter_init_zero(&flash_ctrl, kCounterTestSteps); |
| test_step_cnt = flash_ctrl_testutils_counter_get(kCounterTestSteps); |
| } |
| } else { |
| // Initialize the test_step counter to zero for the simulation |
| test_step_cnt = 0; |
| } |
| |
| /* TEST PHASE #0: NEGATIVE TESTING (FPGA-ONLY) |
| |
| The timeout value is set to 2 |
| All of the alerts should trigger ping_timeout_alert |
| if they are not in reset |
| TEST: for_each_peripheral |
| 1- Hold the reset of the IP |
| 2- Enable the corresponding alerts |
| 3- Wait long enough |
| 4- Confirm that ping_timeout has not been triggered |
| 5- Release the reset of the IP |
| 6- Reset the the device to test the next peripheral |
| */ |
| while (test_step_cnt < 1 * ARRAYSIZE(kPeripherals)) { |
| // Run the negative test only on the FPGA |
| // This will save the test time in the simulation |
| if (kDeviceType == kDeviceFpgaCw310) { |
| // Read the test_step_cnt and compute the test phase |
| // amd the peripheral ID to test |
| test_step_cnt = flash_ctrl_testutils_counter_get(kCounterTestSteps); |
| test_phase = test_step_cnt / ARRAYSIZE(kPeripherals); |
| peri_idx = test_step_cnt - (test_phase)*ARRAYSIZE(kPeripherals); |
| |
| // Wait for a random time <= 1024us |
| get_rand_words(&ibex, /*number of words*/ 1, &rnd_wait_time, |
| /*max*/ 1 << 10); |
| busy_spin_micros(rnd_wait_time); |
| |
| // Hold the reset of the peripheral |
| CHECK_DIF_OK(dif_rstmgr_software_reset(&rstmgr, |
| kPeripherals[peri_idx].reset_index, |
| kDifRstmgrSoftwareResetHold)); |
| |
| alert_handler_config_peripherals( |
| /*num_peripherals*/ 1, /* *first_peripheral*/ &kPeripherals[peri_idx], |
| /*ping_timeout*/ 2); |
| |
| // wait enough until the alert handler pings the peripheral |
| wait_enough_for_alert_ping(); |
| |
| // Check the ping_timeout alert status |
| CHECK_DIF_OK(dif_alert_handler_local_alert_is_cause( |
| &alert_handler, kDifAlertHandlerLocalAlertAlertPingFail, &is_cause)); |
| CHECK(!is_cause, |
| "Expected response is ping_timeout_fail= 0! But we got " |
| "ping_timrout_fail = 1 for peripheral[%d]", |
| peri_idx); |
| |
| // Release the reset |
| CHECK_DIF_OK(dif_rstmgr_software_reset(&rstmgr, |
| kPeripherals[peri_idx].reset_index, |
| kDifRstmgrSoftwareResetRelease)); |
| |
| // Increment the test_step counter |
| flash_ctrl_testutils_counter_increment(&flash_ctrl, kCounterTestSteps); |
| // Request system reset to unlock the alert handler config and test the |
| // next peripheral |
| CHECK_DIF_OK(dif_rstmgr_software_device_reset(&rstmgr)); |
| } else { |
| // For the simulation, only increment the test_step counter |
| // to procced to the next phase without resetting the device |
| test_step_cnt++; |
| } |
| } |
| |
| /* TEST PHASE #1: TEST THE REGULAR CONFIG |
| |
| The timeout value is set to 256 |
| None of the alerts should trigger ping_timeout_alert |
| TEST: |
| enable_alerts (all_peripherals, timeout=256) |
| for_each_peripheral |
| 1- wait for random time |
| 2- Hold the IP's reset |
| 3- Wait long enough |
| 4- Release the IP's reset |
| 5- Confirm that ping_timeout has not been triggered |
| */ |
| |
| // Enable and lock the all peripherals' alerts at the beginning |
| // of the PHASE #1 |
| if (test_step_cnt == 1 * ARRAYSIZE(kPeripherals)) { |
| alert_handler_config_peripherals( |
| /*num_peripherals*/ ARRAYSIZE(kPeripherals), |
| /* *first_peripheral*/ &kPeripherals[0], /*ping_timeout*/ 256); |
| } |
| |
| while (test_step_cnt < 2 * ARRAYSIZE(kPeripherals)) { |
| // Read the test_step_cnt and compute the test phase |
| // amd the peripheral ID to test |
| test_phase = test_step_cnt / ARRAYSIZE(kPeripherals); |
| peri_idx = test_step_cnt - (test_phase)*ARRAYSIZE(kPeripherals); |
| |
| // Wait for a random time <= 1024us |
| get_rand_words(&ibex, /*number of words*/ 1, &rnd_wait_time, |
| /*max*/ 1 << 10); |
| busy_spin_micros(rnd_wait_time); |
| |
| // Hold the reset of the peripheral |
| CHECK_DIF_OK(dif_rstmgr_software_reset(&rstmgr, |
| kPeripherals[peri_idx].reset_index, |
| kDifRstmgrSoftwareResetHold)); |
| |
| // wait enough until the alert handler pings the peripheral |
| wait_enough_for_alert_ping(); |
| |
| // Check the alert status |
| CHECK_DIF_OK(dif_alert_handler_local_alert_is_cause( |
| &alert_handler, kDifAlertHandlerLocalAlertAlertPingFail, &is_cause)); |
| CHECK(!is_cause, "Expected response: No alert_ping_fail! but we got %d", |
| is_cause); |
| |
| // Release the reset of the peripheral |
| CHECK_DIF_OK(dif_rstmgr_software_reset(&rstmgr, |
| kPeripherals[peri_idx].reset_index, |
| kDifRstmgrSoftwareResetRelease)); |
| |
| // Increment the test counter |
| test_step_cnt++; |
| } |
| |
| return true; |
| } |