| /* |
| * Copyright 2023 Google LLC |
| * Copyright lowRISC contributors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| #include "sw/device/lib/arch/device.h" |
| #include "sw/device/lib/base/mmio.h" |
| #include "sw/device/lib/dif/dif_base.h" |
| #include "sw/device/lib/dif/dif_clkmgr.h" |
| #include "sw/device/lib/dif/dif_lc_ctrl.h" |
| #include "sw/device/lib/dif/dif_rv_plic.h" |
| #include "sw/device/lib/dif/dif_uart.h" |
| #include "sw/device/lib/runtime/hart.h" |
| #include "sw/device/lib/runtime/irq.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/testing/clkmgr_testutils.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/ottf_main.h" |
| #include "sw/device/lib/testing/test_framework/status.h" |
| |
| #include "hw/top_matcha/sw/autogen/top_matcha.h" |
| |
| // TODO, remove it once pinout configuration is provided |
| #include "pinmux_regs.h" |
| |
| #define UART_DATASET_SIZE 128 |
| |
| static dif_uart_t uart; |
| static dif_rv_plic_t plic; |
| |
| /** |
| * UART TX RX test |
| * |
| * This test sends and receives a known dataset over UART. The size of the |
| * dataset is indicated with UART_DATASET_SIZE. The dataset is agreed upon by |
| * the device (a.k.a. the OpenTitan chip) and the host (a.k.a. the simulation |
| * device, such as DV testbench) communicating with it. Data transmitted over |
| * TX is checked for correctness at the host, and likewise, data sent by the |
| * host is checked for correctness at the device (in this SW test). The data |
| * transmitted over TX and RX ports may occur simultaneously. The test ensures |
| * that the TX watermark, RX watermark and TX empty interrupts are seen. |
| * At the end, the host transmits another set of random data (greater than the |
| * RX fifo size) which the device drops, to generate the RX overflow condition. |
| * The test passes when the datasets at both ends match the expected and all |
| * required interrupts are seen. |
| */ |
| |
| /** |
| * UART test data transfer direction |
| * |
| * Enumeration indicating the direction of transfer of test data. |
| */ |
| typedef enum uart_direction { |
| kUartSend = 0, |
| kUartReceive, |
| } uart_direction_t; |
| |
| /** |
| * Indicates the UART instance under test. |
| * |
| * From the software / compiler's perspective, this is a constant (hence the |
| * `const` qualifier). However, the external DV testbench finds this symbol's |
| * address and modifies it via backdoor, to test a different UART instance with |
| * the same test SW image. Hence, we add the `volatile` keyword to prevent the |
| * compiler from optimizing it out. |
| * The `const` is needed to put it in the .rodata section, otherwise it gets |
| * placed in .data section in the main SRAM. We cannot backdoor write anything |
| * in SRAM at the start of the test because the CRT init code wipes it to 0s. |
| */ |
| static volatile const uint8_t kUartIdx = 0x0; |
| |
| /** |
| * Indicates if ext_clk is used and what speed. |
| * |
| * Similar to `kUartIdx`, this may be overridden in DV testbench |
| */ |
| static volatile const bool kUseExtClk = false; |
| static volatile const bool kUseLowSpeedSel = false; |
| |
| // A set of bytes to be send out of TX. |
| static const uint8_t kUartTxData[UART_DATASET_SIZE] = { |
| 0xe8, 0x50, 0xc6, 0xb4, 0xbe, 0x16, 0xed, 0x55, 0x16, 0x1d, 0xe6, 0x1c, |
| 0xde, 0x9f, 0xfd, 0x24, 0x89, 0x81, 0x4d, 0x0d, 0x1a, 0x12, 0x4f, 0x57, |
| 0xea, 0xd6, 0x6f, 0xc0, 0x7d, 0x46, 0xe7, 0x37, 0x81, 0xd3, 0x8e, 0x16, |
| 0xad, 0x7b, 0xd0, 0xe2, 0x4f, 0xff, 0x39, 0xe6, 0x71, 0x3c, 0x82, 0x04, |
| 0xec, 0x3a, 0x27, 0xcc, 0x3d, 0x58, 0x0e, 0x56, 0xd2, 0xd2, 0xb9, 0xa3, |
| 0xb5, 0x3d, 0xc0, 0x40, 0xba, 0x90, 0x16, 0xd8, 0xe3, 0xa4, 0x22, 0x74, |
| 0x80, 0xcb, 0x7b, 0xde, 0xd7, 0x3f, 0x4d, 0x93, 0x4d, 0x59, 0x79, 0x88, |
| 0x24, 0xe7, 0x68, 0x8b, 0x7a, 0x78, 0xb7, 0x07, 0x09, 0x26, 0xcf, 0x6b, |
| 0x52, 0xd9, 0x4c, 0xd3, 0x33, 0xdf, 0x2e, 0x0d, 0x3b, 0xab, 0x45, 0x85, |
| 0xc2, 0xc2, 0x19, 0xe5, 0xc7, 0x2b, 0xb0, 0xf6, 0xcb, 0x06, 0xf6, 0xe2, |
| 0xf5, 0xb1, 0xab, 0xef, 0x6f, 0xd8, 0x23, 0xfd, |
| }; |
| |
| // The set of bytes expected to be received over RX. |
| static const uint8_t kExpUartRxData[UART_DATASET_SIZE] = { |
| 0x1b, 0x95, 0xc5, 0xb5, 0x8a, 0xa4, 0xa8, 0x9f, 0x6a, 0x7d, 0x6b, 0x0c, |
| 0xcd, 0xd5, 0xa6, 0x8f, 0x07, 0x3a, 0x9e, 0x82, 0xe6, 0xa2, 0x2b, 0xe0, |
| 0x0c, 0x30, 0xe8, 0x5a, 0x05, 0x14, 0x79, 0x8a, 0xFf, 0x88, 0x29, 0xda, |
| 0xc8, 0xdd, 0x82, 0xd5, 0x68, 0xa5, 0x9d, 0x5a, 0x48, 0x02, 0x7f, 0x24, |
| 0x32, 0xaf, 0x9d, 0xca, 0xa7, 0x06, 0x0c, 0x96, 0x65, 0x18, 0xe4, 0x7f, |
| 0x26, 0x44, 0xf3, 0x14, 0xC1, 0xe7, 0xd9, 0x82, 0xf7, 0x64, 0xe8, 0x68, |
| 0xf9, 0x6c, 0xa9, 0xe7, 0xd1, 0x9b, 0xac, 0xe1, 0xFd, 0xd8, 0x59, 0xb7, |
| 0x8e, 0xdc, 0x24, 0xb8, 0xa7, 0xaf, 0x20, 0xee, 0x6c, 0x61, 0x48, 0x41, |
| 0xB4, 0x62, 0x3c, 0xcb, 0x2c, 0xbb, 0xe4, 0x44, 0x97, 0x8a, 0x5e, 0x2f, |
| 0x7f, 0x2b, 0x10, 0xcc, 0x7d, 0x89, 0x32, 0xfd, 0xfd, 0x58, 0x7f, 0xd8, |
| 0xc7, 0x33, 0xd1, 0x6a, 0xc7, 0xba, 0x78, 0x69, |
| }; |
| |
| // There are multiple uart instances in the chip. These variables will be |
| // updated according to the uart we select. |
| static volatile uint32_t uart_base_addr; |
| static volatile uint32_t uart_peripheral_id; |
| static volatile uint32_t uart_irq_tx_watermartk_id; |
| static volatile uint32_t uart_irq_rx_watermartk_id; |
| static volatile uint32_t uart_irq_tx_empty_id; |
| static volatile uint32_t uart_irq_rx_overflow_id; |
| static volatile uint32_t uart_irq_rx_frame_err_id; |
| static volatile uint32_t uart_irq_rx_frame_err_id; |
| static volatile uint32_t uart_irq_rx_break_err_id; |
| static volatile uint32_t uart_irq_rx_timeout_id; |
| static volatile uint32_t uart_irq_rx_parity_err_id; |
| |
| /** |
| * Set our expectation & event indications of the interrupts we intend to |
| * exercise in this test. These are declared volatile since they are used by the |
| * ISR. |
| */ |
| static volatile bool exp_uart_irq_tx_watermark; |
| static volatile bool uart_irq_tx_watermark_fired; |
| static volatile bool exp_uart_irq_rx_watermark; |
| static volatile bool uart_irq_rx_watermark_fired; |
| static volatile bool exp_uart_irq_tx_empty; |
| static volatile bool uart_irq_tx_empty_fired; |
| static volatile bool exp_uart_irq_rx_overflow; |
| static volatile bool uart_irq_rx_overflow_fired; |
| |
| // Configures the pinmux to connect the UART instance to chip IOs based on the |
| // ChromeOS pinout configuration. |
| // |
| // The pinout configuration is documented here: |
| // https://github.com/lowRISC/opentitan/blob/master/hw/top_matcha/data/top_matcha.hjson |
| // TODO: Pinout configuration APIs based on customer usecases will be |
| // auto-generated in future. This function is a stop-gap solution until that is |
| // made available. |
| static void pinmux_connect_uart_to_pads(uint32_t rx_pin_in_idx, |
| uint32_t rx_uart_idx, |
| uint32_t tx_pin_out_idx, |
| uint32_t tx_uart_idx) { |
| mmio_region_t reg32 = mmio_region_from_addr( |
| TOP_MATCHA_PINMUX_AON_BASE_ADDR + PINMUX_MIO_PERIPH_INSEL_0_REG_OFFSET); |
| uint32_t reg_value = rx_pin_in_idx; |
| // We've got one insel configuration field per register. Hence, we have to |
| // convert the enumeration index into a byte address using << 2. |
| uint32_t reg_offset = rx_uart_idx << 2; |
| uint32_t mask = PINMUX_MIO_PERIPH_INSEL_0_IN_0_MASK; |
| mmio_region_write32(reg32, reg_offset, reg_value & mask); |
| |
| reg32 = mmio_region_from_addr(TOP_MATCHA_PINMUX_AON_BASE_ADDR + |
| PINMUX_MIO_OUTSEL_0_REG_OFFSET); |
| reg_value = tx_uart_idx; |
| // We've got one insel configuration field per register. Hence, we have to |
| // convert the enumeration index into a byte address using << 2. |
| reg_offset = tx_pin_out_idx << 2; |
| mask = PINMUX_MIO_OUTSEL_0_OUT_0_MASK; |
| mmio_region_write32(reg32, reg_offset, reg_value & mask); |
| } |
| |
| void update_uart_base_addr_and_irq_id(void) { |
| switch (kUartIdx) { |
| case 0: |
| uart_base_addr = TOP_MATCHA_UART0_BASE_ADDR; |
| uart_peripheral_id = kTopMatchaPlicPeripheralUart0; |
| uart_irq_tx_watermartk_id = kTopMatchaPlicIrqIdUart0TxWatermark; |
| uart_irq_rx_watermartk_id = kTopMatchaPlicIrqIdUart0RxWatermark; |
| uart_irq_tx_empty_id = kTopMatchaPlicIrqIdUart0TxEmpty; |
| uart_irq_rx_overflow_id = kTopMatchaPlicIrqIdUart0RxOverflow; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart0RxFrameErr; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart0RxFrameErr; |
| uart_irq_rx_break_err_id = kTopMatchaPlicIrqIdUart0RxBreakErr; |
| uart_irq_rx_timeout_id = kTopMatchaPlicIrqIdUart0RxTimeout; |
| uart_irq_rx_parity_err_id = kTopMatchaPlicIrqIdUart0RxParityErr; |
| break; |
| case 1: |
| uart_base_addr = TOP_MATCHA_UART1_BASE_ADDR; |
| uart_peripheral_id = kTopMatchaPlicPeripheralUart1; |
| uart_irq_tx_watermartk_id = kTopMatchaPlicIrqIdUart1TxWatermark; |
| uart_irq_rx_watermartk_id = kTopMatchaPlicIrqIdUart1RxWatermark; |
| uart_irq_tx_empty_id = kTopMatchaPlicIrqIdUart1TxEmpty; |
| uart_irq_rx_overflow_id = kTopMatchaPlicIrqIdUart1RxOverflow; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart1RxFrameErr; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart1RxFrameErr; |
| uart_irq_rx_break_err_id = kTopMatchaPlicIrqIdUart1RxBreakErr; |
| uart_irq_rx_timeout_id = kTopMatchaPlicIrqIdUart1RxTimeout; |
| uart_irq_rx_parity_err_id = kTopMatchaPlicIrqIdUart1RxParityErr; |
| break; |
| case 2: |
| uart_base_addr = TOP_MATCHA_UART2_BASE_ADDR; |
| uart_peripheral_id = kTopMatchaPlicPeripheralUart2; |
| uart_irq_tx_watermartk_id = kTopMatchaPlicIrqIdUart2TxWatermark; |
| uart_irq_rx_watermartk_id = kTopMatchaPlicIrqIdUart2RxWatermark; |
| uart_irq_tx_empty_id = kTopMatchaPlicIrqIdUart2TxEmpty; |
| uart_irq_rx_overflow_id = kTopMatchaPlicIrqIdUart2RxOverflow; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart2RxFrameErr; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdUart2RxFrameErr; |
| uart_irq_rx_break_err_id = kTopMatchaPlicIrqIdUart2RxBreakErr; |
| uart_irq_rx_timeout_id = kTopMatchaPlicIrqIdUart2RxTimeout; |
| uart_irq_rx_parity_err_id = kTopMatchaPlicIrqIdUart2RxParityErr; |
| break; |
| case 3: |
| uart_base_addr = TOP_MATCHA_UART3_BASE_ADDR; |
| uart_peripheral_id = kTopMatchaPlicPeripheralSmcUart; |
| uart_irq_tx_watermartk_id = kTopMatchaPlicIrqIdSmcUartTxWatermark; |
| uart_irq_rx_watermartk_id = kTopMatchaPlicIrqIdSmcUartRxWatermark; |
| uart_irq_tx_empty_id = kTopMatchaPlicIrqIdSmcUartTxEmpty; |
| uart_irq_rx_overflow_id = kTopMatchaPlicIrqIdSmcUartRxOverflow; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdSmcUartRxFrameErr; |
| uart_irq_rx_frame_err_id = kTopMatchaPlicIrqIdSmcUartRxFrameErr; |
| uart_irq_rx_break_err_id = kTopMatchaPlicIrqIdSmcUartRxBreakErr; |
| uart_irq_rx_timeout_id = kTopMatchaPlicIrqIdSmcUartRxTimeout; |
| uart_irq_rx_parity_err_id = kTopMatchaPlicIrqIdSmcUartRxParityErr; |
| break; |
| default: |
| LOG_FATAL("Unsupported uart ID %x", kUartIdx); |
| } |
| } |
| /** |
| * Provides external irq handling for this test. |
| * |
| * This function overrides the default OTTF external ISR. |
| */ |
| void ottf_external_isr(void) { |
| // Find which interrupt fired at PLIC by claiming it. |
| dif_rv_plic_irq_id_t plic_irq_id; |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_claim(&plic, kTopMatchaPlicTargetIbex0, &plic_irq_id)); |
| |
| // Check if it is the right peripheral. |
| top_matcha_plic_peripheral_t peripheral = (top_matcha_plic_peripheral_t) |
| top_matcha_plic_interrupt_for_peripheral[plic_irq_id]; |
| CHECK(peripheral == uart_peripheral_id, |
| "Interurpt from unexpected peripheral: %d", peripheral); |
| |
| // Correlate the interrupt fired at PLIC with UART. |
| dif_uart_irq_t uart_irq; |
| if (plic_irq_id == uart_irq_tx_watermartk_id) { |
| CHECK(exp_uart_irq_tx_watermark, "Unexpected TX watermark interrupt"); |
| uart_irq_tx_watermark_fired = true; |
| uart_irq = kDifUartIrqTxWatermark; |
| } else if (plic_irq_id == uart_irq_rx_watermartk_id) { |
| CHECK(exp_uart_irq_rx_watermark, "Unexpected RX watermark interrupt"); |
| uart_irq_rx_watermark_fired = true; |
| uart_irq = kDifUartIrqRxWatermark; |
| } else if (plic_irq_id == uart_irq_tx_empty_id) { |
| CHECK(exp_uart_irq_tx_empty, "Unexpected TX empty interrupt"); |
| uart_irq_tx_empty_fired = true; |
| uart_irq = kDifUartIrqTxEmpty; |
| } else if (plic_irq_id == uart_irq_rx_overflow_id) { |
| CHECK(exp_uart_irq_rx_overflow, "Unexpected RX overflow interrupt"); |
| uart_irq_rx_overflow_fired = true; |
| uart_irq = kDifUartIrqRxOverflow; |
| } else { |
| LOG_ERROR("Unexpected interrupt (at PLIC): %d", plic_irq_id); |
| test_status_set(kTestStatusFailed); |
| // The `abort()` call below is redundant. It is added to prevent the |
| // compilation error due to not initializing the `uart_irq` enum variable |
| // above. See issue #2157 for moe details. |
| abort(); |
| } |
| |
| // Check if the same interrupt fired at UART as well. |
| bool is_pending; |
| CHECK_DIF_OK(dif_uart_irq_is_pending(&uart, uart_irq, &is_pending)); |
| CHECK(is_pending, "UART interrupt fired at PLIC did not fire at UART"); |
| |
| // Clear the interrupt at UART. |
| CHECK_DIF_OK(dif_uart_irq_acknowledge(&uart, uart_irq)); |
| |
| // Complete the IRQ at PLIC. |
| CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kTopMatchaPlicTargetIbex0, |
| plic_irq_id)); |
| } |
| |
| /** |
| * Initializes UART and enables the relevant interrupts. |
| */ |
| static void uart_init_with_irqs(mmio_region_t base_addr, dif_uart_t *uart) { |
| LOG_INFO("Initializing the UART."); |
| |
| CHECK_DIF_OK(dif_uart_init(base_addr, uart)); |
| CHECK_DIF_OK( |
| dif_uart_configure(uart, (dif_uart_config_t){ |
| .baudrate = kUartBaudrate, |
| .clk_freq_hz = kClockFreqPeripheralHz, |
| .parity_enable = kDifToggleDisabled, |
| .parity = kDifUartParityEven, |
| .tx_enable = kDifToggleEnabled, |
| .rx_enable = kDifToggleEnabled, |
| })); |
| |
| // Set the TX and RX watermark to 16 bytes. |
| CHECK_DIF_OK(dif_uart_watermark_tx_set(uart, kDifUartWatermarkByte16)); |
| CHECK_DIF_OK(dif_uart_watermark_rx_set(uart, kDifUartWatermarkByte16)); |
| |
| // Enable these UART interrupts - TX/TX watermark, TX empty and RX overflow. |
| CHECK_DIF_OK(dif_uart_irq_set_enabled(uart, kDifUartIrqTxWatermark, |
| kDifToggleEnabled)); |
| CHECK_DIF_OK(dif_uart_irq_set_enabled(uart, kDifUartIrqRxWatermark, |
| kDifToggleEnabled)); |
| CHECK_DIF_OK( |
| dif_uart_irq_set_enabled(uart, kDifUartIrqTxEmpty, kDifToggleEnabled)); |
| CHECK_DIF_OK( |
| dif_uart_irq_set_enabled(uart, kDifUartIrqRxOverflow, kDifToggleEnabled)); |
| } |
| |
| /** |
| * Initializes PLIC and enables the relevant UART interrupts. |
| */ |
| static void plic_init_with_irqs(mmio_region_t base_addr, dif_rv_plic_t *plic) { |
| LOG_INFO("Initializing the PLIC. %08x", uart_irq_tx_watermartk_id); |
| |
| CHECK_DIF_OK(dif_rv_plic_init(base_addr, plic)); |
| |
| // Set the priority of UART interrupts at PLIC to be >=1 (so ensure the target |
| // does get interrupted). |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_tx_watermartk_id, 0x1)); |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_rx_watermartk_id, 0x2)); |
| CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, uart_irq_tx_empty_id, 0x3)); |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_rx_overflow_id, 0x1)); |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_rx_frame_err_id, 0x2)); |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_rx_break_err_id, 0x3)); |
| CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, uart_irq_rx_timeout_id, 0x1)); |
| CHECK_DIF_OK( |
| dif_rv_plic_irq_set_priority(plic, uart_irq_rx_parity_err_id, 0x2)); |
| |
| // Set the threshold for the Ibex to 0. |
| CHECK_DIF_OK( |
| dif_rv_plic_target_set_threshold(plic, kTopMatchaPlicTargetIbex0, 0x0)); |
| |
| // Enable all UART interrupts at the PLIC. |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_tx_watermartk_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_watermartk_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_tx_empty_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_overflow_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_frame_err_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_break_err_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_timeout_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| |
| CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, uart_irq_rx_parity_err_id, |
| kTopMatchaPlicTargetIbex0, |
| kDifToggleEnabled)); |
| } |
| |
| /** |
| * Continue ongoing transmission of bytes. |
| * |
| * This is a wrapper around `dif_uart_bytes_send|receive()` functions. It picks |
| * up an ongoing transfer of data starting at `dataset_index` location until |
| * the UART can no longer accept any more data to be sent / return any more |
| * data received, depending on the direction of the data transfer indicated with |
| * the `uart_direction` argument. It uses the `bytes_written` / `bytes_read` |
| * value to advance the `dataset_index` for the next round. It updates the |
| * `transfer_done` arg to indicate if the ongoing transfer has completed. |
| */ |
| static bool uart_transfer_ongoing_bytes(const dif_uart_t *uart, |
| uart_direction_t uart_direction, |
| uint8_t *data, size_t dataset_size, |
| size_t *dataset_index, |
| bool *transfer_done) { |
| size_t bytes_remaining = dataset_size - *dataset_index; |
| size_t bytes_transferred = 0; |
| bool result = false; |
| switch (uart_direction) { |
| case kUartSend: |
| result = dif_uart_bytes_send(uart, &data[*dataset_index], bytes_remaining, |
| &bytes_transferred) == kDifOk; |
| break; |
| case kUartReceive: |
| result = |
| dif_uart_bytes_receive(uart, bytes_remaining, &data[*dataset_index], |
| &bytes_transferred) == kDifOk; |
| break; |
| default: |
| LOG_FATAL("Invalid UART data transfer direction!"); |
| } |
| *dataset_index += bytes_transferred; |
| *transfer_done = *dataset_index == dataset_size; |
| return result; |
| } |
| |
| static void execute_test(const dif_uart_t *uart) { |
| bool uart_tx_done = false; |
| size_t uart_tx_bytes_written = 0; |
| exp_uart_irq_tx_watermark = true; |
| // Set the flag below to true to allow TX data to be sent the first time in |
| // the if comdition below. Subsequently, TX watermark interrupt will trigger |
| // more data to be sent. |
| uart_irq_tx_watermark_fired = true; |
| exp_uart_irq_tx_empty = false; |
| uart_irq_tx_empty_fired = false; |
| |
| bool uart_rx_done = false; |
| size_t uart_rx_bytes_read = 0; |
| exp_uart_irq_rx_watermark = true; |
| // Set the flag below to true to allow RX data to be received the first time |
| // in the if comdition below. Subsequently, RX watermark interrupt will |
| // trigger more data to be received. |
| uart_irq_rx_watermark_fired = true; |
| exp_uart_irq_rx_overflow = false; |
| uart_irq_rx_overflow_fired = false; |
| |
| // A set of bytes actually received over RX. |
| uint8_t uart_rx_data[UART_DATASET_SIZE]; |
| |
| LOG_INFO("Executing the test."); |
| while (!uart_tx_done || !uart_rx_done || !uart_irq_tx_empty_fired || |
| !uart_irq_rx_overflow_fired) { |
| if (!uart_tx_done && uart_irq_tx_watermark_fired) { |
| uart_irq_tx_watermark_fired = false; |
| |
| // Send the remaining kUartTxData as and when the TX watermark fires. |
| CHECK(uart_transfer_ongoing_bytes(uart, kUartSend, (uint8_t *)kUartTxData, |
| UART_DATASET_SIZE, |
| &uart_tx_bytes_written, &uart_tx_done)); |
| |
| if (uart_tx_done) { |
| // At this point, we have sent the required number of bytes. |
| // Expect the TX empty interrupt to fire at some point. |
| exp_uart_irq_tx_empty = true; |
| } |
| } |
| |
| if (!uart_rx_done && uart_irq_rx_watermark_fired) { |
| uart_irq_rx_watermark_fired = false; |
| |
| // When RX watermark fires, read the data, but if remaining items are less |
| // than 16, RX watermark won't fire. In that case, keep reading until all |
| // item are received. |
| do { |
| CHECK(uart_transfer_ongoing_bytes(uart, kUartReceive, uart_rx_data, |
| UART_DATASET_SIZE, |
| &uart_rx_bytes_read, &uart_rx_done)); |
| } while (!uart_rx_done && (UART_DATASET_SIZE - uart_rx_bytes_read < 16)); |
| |
| if (uart_rx_done) { |
| exp_uart_irq_rx_watermark = false; |
| // At this point we have received the required number of bytes. |
| // We disable the RX watermark interrupt and let the fifo |
| // overflow by dropping all future incoming data. |
| CHECK_DIF_OK(dif_uart_irq_set_enabled(uart, kDifUartIrqRxWatermark, |
| kDifToggleDisabled)); |
| // Expect the RX overflow interrupt to fire at some point. |
| exp_uart_irq_rx_overflow = true; |
| } |
| } |
| |
| if (uart_irq_tx_empty_fired) { |
| exp_uart_irq_tx_watermark = false; |
| exp_uart_irq_tx_empty = false; |
| } |
| |
| if (uart_irq_rx_overflow_fired) { |
| exp_uart_irq_rx_overflow = false; |
| } |
| |
| // Wait for the next interrupt to arrive. |
| // This check here is necessary as rx interrupts may sometimes occur ahead |
| // of tx interrupts. When this happens, the tx handling code above is not |
| // triggerd and as a result an unexpected tx_empty interrupt is fired later. |
| if (!uart_irq_rx_watermark_fired && !uart_irq_tx_watermark_fired) { |
| wait_for_interrupt(); |
| } |
| } |
| |
| // Check data consistency. |
| LOG_INFO("Checking the received UART RX data for consistency."); |
| for (int i = 0; i < UART_DATASET_SIZE; ++i) { |
| CHECK(uart_rx_data[i] == kExpUartRxData[i], |
| "UART RX data[%d] mismatched: {act: %x, exp: %x}", i, uart_rx_data[i], |
| kExpUartRxData[i]); |
| } |
| } |
| |
| void config_external_clock(const dif_clkmgr_t *clkmgr) { |
| dif_lc_ctrl_t lc; |
| mmio_region_t lc_ctrl_base_addr = |
| mmio_region_from_addr(TOP_MATCHA_LC_CTRL_BASE_ADDR); |
| CHECK_DIF_OK(dif_lc_ctrl_init(lc_ctrl_base_addr, &lc)); |
| |
| LOG_INFO("Read and check LC state."); |
| dif_lc_ctrl_state_t curr_state; |
| CHECK_DIF_OK(dif_lc_ctrl_get_state(&lc, &curr_state)); |
| CHECK(curr_state == kDifLcCtrlStateRma, |
| "LC State isn't in kDifLcCtrlStateRma!"); |
| |
| clkmgr_testutils_enable_external_clock_and_wait_for_completion( |
| clkmgr, kUseLowSpeedSel); |
| } |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| bool test_main(void) { |
| dif_clkmgr_t clkmgr; |
| mmio_region_t clkmgr_base_addr = |
| mmio_region_from_addr(TOP_MATCHA_CLKMGR_AON_BASE_ADDR); |
| CHECK_DIF_OK(dif_clkmgr_init(clkmgr_base_addr, &clkmgr)); |
| |
| update_uart_base_addr_and_irq_id(); |
| |
| LOG_INFO("Test UART%d with base_addr: %08x", kUartIdx, uart_base_addr); |
| |
| pinmux_connect_uart_to_pads( |
| kTopMatchaPinmuxInselIoc3, kTopMatchaPinmuxPeripheralInUart0Rx, |
| kTopMatchaPinmuxMioOutIoc4, kTopMatchaPinmuxOutselUart0Tx); |
| pinmux_connect_uart_to_pads( |
| kTopMatchaPinmuxInselIob4, kTopMatchaPinmuxPeripheralInUart1Rx, |
| kTopMatchaPinmuxMioOutIob5, kTopMatchaPinmuxOutselUart1Tx); |
| // TODO: the UARTs below still need to be mapped to the correct location. |
| pinmux_connect_uart_to_pads( |
| kTopMatchaPinmuxInselIoa4, kTopMatchaPinmuxPeripheralInUart2Rx, |
| kTopMatchaPinmuxMioOutIoa5, kTopMatchaPinmuxOutselUart2Tx); |
| pinmux_connect_uart_to_pads( |
| kTopMatchaPinmuxInselIoa0, kTopMatchaPinmuxPeripheralInSmcUartRx, |
| kTopMatchaPinmuxMioOutIoa1, kTopMatchaPinmuxOutselSmcUartTx); |
| |
| if (kUseExtClk) { |
| config_external_clock(&clkmgr); |
| } |
| clkmgr_testutils_enable_clock_counts_with_expected_thresholds( |
| &clkmgr, /*jitter_enabled=*/false, kUseExtClk, kUseLowSpeedSel); |
| |
| // Initialize the UART. |
| mmio_region_t chosen_uart_region = mmio_region_from_addr(uart_base_addr); |
| uart_init_with_irqs(chosen_uart_region, &uart); |
| |
| // Initialize the PLIC. |
| mmio_region_t plic_base_addr = |
| mmio_region_from_addr(TOP_MATCHA_RV_PLIC_BASE_ADDR); |
| plic_init_with_irqs(plic_base_addr, &plic); |
| |
| // Enable the external IRQ at Ibex. |
| irq_global_ctrl(true); |
| irq_external_ctrl(true); |
| |
| // Execute the test. |
| execute_test(&uart); |
| |
| CHECK(clkmgr_testutils_check_measurement_counts(&clkmgr)); |
| |
| return true; |
| } |