| /* | |
| * 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; | |
| } |