diff --git a/target/earlgrey/tests/uart/BUILD.bazel b/target/earlgrey/tests/uart/BUILD.bazel new file mode 100644 index 0000000..4c0819a --- /dev/null +++ b/target/earlgrey/tests/uart/BUILD.bazel @@ -0,0 +1,169 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:app_package.bzl", "app_package") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_binary( + name = "test_uart", + srcs = [ + "test_uart.rs", + ], + edition = "2024", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + ":app_test_uart", + "//target/earlgrey/registers", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +app_package( + name = "app_test_uart", + app_name = "test_uart", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], +) + +rust_binary( + name = "test_uart_listener", + srcs = [ + "test_uart_listener.rs", + ], + edition = "2024", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + ":app_test_uart_listener", + "//target/earlgrey/registers", + "@pigweed//pw_kernel/lib/pw_assert", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +app_package( + name = "app_test_uart_listener", + app_name = "test_uart_listener", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], +) + +system_image( + name = "uart", + apps = [ + ":test_uart", + ":test_uart_listener", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +rust_binary_no_panics_test( + name = "no_panics_test", + binary = ":uart", + tags = [ + # TODO(cfrantz): Fix this test. + "disabled", + "kernel", + ], +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "uart_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":uart", +) + +opentitan_test( + name = "uart_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":uart", +) + +opentitan_test( + name = "uart_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":uart", +) + +opentitan_test( + name = "uart_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":uart", +) diff --git a/target/earlgrey/tests/uart/system.json5 b/target/earlgrey/tests/uart/system.json5 new file mode 100644 index 0000000..e63612d --- /dev/null +++ b/target/earlgrey/tests/uart/system.json5 @@ -0,0 +1,91 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "test_uart", + flash_size_bytes: 16384, + ram_size_bytes: 4096, + process: { + name: "test_uart process", + objects: [ + { + name: "ipc", + type: "channel_handler", + }, + ], + memory_mappings: [ + { + name: "uart1", + type: "device", + start_address: 0x40010000, + size_bytes: 0x40, + } + ], + threads: [ + { + name: "uart thread", + stack_size_bytes: 2048, + }, + ], + }, + }, + { + name: "test_uart_listener", + flash_size_bytes: 16384, + ram_size_bytes: 4096, + process: { + name: "test_uart_listener process", + objects: [ + { + name: "uart_interrupts", + type: "interrupt", + irqs: [ + { name: "uart1_tx_watermark", number: 10 }, + { name: "uart1_rx_watermark", number: 11 }, + { name: "uart1_tx_done", number: 12 }, + { name: "uart1_rx_overflow", number: 13 }, + { name: "uart1_rx_frame_err", number: 14 }, + { name: "uart1_rx_break_err", number: 15 }, + { name: "uart1_rx_timeout", number: 16 }, + { name: "uart1_rx_parity_err", number: 17 }, + { name: "uart1_tx_empty", number: 18 }, + ], + }, + { + name: "ipc", + type: "channel_initiator", + handler_app: "test_uart", + handler_object_name: "ipc", + }, + ], + memory_mappings: [ + { + name: "uart1", + type: "device", + start_address: 0x40010000, + size_bytes: 0x40, + } + ], + threads: [ + { + name: "uart thread", + stack_size_bytes: 2048, + }, + ], + }, + }, + ], +} diff --git a/target/earlgrey/tests/uart/target.rs b/target/earlgrey/tests/uart/target.rs new file mode 100644 index 0000000..2e253d3 --- /dev/null +++ b/target/earlgrey/tests/uart/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/uart/test_uart.rs b/target/earlgrey/tests/uart/test_uart.rs new file mode 100644 index 0000000..71f9e22 --- /dev/null +++ b/target/earlgrey/tests/uart/test_uart.rs @@ -0,0 +1,82 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use app_test_uart::handle; +use pw_status::{Error, Result, StatusCode}; +use userspace::syscall::Signals; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use registers::uart::*; + +fn read_expected_value(expected_value: u8) -> Result<()> { + // the UART listener responds on IPC with the value written to the UART. + let wait_return = syscall::object_wait(handle::IPC, Signals::READABLE, Instant::MAX)?; + + if !wait_return.pending_signals.contains(Signals::READABLE) || wait_return.user_data != 0 { + return Err(Error::Internal); + } + + let mut buffer = [0u8; 1]; + let len = syscall::channel_read(handle::IPC, 0, &mut buffer)?; + if len != 1 { + return Err(Error::OutOfRange); + }; + + if buffer[0] != expected_value { + pw_log::error!( + "UART read() wrong value {} (expected {})", + buffer[0] as u8, + expected_value as u8 + ); + return Err(Error::Internal); + } + + let response_buffer = [0u8; 0]; + syscall::channel_respond(handle::IPC, &response_buffer)?; + + Ok(()) +} + +fn test_uart_interrupts() -> Result<()> { + let mut uart1 = unsafe { Uart1::new() }; + let regs = uart1.regs_mut(); + + regs.ctrl() + .modify(|ctrl| ctrl.tx(true).rx(true).slpbk(true).nco(0xffff)); + + while !regs.status().read().rxempty() { + let _ = regs.rdata(); + } + + for txval in 65..91 { + regs.wdata().write(|w| w.wdata(txval as u32)); + read_expected_value(txval)?; + } + + Ok(()) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + let ret = test_uart_interrupts(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + if ret.is_err() { + pw_log::error!("❌ FAILED: {}", ret.status_code() as u32); + } else { + pw_log::info!("✅ PASSED"); + } + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/target/earlgrey/tests/uart/test_uart_listener.rs b/target/earlgrey/tests/uart/test_uart_listener.rs new file mode 100644 index 0000000..0b8429d --- /dev/null +++ b/target/earlgrey/tests/uart/test_uart_listener.rs @@ -0,0 +1,90 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use app_test_uart_listener::{handle, signals}; +use pw_status::{Error, Result}; +use userspace::syscall::Signals; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use registers::uart::*; + +fn handle_interrupt(uart: &mut Uart1, interrupts: Signals) -> Result<()> { + if !interrupts.contains(signals::UART1_RX_WATERMARK) { + pw_log::error!( + "Interrupt on wrong signal. {} not in {}", + signals::UART1_RX_WATERMARK.bits() as u32, + interrupts.bits() as u32 + ); + return Err(Error::FailedPrecondition); + } + + let regs = uart.regs_mut(); + if regs.status().read().rxempty() { + pw_log::error!("No data to read"); + return Err(Error::FailedPrecondition); + } + let value = u32::from(regs.rdata().read()); + + let _ = syscall::interrupt_ack(handle::UART_INTERRUPTS, interrupts); + + const SEND_BUF_LEN: usize = 1; + const RECV_BUF_LEN: usize = 0; + let mut send_buf = [0u8; SEND_BUF_LEN]; + let mut recv_buf = [0u8; RECV_BUF_LEN]; + + send_buf[0] = value as u8; + let len = syscall::channel_transact(handle::IPC, &send_buf, &mut recv_buf, Instant::MAX)?; + if len != RECV_BUF_LEN { + pw_log::error!( + "Received {} bytes, {} expected", + len as usize, + RECV_BUF_LEN as usize + ); + return Err(Error::OutOfRange); + } + Ok(()) +} + +fn wait_for_interrupts() -> Result<()> { + let mut uart1 = unsafe { Uart1::new() }; + { + let regs = uart1.regs_mut(); + // Enable RX watermark interrupt with a high watermark of 1 byte. + regs.intr_enable().modify(|en| en.rx_watermark(true)); + regs.fifo_ctrl() + .modify(|fifo| fifo.rxilvl(|lvl| lvl.rxlvl1())); + } + loop { + let wait_return = syscall::object_wait( + handle::UART_INTERRUPTS, + signals::UART1_RX_WATERMARK, + Instant::MAX, + )?; + if !wait_return + .pending_signals + .contains(signals::UART1_RX_WATERMARK) + || wait_return.user_data != 0 + { + pw_log::error!("Incorrect WaitReturn values"); + return Err(Error::Internal); + } else { + handle_interrupt(&mut uart1, wait_return.pending_signals)?; + } + } +} + +#[entry] +fn entry() -> ! { + // Since this is written as a test, shut down with the return status from `main()`. + let ret = wait_for_interrupts(); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}