diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 6162625f..0d69b9c1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,6 +17,8 @@ jobs: tests: true - target: aarch64-unknown-none.json tests: false + - target: riscv64gcv-unknown-none-elf.json + tests: false steps: - name: Code checkout uses: actions/checkout@v2 diff --git a/Cargo.toml b/Cargo.toml index 6c42ccfb..d8f21e99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,10 @@ chrono = { version = "0.4", default-features = false } uart_16550 = "0.2.18" x86_64 = "0.14.10" +[target.'cfg(target_arch = "riscv64")'.dependencies] +chrono = { version = "0.4", default-features = false } +fdt = "0.1.5" + [dev-dependencies] dirs = "5.0.0" rand = "0.8.5" diff --git a/README.md b/README.md index 18b7613d..65952570 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,6 @@ This project was originally developed using currently support resetting the virtio block device it is not possible to boot all the way into the OS. -## Building - -To compile: - -cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem - -The result will be in: - -target/x86_64-unknown-none/release/hypervisor-fw - ## Features * virtio (PCI) block support @@ -44,7 +34,23 @@ target/x86_64-unknown-none/release/hypervisor-fw * PE32+ loader * Minimal EFI environment (sufficient to boot shim + GRUB2 as used by Ubuntu) -## Running +## x86-64 Support + +### Building + +To compile: + +``` +cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem +``` + +The result will be in: + +``` +target/x86_64-unknown-none/release/hypervisor-fw +``` + +### Running Works with Cloud Hypervisor and QEMU via their PVH loaders as an alternative to the Linux kernel. @@ -52,7 +58,7 @@ the Linux kernel. Cloud Hypervisor and QEMU are currently the primary development targets for the firmware although support for other VMMs will be considered. -### Cloud Hypervisor +#### Cloud Hypervisor As per [getting started](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/README.md#2-getting-started) @@ -73,7 +79,7 @@ $ ./cloud-hypervisor/target/release/cloud-hypervisor \ $ popd ``` -### QEMU +#### QEMU Use the QEMU `-kernel` parameter to specify the path to the firmware. @@ -88,6 +94,39 @@ $ qemu-system-x86_64 -machine q35,accel=kvm -cpu host,-vmx -m 1G\ -device virtio-blk-pci,drive=os,disable-legacy=on ``` +## RISC-V Support + +Experimental RISC-V support is available. This is currently designed to run as a +payload from OpenSBI under QEMU virt. It is expected wider platform support +will become available in the future. + +### Building + +To compile: + +``` +cargo build --release --target riscv64gcv-unknown-none-elf.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem +``` + +The result will be in: + +``` +target/riscv64gcv-unknown-none-elf/release/hypervisor-fw +``` + +### Running + +Currently only QEMU has been tested. + +#### QEMU + +``` +$ qemu-system-riscv64 -M virt -cpu rv64 -smp 1 -m 1024 \ + -nographic -kernel target/riscv64gcv-unknown-none-elf/release/hypervisor-fw \ + -drive id=mydrive,file=root.img,format=raw \ + -device virtio-blk-pci,drive=mydrive,disable-legacy=on +``` + ## Testing "cargo test" needs disk images from make-test-disks.sh diff --git a/riscv64gcv-unknown-none-elf.json b/riscv64gcv-unknown-none-elf.json new file mode 100644 index 00000000..de0dabd5 --- /dev/null +++ b/riscv64gcv-unknown-none-elf.json @@ -0,0 +1,21 @@ +{ + "arch": "riscv64", + "code-model": "medium", + "cpu": "generic-rv64", + "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n64-S128", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "features": "+m,+a,+f,+d,+c,+v", + "is-builtin": false, + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "llvm-abiname": "lp64d", + "llvm-target": "riscv64", + "max-atomic-width": 64, + "panic-strategy": "abort", + "relocation-model": "static", + "target-pointer-width": "64", + "pre-link-args": { + "ld.lld": ["--script=riscv64gcv-unknown-none-elf.ld"] + } +} diff --git a/riscv64gcv-unknown-none-elf.ld b/riscv64gcv-unknown-none-elf.ld new file mode 100644 index 00000000..b00bc5e4 --- /dev/null +++ b/riscv64gcv-unknown-none-elf.ld @@ -0,0 +1,46 @@ +ENTRY(ram64_start) + +/* OpenSBI loads here */ +ram_min = 0x80200000; + +SECTIONS +{ + /* Mapping the program headers and note into RAM makes the file smaller. */ + . = ram_min; + + /* These sections are mapped into RAM from the file. Omitting :ram from + later sections avoids emitting empty sections in the final binary. */ + code_start = .; + .text.boot : { *(.text.boot) } + .text : { *(.text .text.*) } + . = ALIGN(4K); + code_end = .; + + data_start = .; + + .data : { + . = ALIGN(4096); + *(.data .data.*) + . = ALIGN(8); + PROVIDE(__global_pointer$ = . + 0x800); + } + + .rodata : { *(.rodata .rodata.*) } + + /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ + .bss : { + *(.bss .bss.*) + } + . = ALIGN(4K); + data_end = .; + + stack_start = .; + .stack (NOLOAD) : ALIGN(4K) { . += 128K; } + stack_end = .; + + /* Strip symbols from the output binary (comment out to get symbols) */ + /DISCARD/ : { + *(.symtab) + *(.strtab) + } +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index f98be70b..f3cc2fcd 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -6,3 +6,6 @@ pub mod aarch64; #[cfg(target_arch = "x86_64")] pub mod x86_64; + +#[cfg(target_arch = "riscv64")] +pub mod riscv64; diff --git a/src/arch/riscv64/asm.rs b/src/arch/riscv64/asm.rs new file mode 100644 index 00000000..797b84c2 --- /dev/null +++ b/src/arch/riscv64/asm.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 Rivos Inc. + +use core::arch::global_asm; + +global_asm!(include_str!("ram64.s")); diff --git a/src/arch/riscv64/layout.rs b/src/arch/riscv64/layout.rs new file mode 100644 index 00000000..68f9e908 --- /dev/null +++ b/src/arch/riscv64/layout.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo +// Copyright (c) 2021-2022 Andre Richter +// Copyright (C) 2023 Rivos Inc. + +use core::{cell::UnsafeCell, ops::Range}; + +use crate::layout::{MemoryAttribute, MemoryDescriptor, MemoryLayout}; + +extern "Rust" { + static code_start: UnsafeCell<()>; + static code_end: UnsafeCell<()>; + static data_start: UnsafeCell<()>; + static data_end: UnsafeCell<()>; + static stack_start: UnsafeCell<()>; + static stack_end: UnsafeCell<()>; +} + +pub fn code_range() -> Range { + unsafe { (code_start.get() as _)..(code_end.get() as _) } +} + +pub fn data_range() -> Range { + unsafe { (data_start.get() as _)..(data_end.get() as _) } +} + +pub fn stack_range() -> Range { + unsafe { (stack_start.get() as _)..(stack_end.get() as _) } +} + +pub fn reserved_range() -> Range { + 0x8000_0000..0x8020_0000 +} + +const NUM_MEM_DESCS: usize = 4; + +pub static MEM_LAYOUT: MemoryLayout = [ + MemoryDescriptor { + name: "Code", + range: code_range, + attribute: MemoryAttribute::Code, + }, + MemoryDescriptor { + name: "Data", + range: data_range, + attribute: MemoryAttribute::Data, + }, + MemoryDescriptor { + name: "Stack", + range: stack_range, + attribute: MemoryAttribute::Data, + }, + MemoryDescriptor { + name: "SBI", + range: reserved_range, + attribute: MemoryAttribute::Unusable, + }, +]; diff --git a/src/arch/riscv64/mod.rs b/src/arch/riscv64/mod.rs new file mode 100644 index 00000000..60751062 --- /dev/null +++ b/src/arch/riscv64/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 Rivos Inc. + +pub mod asm; +pub mod layout; diff --git a/src/arch/riscv64/ram64.s b/src/arch/riscv64/ram64.s new file mode 100644 index 00000000..91bdafd2 --- /dev/null +++ b/src/arch/riscv64/ram64.s @@ -0,0 +1,24 @@ +// Copyright (c) 2021 by Rivos Inc. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +.option norvc + +.section .text.boot + +// The entry point for the boot CPU. +.global ram64_start +ram64_start: + +.option push +.option norelax + la gp, __global_pointer$ +.option pop + csrw sstatus, zero + csrw sie, zero + + la sp, stack_end + call rust64_start +wfi_loop: + wfi + j wfi_loop diff --git a/src/bootinfo.rs b/src/bootinfo.rs index fbadfbf7..d942deb8 100644 --- a/src/bootinfo.rs +++ b/src/bootinfo.rs @@ -11,8 +11,8 @@ pub trait Info { fn rsdp_addr(&self) -> Option { None } - // Address of FDT to use for booting if present - fn fdt_addr(&self) -> Option { + // Address/size of FDT used for booting + fn fdt_reservation(&self) -> Option { None } // The kernel command line (not including null terminator) diff --git a/src/delay.rs b/src/delay.rs index 086c9b38..5b505bab 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -3,13 +3,11 @@ // Copyright (C) 2018 Google LLC use core::arch::asm; +#[cfg(target_arch = "riscv64")] +use core::arch::riscv64::pause; #[cfg(target_arch = "x86_64")] use core::arch::x86_64::_rdtsc; -const NSECS_PER_SEC: u64 = 1000000000; -const CPU_KHZ_DEFAULT: u64 = 200; -const PAUSE_THRESHOLD_TICKS: u64 = 150; - #[cfg(target_arch = "aarch64")] #[inline] unsafe fn rdtsc() -> u64 { @@ -18,6 +16,13 @@ unsafe fn rdtsc() -> u64 { value } +#[cfg(target_arch = "riscv64")] +unsafe fn rdtsc() -> u64 { + let r: u64; + unsafe { asm!("csrr {rd}, time", rd = out(reg) r) }; + r +} + #[cfg(target_arch = "x86_64")] #[inline] unsafe fn rdtsc() -> u64 { @@ -37,6 +42,13 @@ unsafe fn pause() { } pub fn ndelay(ns: u64) { + #[cfg(not(target_arch = "riscv64"))] + const CPU_KHZ_DEFAULT: u64 = 200; + #[cfg(target_arch = "riscv64")] + const CPU_KHZ_DEFAULT: u64 = 1_000_000; /* QEMU currently defines as 1GHz */ + const NSECS_PER_SEC: u64 = 1_000_000_000; + const PAUSE_THRESHOLD_TICKS: u64 = 150; + let delta = ns * CPU_KHZ_DEFAULT / NSECS_PER_SEC; let mut pause_delta = 0; unsafe { diff --git a/src/efi/mod.rs b/src/efi/mod.rs index e14181e1..eb245cfd 100644 --- a/src/efi/mod.rs +++ b/src/efi/mod.rs @@ -34,6 +34,9 @@ use r_efi::{ system::{ConfigurationTable, RuntimeServices}, }; +#[cfg(target_arch = "riscv64")] +use r_efi::{eficall, eficall_abi}; + use crate::bootinfo; use crate::layout; use crate::rtc; @@ -50,6 +53,8 @@ use var::VariableAllocator; pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTAA64.EFI"; #[cfg(target_arch = "x86_64")] pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTX64.EFI"; +#[cfg(target_arch = "riscv64")] +pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTRISCV64.EFI"; #[derive(Copy, Clone, PartialEq, Eq)] enum HandleType { @@ -854,11 +859,45 @@ pub extern "efiapi" fn locate_handle_buffer( Status::UNSUPPORTED } +#[cfg(target_arch = "riscv64")] +#[repr(C)] +struct RiscVBootProtocol { + revision: u64, + get_boot_hart_id: eficall! {fn(*const RiscVBootProtocol, *mut u64) -> Status }, +} + +#[cfg(target_arch = "riscv64")] +extern "efiapi" fn get_boot_hart_id(_: *const RiscVBootProtocol, hart: *mut u64) -> Status { + unsafe { *hart = 0 }; + Status::SUCCESS +} + +#[cfg(target_arch = "riscv64")] +const RISC_V_BOOT_PROTOCOL: RiscVBootProtocol = RiscVBootProtocol { + revision: 0, + get_boot_hart_id, +}; + +#[cfg(target_arch = "riscv64")] +pub const RISV_V_BOOT_PROTOCOL_GUID: Guid = Guid::from_fields( + 0xccd15fec, + 0x6f73, + 0x4eec, + 0x83, + 0x95, + &[0x3e, 0x69, 0xe4, 0xb9, 0x40, 0xbf], +); + pub extern "efiapi" fn locate_protocol( - _: *mut Guid, + _guid: *mut Guid, _: *mut c_void, - _: *mut *mut c_void, + _out: *mut *mut c_void, ) -> Status { + #[cfg(target_arch = "riscv64")] + if unsafe { *_guid } == RISV_V_BOOT_PROTOCOL_GUID { + unsafe { *_out = &RISC_V_BOOT_PROTOCOL as *const RiscVBootProtocol as *mut c_void }; + return Status::SUCCESS; + } // XXX: A recent version of Linux kernel fails to boot if EFI_UNSUPPORTED returned. Status::NOT_FOUND } @@ -957,6 +996,15 @@ fn populate_allocator(info: &dyn bootinfo::Info, image_address: u64, image_size: ); } + if let Some(fdt_entry) = info.fdt_reservation() { + ALLOCATOR.borrow_mut().allocate_pages( + efi::ALLOCATE_ADDRESS, + efi::UNUSABLE_MEMORY, + (fdt_entry.size + 4095) / 4096, + fdt_entry.addr, + ); + } + // Add the loaded binary ALLOCATOR.borrow_mut().allocate_pages( efi::ALLOCATE_ADDRESS, @@ -1078,7 +1126,7 @@ pub fn efi_exec( let mut ct_index = 0; // Populate with FDT table if present - if let Some(fdt_addr) = info.fdt_addr() { + if let Some(fdt_entry) = info.fdt_reservation() { ct[ct_index] = efi::ConfigurationTable { vendor_guid: Guid::from_fields( 0xb1b621d5, @@ -1088,7 +1136,7 @@ pub fn efi_exec( 0x0b, &[0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0], ), - vendor_table: fdt_addr as *const u64 as *mut _, + vendor_table: fdt_entry.addr as *const u64 as *mut _, }; ct_index += 1; } diff --git a/src/fdt.rs b/src/fdt.rs index 0284761b..b823c555 100644 --- a/src/fdt.rs +++ b/src/fdt.rs @@ -10,7 +10,7 @@ use crate::{ pub struct StartInfo<'a> { acpi_rsdp_addr: Option, - fdt_addr: u64, + fdt_entry: MemoryEntry, fdt: Fdt<'a>, kernel_load_addr: u64, memory_layout: &'static [MemoryDescriptor], @@ -32,10 +32,14 @@ impl StartInfo<'_> { } }; - let fdt_addr = ptr as u64; + let fdt_entry = MemoryEntry { + addr: ptr as u64, + size: fdt.total_size() as u64, + entry_type: EntryType::Reserved, + }; Self { - fdt_addr, + fdt_entry, fdt, acpi_rsdp_addr, kernel_load_addr, @@ -62,8 +66,8 @@ impl Info for StartInfo<'_> { self.acpi_rsdp_addr } - fn fdt_addr(&self) -> Option { - Some(self.fdt_addr) + fn fdt_reservation(&self) -> Option { + Some(self.fdt_entry) } fn cmdline(&self) -> &[u8] { diff --git a/src/main.rs b/src/main.rs index 8480d8d4..f2b6fe4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,9 @@ #![feature(asm_const)] #![feature(alloc_error_handler)] -#![feature(stmt_expr_attributes)] #![feature(slice_take)] +#![feature(stdsimd)] +#![feature(stmt_expr_attributes)] #![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_main)] #![cfg_attr(test, allow(unused_imports, dead_code))] @@ -44,7 +45,7 @@ mod coreboot; mod delay; mod efi; mod fat; -#[cfg(target_arch = "aarch64")] +#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] mod fdt; #[cfg(all(test, feature = "integration_tests"))] mod integration; @@ -57,8 +58,12 @@ mod pe; #[cfg(target_arch = "x86_64")] mod pvh; mod rtc; +#[cfg(target_arch = "riscv64")] +mod rtc_goldfish; #[cfg(target_arch = "aarch64")] mod rtc_pl031; +#[cfg(target_arch = "riscv64")] +mod uart_mmio; #[cfg(target_arch = "aarch64")] mod uart_pl011; mod virtio; @@ -183,6 +188,43 @@ pub extern "C" fn rust64_start(x0: *const u8) -> ! { main(&info) } +#[cfg(target_arch = "riscv64")] +#[no_mangle] +pub extern "C" fn rust64_start(a0: u64, a1: *const u8) -> ! { + use crate::bootinfo::{EntryType, Info, MemoryEntry}; + + serial::PORT.borrow_mut().init(); + + log!("Starting on RV64 0x{:x} 0x{:x}", a0, a1 as u64,); + + let info = fdt::StartInfo::new( + a1, + None, + 0x8040_0000, + &crate::arch::riscv64::layout::MEM_LAYOUT[..], + Some(MemoryEntry { + addr: 0x4000_0000, + size: 2 << 20, + entry_type: EntryType::Reserved, + }), + ); + + for i in 0..info.num_entries() { + let region = info.entry(i); + log!( + "Memory region {}MiB@0x{:x}", + region.size / 1024 / 1024, + region.addr + ); + } + + if let Some((base, length)) = info.find_compatible_region(&["pci-host-ecam-generic"]) { + pci::init(base as u64, length as u64); + } + + main(&info); +} + fn main(info: &dyn bootinfo::Info) -> ! { log!("\nBooting with {}", info.name()); diff --git a/src/pe.rs b/src/pe.rs index 699bcc44..3bc79f44 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -43,7 +43,14 @@ impl<'a> Loader<'a> { #[cfg(target_arch = "x86_64")] const MACHINE_TYPE: u16 = 0x8664; - #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] + #[cfg(target_arch = "riscv64")] + const MACHINE_TYPE: u16 = 0x5064; + + #[cfg(any( + target_arch = "aarch64", + target_arch = "x86_64", + target_arch = "riscv64" + ))] const OPTIONAL_HEADER_MAGIC: u16 = 0x20b; // PE32+ pub fn new(file: &'a mut dyn crate::fat::Read) -> Loader { diff --git a/src/rtc.rs b/src/rtc.rs index 3bb63967..61791b5e 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -6,3 +6,6 @@ pub use crate::rtc_pl031::{read_date, read_time}; #[cfg(target_arch = "x86_64")] pub use crate::cmos::{read_date, read_time}; + +#[cfg(target_arch = "riscv64")] +pub use crate::rtc_goldfish::{read_date, read_time}; diff --git a/src/rtc_goldfish.rs b/src/rtc_goldfish.rs new file mode 100644 index 00000000..b6b00851 --- /dev/null +++ b/src/rtc_goldfish.rs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 Rivos Inc. + +use crate::mem::MemoryRegion; +use atomic_refcell::AtomicRefCell; +use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc}; + +// TODO: Fill from FDT +const RTC_GOLDFISH_ADDRESS: u64 = 0x101000; +static RTC_GOLDFISH: AtomicRefCell = + AtomicRefCell::new(RtcGoldfish::new(RTC_GOLDFISH_ADDRESS)); + +pub struct RtcGoldfish { + region: MemoryRegion, +} + +impl RtcGoldfish { + pub const fn new(base: u64) -> RtcGoldfish { + RtcGoldfish { + region: MemoryRegion::new(base, 8), + } + } + + fn read_ts(&self) -> u64 { + const NSECS_PER_SEC: u64 = 1_000_000_000; + + let low = u64::from(self.region.io_read_u32(0x0)); + let high = u64::from(self.region.io_read_u32(0x04)); + + let t = high << 32 | low; + t / NSECS_PER_SEC + } +} + +pub fn read_date() -> Result<(u8, u8, u8), ()> { + let ts = RTC_GOLDFISH.borrow_mut().read_ts(); + + let naive = NaiveDateTime::from_timestamp_opt(ts as i64, 0).ok_or(())?; + let datetime: DateTime = DateTime::from_utc(naive, Utc); + Ok(( + (datetime.year() - 2000) as u8, + datetime.month() as u8, + datetime.day() as u8, + )) +} + +pub fn read_time() -> Result<(u8, u8, u8), ()> { + let ts = RTC_GOLDFISH.borrow_mut().read_ts(); + let naive = NaiveDateTime::from_timestamp_opt(ts as i64, 0).ok_or(())?; + let datetime: DateTime = DateTime::from_utc(naive, Utc); + Ok(( + datetime.hour() as u8, + datetime.minute() as u8, + datetime.second() as u8, + )) +} diff --git a/src/serial.rs b/src/serial.rs index 38f875c2..8cb46cbb 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -25,6 +25,9 @@ use crate::{arch::aarch64::layout::map, uart_pl011::Pl011 as UartPl011}; #[cfg(target_arch = "x86_64")] use uart_16550::SerialPort as Uart16550; +#[cfg(target_arch = "riscv64")] +use crate::uart_mmio::UartMmio; + // We use COM1 as it is the standard first serial port. #[cfg(target_arch = "x86_64")] pub static PORT: AtomicRefCell = AtomicRefCell::new(unsafe { Uart16550::new(0x3f8) }); @@ -33,6 +36,12 @@ pub static PORT: AtomicRefCell = AtomicRefCell::new(unsafe { Uart1655 pub static PORT: AtomicRefCell = AtomicRefCell::new(UartPl011::new(map::mmio::PL011_START)); +// TODO: Fill from FDT? +#[cfg(target_arch = "riscv64")] +const SERIAL_PORT_ADDRESS: u64 = 0x1000_0000; +#[cfg(target_arch = "riscv64")] +pub static PORT: AtomicRefCell = AtomicRefCell::new(UartMmio::new(SERIAL_PORT_ADDRESS)); + pub struct Serial; impl fmt::Write for Serial { fn write_str(&mut self, s: &str) -> fmt::Result { diff --git a/src/uart_mmio.rs b/src/uart_mmio.rs new file mode 100644 index 00000000..a9ce21ca --- /dev/null +++ b/src/uart_mmio.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2023 Rivos Inc. + +use crate::mem::MemoryRegion; +use core::fmt; + +pub struct UartMmio { + region: MemoryRegion, +} + +impl UartMmio { + pub const fn new(base: u64) -> UartMmio { + UartMmio { + region: MemoryRegion::new(base, 8), + } + } + + fn send(&mut self, byte: u8) { + self.region.io_write_u8(0, byte) + } + + pub fn init(&mut self) {} +} + +impl fmt::Write for UartMmio { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + self.send(byte); + } + Ok(()) + } +}