| /* |
| * 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 "hw/top_matcha/sw/autogen/top_matcha.h" |
| #include "sw/device/lib/base/csr.h" |
| #include "sw/device/lib/dif/dif_rv_timer.h" |
| #include "sw/device/lib/runtime/log.h" |
| #include "sw/device/lib/runtime/print.h" |
| #include "sw/device/lib/testing/test_framework/check.h" |
| #include "sw/device/lib/testing/test_framework/ottf_test_config.h" |
| #include "sw/device/lib/testing/test_framework/status.h" |
| #include "sw/device/lib/testing/test_framework/test_util.h" |
| #include "sw/device/lib/virtual_memory.h" |
| |
| uint32_t supervisor_l1pt[PAGE_SIZE / sizeof(uint32_t)] |
| __attribute__((aligned(PAGE_SIZE))); |
| uint32_t supervisor_l2pt[PAGE_SIZE / sizeof(uint32_t)] |
| __attribute__((aligned(PAGE_SIZE))); |
| |
| extern uint32_t __SUPER_VIRTUAL_ROM; |
| extern uint32_t __super_virtual_start; |
| extern uint32_t __super_virtual_end; |
| |
| OTTF_DEFINE_TEST_CONFIG(); |
| |
| static dif_uart_t smc_uart; |
| static dif_rv_timer_t rv_timer; |
| static uint32_t supervisor_custom_fn_addr = 0; |
| |
| // Overrides the default supervisor ecall handler. |
| // In this test, any ecalls indicate that we took an unexpected interrupt, |
| // so fail the test. |
| void ottf_supervisor_ecall_handler(void) { test_status_set(kTestStatusFailed); } |
| |
| // Function that will execute in S-mode. |
| // Triggers a test pass. |
| __attribute__((section(".super_virtual"))) void smode_fn(void) { |
| test_status_set(kTestStatusPassed); |
| } |
| |
| // S-mode trap handler. |
| // All entries are ecall, except the timer entry. |
| static __attribute__((section(".super_virtual"))) |
| __attribute__((naked, aligned(256))) void |
| stvec(void) { |
| asm volatile( |
| ".option push \n" |
| ".option norvc \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| // Timer interrupt is index 5 |
| "j smode_fn \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| "ecall \n" |
| ".option pop \n"); |
| } |
| |
| void _ottf_main(void) { |
| test_status_set(kTestStatusInTest); |
| |
| // Initialize the SMC UART to enable logging for non-DV simulation platforms. |
| if (kDeviceType != kDeviceSimDV) { |
| init_uart(TOP_MATCHA_SMC_UART_BASE_ADDR, &smc_uart); |
| } |
| |
| dif_rv_timer_tick_params_t tick_params; |
| CHECK_DIF_OK(dif_rv_timer_approximate_tick_params(kClockFreqPeripheralHz, |
| 1000000, &tick_params)); |
| CHECK_DIF_OK(dif_rv_timer_init( |
| mmio_region_from_addr(TOP_MATCHA_RV_TIMER_SMC_BASE_ADDR), &rv_timer)); |
| CHECK_DIF_OK(dif_rv_timer_set_tick_params(&rv_timer, 0, tick_params)); |
| CHECK_DIF_OK(dif_rv_timer_irq_set_enabled( |
| &rv_timer, kDifRvTimerIrqTimerExpiredHart0Timer0, kDifToggleEnabled)); |
| uint64_t counter_start; |
| CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, 0, &counter_start)); |
| CHECK_DIF_OK(dif_rv_timer_arm(&rv_timer, 0, 0, counter_start + 1000)); |
| CHECK_DIF_OK( |
| dif_rv_timer_counter_set_enabled(&rv_timer, 0, kDifToggleEnabled)); |
| |
| // Map our code space and UART peripheral. |
| // Both are mapped such that the virtual addresses |
| // and physical addresses are the same, but this still |
| // causes address translation to happen. |
| machine_map_megapage((void *)TOP_MATCHA_RAM_SMC_BASE_ADDR, |
| (void *)TOP_MATCHA_RAM_SMC_BASE_ADDR, |
| (uint32_t *)supervisor_l1pt); |
| machine_map_megapage((void *)TOP_MATCHA_SMC_UART_BASE_ADDR, |
| (void *)TOP_MATCHA_SMC_UART_BASE_ADDR, |
| (uint32_t *)supervisor_l1pt); |
| |
| // Set up 2-level page tables for supervisor-mode |
| machine_map_region( |
| (uint32_t)&__SUPER_VIRTUAL_ROM, (uint32_t)&__super_virtual_start, |
| (uint32_t)&__super_virtual_end, supervisor_l1pt, supervisor_l2pt, false); |
| |
| // Map the next two physical pages after supervisor's space into supervisor's |
| // virtual memory space. |
| uint32_t super_virtual_size = &__super_virtual_end - &__super_virtual_start; |
| uint32_t super_virtual_size_padded = |
| (super_virtual_size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); |
| machine_map_region( |
| (uint32_t)&__SUPER_VIRTUAL_ROM + super_virtual_size_padded, |
| (uint32_t)&__super_virtual_start + super_virtual_size_padded, |
| (uint32_t)&__super_virtual_start + super_virtual_size_padded + |
| (PAGE_SIZE * 2), |
| supervisor_l1pt, supervisor_l2pt, false); |
| |
| uint32_t unused_supervisor_paddr = |
| (uint32_t)&__SUPER_VIRTUAL_ROM + super_virtual_size_padded; |
| // This code enables the timer interrupt, followed by an ecall. |
| // When placed near a page boundary with the timer already expired, |
| // this should cause an interrupt during a page table walk. |
| uint32_t enable_isr_code[] = { |
| 0x02000513, // li a0,32 |
| 0x10452073, // csrs sie,a0 |
| 0x00000073, // ecall |
| }; |
| // Offset the code such that it straddles the page boundary. |
| uint32_t offset = PAGE_SIZE - (sizeof(uint32_t) * 2); |
| uint32_t target_paddr = unused_supervisor_paddr + offset; |
| memcpy((uint32_t *)target_paddr, enable_isr_code, sizeof(enable_isr_code)); |
| supervisor_custom_fn_addr = |
| (uint32_t)&__super_virtual_start + super_virtual_size_padded + offset; |
| |
| // Calculate the value of the SATP register. |
| // We enable SV32 virtual memory, and store the page |
| // containing our page tables. |
| uint32_t satp = (uint32_t)(0x1llu << 31) | ((uint32_t)supervisor_l1pt >> 12); |
| |
| // Delegate the timer interrupt to supervisor mode. |
| uint32_t mideleg = (1 << 5); |
| |
| // Wait until the timer interrupt 'expires'. That way, we will receive |
| // it immediately upon enabling supervisor interrupts. |
| dif_rv_timer_irq_state_snapshot_t snapshot = 0; |
| do { |
| CHECK_DIF_OK(dif_rv_timer_irq_get_state(&rv_timer, 0, &snapshot)); |
| } while (!snapshot); |
| |
| // Execute supervisor_custom_fn_addr_fn in supervisor mode, after enabling |
| // virtual memory. |
| supervisor_trampoline((uint32_t)supervisor_custom_fn_addr, (uint32_t)satp, 0, |
| (uint32_t)stvec | 1, (uint32_t)mideleg); |
| __builtin_unreachable(); |
| } |