refactor: unsafe only in adapter + libkos; forbid(unsafe_code) everywhere else; libkos Window (volatile, bounds-checked MMIO/DMA/shared mem), entry!, thread::spawn; MapRegion returns pages; kernel const-built (with_cores/install_platform); newtype addrs

This commit is contained in:
2026-10-27 21:43:24 +01:00
parent 83809bbe94
commit 842d14987c
107 changed files with 771 additions and 647 deletions

1
Cargo.lock generated
View File

@@ -165,6 +165,7 @@ dependencies = [
name = "hw-virtio"
version = "0.1.0"
dependencies = [
"libkos",
"types",
]

View File

@@ -1,5 +1,5 @@
use crate::hw::uart::Uart;
use crate::{KERNEL, board, mmu, platform, trap};
use crate::{board, mmu, platform, trap};
unsafe extern "C" {
static _bss_start: u8;
@@ -56,17 +56,7 @@ pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
}
let riscv_platform =
platform::RiscVPlatform::new(kernel_pt as *const mmu::PageTable as usize, harts as u8);
// SAFETY: `init_in_place` writes every field of the kernel.
unsafe {
KERNEL.init_with(|slot| {
kernel::Kernel::init_in_place(
slot,
riscv_platform,
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
board::USER_POOL_PAGES,
)
});
}
crate::kern_mut().install_platform(riscv_platform);
kernel_pt
}

View File

@@ -11,22 +11,15 @@ pub struct KernelCell<T> {
unsafe impl<T> Sync for KernelCell<T> {}
impl<T> KernelCell<T> {
pub const fn uninit() -> Self {
/// Holds `value` from the start: built at compile time, so a large `T`
/// never lives on the stack.
pub const fn new(value: T) -> Self {
Self {
initialized: AtomicBool::new(false),
inner: UnsafeCell::new(MaybeUninit::uninit()),
initialized: AtomicBool::new(true),
inner: UnsafeCell::new(MaybeUninit::new(value)),
}
}
/// Initializes in place, so a large `T` never lives on the stack.
///
/// # Safety
/// `build` must fully initialize the `T` behind the pointer.
pub unsafe fn init_with(&self, build: impl FnOnce(*mut T)) {
build(unsafe { (*self.inner.get()).as_mut_ptr() });
self.initialized.store(true, Ordering::Release);
}
/// Raw pointer to the kernel; callers must avoid aliasing `&mut`.
pub fn as_ptr(&self) -> *mut T {
debug_assert!(self.initialized.load(Ordering::Acquire));

View File

@@ -15,7 +15,15 @@ mod trap;
use hw::uart::Uart;
use kernel_cell::KernelCell;
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
/// Built at compile time; boot installs the real platform before anything
/// runs.
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> =
KernelCell::new(kernel::Kernel::with_cores(
platform::RiscVPlatform::new(0, 1),
1,
types::address::PhysicalAddress::from_raw(board::USER_POOL_BASE),
board::USER_POOL_PAGES,
));
fn kern_mut() -> &'static mut kernel::Kernel<platform::RiscVPlatform> {
// SAFETY: boot runs with the kernel lock held and before any other

View File

@@ -20,7 +20,7 @@ pub struct RiscVPlatform {
}
impl RiscVPlatform {
pub fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
pub const fn new(kernel_pt_addr: usize, num_cores: u8) -> Self {
Self {
num_cores,
kernel_pt: kernel_pt_addr,

View File

@@ -1,6 +1,7 @@
//! Prints files: `cat <file>...`.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -9,7 +10,7 @@ use libkos::fs::{FsError, Storage};
use libkos::println;
/// Where storage's buffer is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
fn cat(fs: &mut Storage, path: &str) -> Result<(), FsError> {
let h = fs.open(path.as_bytes())?;
@@ -24,8 +25,9 @@ fn cat(fs: &mut Storage, path: &str) -> Result<(), FsError> {
fs.close(h)
}
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
println!("cat: no storage");
return;

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -6,8 +7,9 @@ extern crate libkos;
use types::bounded::CapSlot;
use libkos::syscall;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
let ep = CapSlot::const_new(0);
if let Ok(syscall::Received::Call { words: [w0, w1, w2, w3], .. }) = syscall::receive_msg(ep) {

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
libkos::println!("Hello from k-os!");
}

View File

@@ -1,6 +1,7 @@
//! Lists a directory: `ls [dir]`, the root by default.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -9,10 +10,11 @@ use libkos::fs::{FsError, Storage};
use libkos::println;
/// Where storage's buffer is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
let Some(mut fs) = libkos::start::storage().and_then(|ep| Storage::attach(ep, STORAGE_BUF_VA).ok()) else {
println!("ls: no storage");
return;

View File

@@ -5,6 +5,7 @@
//! then waits for it (Ctrl+C kills it) and takes the storage back.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -35,7 +36,7 @@ const MAX_MANIFEST: usize = 512;
const PROGRAM: Limits = Limits { pages: 128, endpoints: 1, notifications: 1, regions: 1 };
const MAX_ARGS: usize = 16;
/// Where storage's buffer is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
/// The current directory, as a path on disk: no leading or trailing `/`.
struct Cwd {
@@ -227,8 +228,9 @@ impl core::fmt::Display for Joined<'_> {
}
}
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
// Everything this shell prints goes to the terminal.
libkos::start::set_log(Some(TERMINAL));
let Ok(done) = syscall::create_notification() else {

View File

@@ -1,6 +1,7 @@
//! Waits: `sleep <seconds>`. Mostly something to stop with Ctrl+C.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -11,8 +12,9 @@ use types::time::Ticks;
/// QEMU virt's timebase.
const TICKS_PER_SECOND: u64 = 10_000_000;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
let Some(seconds) = libkos::start::args().nth(1).and_then(|s| s.parse::<u64>().ok()) else {
println!("usage: sleep <seconds>");
return;

View File

@@ -3,44 +3,43 @@
#![no_std]
#![no_main]
#![forbid(unsafe_code)]
extern crate libkos;
use core::sync::atomic::{AtomicUsize, Ordering};
use libkos::syscall;
use types::address::{EntryPoint, StackPointer, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::address::VirtualAddress;
use types::bounded::CapSlot;
use types::mapping::PagePerms;
/// Counter page layout (words): 0 next free counter index, 1..=THREADS counters.
const COUNTERS_VA: usize = 0x0100_0000;
const COUNTERS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
const THREADS: usize = 4;
#[unsafe(no_mangle)]
extern "C" fn main() {
fn main() {
let page = CapSlot::const_new(0);
if syscall::map_region(page, VirtualAddress::from_raw(COUNTERS_VA), PagePerms::ReadWrite).is_err() {
let Ok(window) = libkos::window::map(page, COUNTERS_VA, PagePerms::ReadWrite) else {
libkos::println!("smptest: cannot map the counter page");
syscall::exit();
}
for _ in 1..THREADS {
let Some(stack) = PageCount::new(1).and_then(syscall::request_pages) else {
break;
};
let top = StackPointer::from_raw(stack.raw() + 4096);
syscall::spawn_thread(EntryPoint::from_raw(spin as *const () as usize), top, 0);
let words = window.atomics(0, 1 + THREADS);
for _ in 1..THREADS {
if !libkos::thread::spawn(words, spin) {
break;
}
spin();
}
spin(words);
}
extern "C" fn spin() -> ! {
// SAFETY: COUNTERS_VA maps the counter page; every access is atomic.
let word = |i: usize| unsafe { &*((COUNTERS_VA + i * 8) as *const AtomicUsize) };
let mine = 1 + word(0).fetch_add(1, Ordering::Relaxed) % THREADS;
libkos::entry!(main);
fn spin(word: &'static [AtomicUsize]) -> ! {
let mine = 1 + word[0].fetch_add(1, Ordering::Relaxed) % THREADS;
loop {
// Yield so a spinner sharing init's hart cannot starve it.
for _ in 0..1024 {
word(mine).fetch_add(1, Ordering::Relaxed);
word[mine].fetch_add(1, Ordering::Relaxed);
}
syscall::yield_now();
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod game;
pub mod renderer;

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -39,8 +40,9 @@ fn flush() {
display_send(&DisplayRequest::Flush);
}
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
if libkos::start::display().is_none() {
libkos::println!("snake: needs the display (see /bin/snake.manifest)");
return;

View File

@@ -3,6 +3,7 @@
//! a child dies. Reports the whole self-test and halts.
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -19,8 +20,8 @@ const STORAGE: CapSlot = CapSlot::const_new(0);
const INIT: CapSlot = CapSlot::const_new(1);
const CONTROL: CapSlot = CapSlot::const_new(2);
/// Where storage's buffers are mapped: the whole disk's, a narrowed one's.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const NARROW_BUF_VA: usize = 0x0190_0000;
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
const NARROW_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0190_0000);
/// Read back from the disk image by scripts/test-qemu.sh.
const NOTE: &[u8] = b"written by k-os on ext4\n";
@@ -73,7 +74,7 @@ fn narrow_storage(fs: &mut Storage, died: CapSlot) -> Result<(), &'static str> {
if inner.create(b"x").err() != Some(FsError::Denied) {
return Err("a read-only grant created a file");
}
let widened = inner.grant(b"", true).and_then(|g| Storage::attach(g, NARROW_BUF_VA + 0x10_0000));
let widened = inner.grant(b"", true).and_then(|g| Storage::attach(g, types::address::VirtualAddress::from_raw(NARROW_BUF_VA.raw() + 0x10_0000)));
if widened.and_then(|mut w| w.create(b"x")).is_ok() {
return Err("a grant was widened to write");
}
@@ -112,8 +113,9 @@ fn check() -> Result<(), &'static str> {
Ok(())
}
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
match check() {
Ok(()) => libkos::println!("SMPTEST PASS"),
Err(why) => libkos::println!("SMPTEST FAIL: {}", why),

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -9,8 +10,9 @@ use types::bounded::CapSlot;
const EP: CapSlot = CapSlot::const_new(0);
const INPUT: CapSlot = CapSlot::const_new(1);
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
// Init hands over the GPU, or nothing if the machine has none.
let dev = libkos::device::receive_device(EP);
let input = subscribe_to_input();

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
@@ -7,8 +8,9 @@ use libkos::device::{self, KIND_UART_16550, KIND_VIRTIO};
use port_input_device::InputDevice;
use types::bounded::CapSlot;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
let ep = CapSlot::new(0).unwrap();
// Init hands over the keyboard (virtio) or, failing that, the UART.
let dev = device::receive_device(ep);

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
storage_server::run();
}

View File

@@ -16,6 +16,16 @@ struct Node {
next: Option<u16>,
}
impl Node {
const EMPTY: Self = Self {
live: false,
parent: None,
first_child: None,
prev: None,
next: None,
};
}
pub struct CapTree<const N: usize> {
nodes: [Node; N],
}
@@ -31,9 +41,9 @@ fn id(i: usize) -> Option<u16> {
}
impl<const N: usize> CapTree<N> {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
nodes: [Node::default(); N],
nodes: [Node::EMPTY; N],
}
}

View File

@@ -98,7 +98,7 @@ pub struct EndpointTable {
}
impl EndpointTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
endpoints: [const { None }; MAX_ENDPOINTS],
generations: SlotGenerations::new(),

View File

@@ -10,7 +10,7 @@ pub struct WaitQueue<const N: usize = MAX_WAITERS> {
}
impl<const N: usize> WaitQueue<N> {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
entries: [None; N],
len: 0,

View File

@@ -12,7 +12,7 @@ pub struct SlotGenerations<const N: usize> {
}
impl<const N: usize> SlotGenerations<N> {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
gens: [Some(Generation::initial()); N],
}

View File

@@ -26,7 +26,7 @@ pub struct IrqRoutingTable {
}
impl IrqRoutingTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
routes: [None; MAX_IRQS],
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
// A kernel panic takes the whole system down, so kernel code may not
// contain any way to panic. Tests and proofs are exempt.
#![cfg_attr(

View File

@@ -55,8 +55,8 @@ pub struct FrameTable<const N: usize = MAX_FRAMES> {
}
impl<const N: usize> FrameTable<N> {
pub fn new(base: PhysicalAddress, total: usize) -> Self {
let total = total.min(N);
pub const fn new(base: PhysicalAddress, total: usize) -> Self {
let total = if total < N { total } else { N };
Self {
base,
total,

View File

@@ -122,7 +122,7 @@ pub struct RegionTable {
}
impl RegionTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
regions: [const { None }; MAX_REGIONS],
generations: SlotGenerations::new(),

View File

@@ -133,7 +133,7 @@ pub struct NotificationTable {
}
impl NotificationTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
slots: [const { None }; MAX_NOTIFICATIONS],
generations: SlotGenerations::new(),

View File

@@ -80,7 +80,7 @@ pub struct ProcessTable {
}
impl ProcessTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
processes: [const { None }; MAX_PROCESSES],
generations: SlotGenerations::new(),

View File

@@ -21,11 +21,23 @@ pub struct MultiCoreScheduler {
}
impl MultiCoreScheduler {
pub fn new(num_cores: u8) -> Self {
let cores = core::array::from_fn(|i| PerCoreScheduler::new(CoreId::from_raw(i as u8)));
pub const fn new(num_cores: u8) -> Self {
let mut cores = [const { PerCoreScheduler::new(CoreId::from_raw(0)) }; MAX_CORES];
// Each core numbered by its place (a const fn cannot index or map).
let mut rest: &mut [PerCoreScheduler] = &mut cores;
let mut id = 0u8;
while let Some((core, tail)) = rest.split_first_mut() {
*core = PerCoreScheduler::new(CoreId::from_raw(id));
rest = tail;
id += 1;
}
Self {
cores,
num_cores: num_cores.min(MAX_CORES as u8),
num_cores: if num_cores < MAX_CORES as u8 {
num_cores
} else {
MAX_CORES as u8
},
placements: [const { None }; MAX_SCHEDULED_THREADS],
}
}
@@ -47,6 +59,12 @@ impl MultiCoreScheduler {
self.placements.get(thread.raw() as usize)?.as_ref()
}
/// How many cores to schedule on, once the platform knows; before any
/// thread is placed.
pub fn set_num_cores(&mut self, num_cores: u8) {
self.num_cores = num_cores.min(MAX_CORES as u8);
}
pub fn num_cores(&self) -> u8 {
self.num_cores
}

View File

@@ -9,7 +9,7 @@ pub struct PerCoreScheduler {
}
impl PerCoreScheduler {
pub fn new(core: CoreId) -> Self {
pub const fn new(core: CoreId) -> Self {
Self {
core,
queue: PriorityQueue::new(),

View File

@@ -25,7 +25,7 @@ pub struct PriorityQueue {
}
impl PriorityQueue {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
lists: [ListEnds {
head: None,

View File

@@ -18,7 +18,7 @@ pub struct ThreadTable {
}
impl ThreadTable {
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
threads: [const { None }; MAX_THREADS],
generations: SlotGenerations::new(),

View File

@@ -1,29 +1,20 @@
#![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::MmioAccess;
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
use hw_virtio::block::{self, DmaAddrs};
use hw_virtio::mmio::{MmioAccess, Registers};
use hw_virtio::queue::VirtioQueue;
use port_block_device::{BlockDevice, SectorId};
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
const MMIO_BASE: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000;
const REQ_HDR_VA: usize = 0x23000;
const MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x20000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x21000);
const REQ_HDR_VA: VirtualAddress = VirtualAddress::from_raw(0x23000);
const SECTOR_SIZE: usize = 512;
struct UserMmio;
impl MmioAccess for UserMmio {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((MMIO_BASE + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((MMIO_BASE + offset) as *mut u32, val) }
}
}
pub struct VirtioBlockDevice {
mmio: UserMmio,
mmio: Registers,
queue: VirtioQueue,
dma: DmaAddrs,
sectors: usize,
@@ -33,31 +24,31 @@ impl VirtioBlockDevice {
/// Takes over the virtio block device behind `mmio`. `None` if it is
/// not one or any step fails.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, MMIO_BASE).ok()?;
let regs = UserMmio;
let regs = Registers::new(libkos::device::map_mmio(mmio, MMIO_VA).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_BLOCK
{
return None;
}
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let hdr_pa = libkos::device::dma(1, REQ_HDR_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let req = libkos::device::dma(PageCount::new(1)?, REQ_HDR_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
// Header, status byte and one sector share the request page.
let at = |offset: usize| PhysicalAddress::from_raw(req.physical.raw() + offset);
let dma = DmaAddrs {
header_pa: PhysicalAddress::from_raw(hdr_pa),
header_va: VirtualAddress::from_raw(REQ_HDR_VA),
status_pa: PhysicalAddress::from_raw(hdr_pa + 16),
status_va: VirtualAddress::from_raw(REQ_HDR_VA + 16),
data_pa: PhysicalAddress::from_raw(hdr_pa + 32),
data_va: VirtualAddress::from_raw(REQ_HDR_VA + 32),
header_pa: at(0),
header: req.window.sub(0, 16),
status_pa: at(16),
status: req.window.sub(16, 1),
data_pa: at(32),
data: req.window.sub(32, SECTOR_SIZE),
};
let capacity = regs.read(block::CONFIG_CAPACITY) as u64
| (regs.read(block::CONFIG_CAPACITY + 4) as u64) << 32;
Some(Self {
mmio: UserMmio,
queue: VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA)),
mmio: regs,
queue: VirtioQueue::new(rings.window),
dma,
sectors: usize::try_from(capacity).ok()?,
})

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
use port_framebuffer::Framebuffer;
use types::display::{Color, Dimension, ScreenCoord};

View File

@@ -1,36 +1,30 @@
#![no_std]
#![forbid(unsafe_code)]
use hw_virtio::mmio::MmioAccess;
use hw_virtio::queue::{QueueAddrs, VirtioQueue};
use hw_virtio::gpu::{self, GpuDmaAddrs};
use hw_virtio::mmio::{MmioAccess, Registers};
use hw_virtio::queue::VirtioQueue;
use libkos::Window;
use port_framebuffer::Framebuffer;
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
use types::display::{Color, Dimension, ScreenCoord};
const GPU_MMIO_VA: usize = 0x20000;
const VQUEUE_VA: usize = 0x21000;
const CMDBUF_VA: usize = 0x23000;
const FB_VA: usize = 0x100000;
const GPU_MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x20000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x21000);
const CMDBUF_VA: VirtualAddress = VirtualAddress::from_raw(0x23000);
const FB_VA: VirtualAddress = VirtualAddress::from_raw(0x100000);
const FB_BPP: usize = 4;
const RESOURCE_ID: u32 = 1;
/// When the device will not say.
const DEFAULT_SIZE: (u16, u16) = (640, 480);
struct GpuMmio;
impl MmioAccess for GpuMmio {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((GPU_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((GPU_MMIO_VA + offset) as *mut u32, val) }
}
}
pub struct VirtioFramebuffer {
mmio: GpuMmio,
mmio: Registers,
queue: VirtioQueue,
dma: GpuDmaAddrs,
/// The pixels the device scans out, `stride` bytes a row.
pixels: Window,
width: Dimension,
height: Dimension,
stride: usize,
@@ -40,24 +34,23 @@ impl VirtioFramebuffer {
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
/// scanout. `None` if it is not a GPU or any step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?;
let regs = GpuMmio;
let regs = Registers::new(libkos::device::map_mmio(mmio, GPU_MMIO_VA).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_GPU
{
return None;
}
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let cmd_pa = libkos::device::dma(1, CMDBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, hw_virtio::queue::QUEUE_SIZE as u32);
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let cmd = libkos::device::dma(PageCount::new(1)?, CMDBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, rings.physical, hw_virtio::queue::QUEUE_SIZE as u32);
let queue = VirtioQueue::new(QueueAddrs::from_base(VQUEUE_VA));
let queue = VirtioQueue::new(rings.window);
// Commands go out from the start of the page, responses come back
// in its second half.
let dma = GpuDmaAddrs {
cmd_pa: PhysicalAddress::from_raw(cmd_pa),
cmd_va: VirtualAddress::from_raw(CMDBUF_VA),
resp_pa: PhysicalAddress::from_raw(cmd_pa + 512),
cmd_pa: cmd.physical,
cmd: cmd.window,
resp_pa: PhysicalAddress::from_raw(cmd.physical.raw() + gpu::RESPONSE),
};
let (w, h) = gpu::get_display_info(&regs, &queue, &dma)
.and_then(|(w, h)| Some((u16::try_from(w).ok()?, u16::try_from(h).ok()?)))
@@ -65,19 +58,20 @@ impl VirtioFramebuffer {
.unwrap_or(DEFAULT_SIZE);
let (width, height) = (Dimension::new(w), Dimension::new(h));
let fb_bytes = w as usize * h as usize * FB_BPP;
let fb_pages = u16::try_from(fb_bytes.div_ceil(4096)).ok()?;
let fb_pa = PhysicalAddress::from_raw(libkos::device::dma(fb_pages, FB_VA).ok()?);
let fb_pages = PageCount::new(u16::try_from(fb_bytes.div_ceil(4096)).ok()?)?;
let fb = libkos::device::dma(fb_pages, FB_VA).ok()?;
let ready = gpu::create_2d(&regs, &queue, &dma, RESOURCE_ID, width, height)
&& gpu::attach_backing(&regs, &queue, &dma, RESOURCE_ID, fb_pa, fb_bytes as u32)
&& gpu::attach_backing(&regs, &queue, &dma, RESOURCE_ID, fb.physical, fb_bytes as u32)
&& gpu::set_scanout(&regs, &queue, &dma, 0, RESOURCE_ID, width, height);
if !ready {
return None;
}
Some(Self {
mmio: GpuMmio,
mmio: regs,
queue,
dma,
pixels: fb.window,
width,
height,
stride: w as usize * FB_BPP,
@@ -86,13 +80,20 @@ impl VirtioFramebuffer {
}
impl Framebuffer for VirtioFramebuffer {
fn width(&self) -> Dimension { self.width }
fn height(&self) -> Dimension { self.height }
fn width(&self) -> Dimension {
self.width
}
fn height(&self) -> Dimension {
self.height
}
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
if x.raw() >= self.width.raw() || y.raw() >= self.height.raw() { return; }
if x.raw() >= self.width.raw() || y.raw() >= self.height.raw() {
return;
}
let offset = (y.raw() as usize) * self.stride + (x.raw() as usize) * FB_BPP;
unsafe { core::ptr::write_volatile((FB_VA + offset) as *mut u32, color.raw()); }
self.pixels.write(offset, color.raw());
}
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
@@ -105,9 +106,7 @@ impl Framebuffer for VirtioFramebuffer {
fn clear(&mut self, color: Color) {
let total = self.width.raw() as usize * self.height.raw() as usize;
for i in 0..total {
unsafe { core::ptr::write_volatile((FB_VA as *mut u32).add(i), color.raw()); }
}
self.pixels.fill_u32(0, total, color.raw());
}
fn flush(&mut self) {

View File

@@ -1,33 +1,33 @@
#![no_std]
#![forbid(unsafe_code)]
use libkos::Window;
use port_input_device::{InputDevice, InputEvent, Key};
use types::address::VirtualAddress;
const UART_VA: usize = 0x20000;
const UART_VA: VirtualAddress = VirtualAddress::from_raw(0x20000);
const UART_IER_OFFSET: usize = 1;
const UART_LSR_OFFSET: usize = 5;
/// IER bit: interrupt when a received byte is waiting (cleared by reading
/// it, so `acknowledge` has nothing to do).
const IER_RX_READY: u8 = 1;
pub struct UartInputDevice;
/// The UART's registers, one byte each.
pub struct UartInputDevice {
regs: Window,
}
impl UartInputDevice {
/// Maps the 16550 UART whose region `mmio` names and enables its
/// receive interrupt.
pub fn new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, UART_VA).ok()?;
// SAFETY: UART_VA now maps the UART's registers.
unsafe { core::ptr::write_volatile((UART_VA + UART_IER_OFFSET) as *mut u8, IER_RX_READY) };
Some(Self)
let regs = libkos::device::map_mmio(mmio, UART_VA).ok()?;
regs.write(UART_IER_OFFSET, IER_RX_READY);
Some(Self { regs })
}
fn read_byte(&self) -> Option<u8> {
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) };
if lsr & 1 != 0 {
Some(unsafe { core::ptr::read_volatile(UART_VA as *const u8) })
} else {
None
}
self.has_event().then(|| self.regs.read(0))
}
fn byte_to_key(byte: u8) -> Key {
@@ -50,7 +50,6 @@ impl InputDevice for UartInputDevice {
}
fn has_event(&self) -> bool {
let lsr = unsafe { core::ptr::read_volatile((UART_VA + UART_LSR_OFFSET) as *const u8) };
lsr & 1 != 0
self.regs.read::<u8>(UART_LSR_OFFSET) & 1 != 0
}
}

View File

@@ -1,35 +1,37 @@
#![no_std]
#![forbid(unsafe_code)]
mod keymap;
use hw_virtio::mmio::MmioAccess;
use core::sync::atomic::{Ordering, fence};
use hw_virtio::mmio::{MmioAccess, Registers};
use libkos::Window;
use port_input_device::{InputDevice, InputEvent};
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::PageCount;
const INPUT_MMIO_VA: usize = 0x25000;
const VQUEUE_VA: usize = 0x26000;
const EVBUF_VA: usize = 0x28000;
const INPUT_MMIO_VA: VirtualAddress = VirtualAddress::from_raw(0x25000);
const VQUEUE_VA: VirtualAddress = VirtualAddress::from_raw(0x26000);
const EVBUF_VA: VirtualAddress = VirtualAddress::from_raw(0x28000);
const QUEUE_SIZE: usize = 32;
const EVENT_SIZE: usize = 8;
const DESC_SIZE: usize = 16;
/// Where the legacy layout puts the available and used rings.
const AVAIL: usize = QUEUE_SIZE * DESC_SIZE;
const USED: usize = 4096;
const EV_KEY: u16 = 1;
const KEY_PRESS: u32 = 1;
struct MmioInput;
impl hw_virtio::mmio::MmioAccess for MmioInput {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((INPUT_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((INPUT_MMIO_VA + offset) as *mut u32, val) }
}
}
pub struct VirtioInputDevice {
regs: Registers,
/// The queue's rings, and the buffers the device writes events into.
rings: Window,
events: Window,
events_pa: PhysicalAddress,
last_used: u16,
next_avail: u16,
evbuf_pa: usize,
shift: bool,
ctrl: bool,
}
@@ -38,87 +40,68 @@ impl VirtioInputDevice {
/// Takes over the virtio input device behind `mmio`. `None` if it is
/// not one or any step fails.
pub fn try_new(mmio: types::bounded::CapSlot) -> Option<Self> {
libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?;
let regs = MmioInput;
let regs = Registers::new(libkos::device::map_mmio(mmio, INPUT_MMIO_VA).ok()?);
if regs.read(hw_virtio::mmio::REG_MAGIC) != hw_virtio::mmio::MAGIC
|| regs.read(hw_virtio::mmio::REG_DEVICE_ID) != hw_virtio::mmio::DEVICE_ID_INPUT
{
return None;
}
let vq_pa = libkos::device::dma(2, VQUEUE_VA).ok()?;
let evbuf_pa = libkos::device::dma(1, EVBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, vq_pa, QUEUE_SIZE as u32);
let rings = libkos::device::dma(PageCount::new(2)?, VQUEUE_VA).ok()?;
let events = libkos::device::dma(PageCount::new(1)?, EVBUF_VA).ok()?;
hw_virtio::mmio::init_legacy_device(&regs, rings.physical, QUEUE_SIZE as u32);
let mut dev = Self { last_used: 0, next_avail: 0, evbuf_pa, shift: false, ctrl: false };
let mut dev = Self {
regs,
rings: rings.window,
events: events.window,
events_pa: events.physical,
last_used: 0,
next_avail: 0,
shift: false,
ctrl: false,
};
dev.fill_queue();
Some(dev)
}
/// Every buffer goes to the device, one event each.
fn fill_queue(&mut self) {
let evbuf_phys = self.evbuf_pa;
let desc_base = VQUEUE_VA;
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
for i in 0..QUEUE_SIZE {
let desc = desc_base + i * 16;
let buf_pa = evbuf_phys + i * EVENT_SIZE;
unsafe {
core::ptr::write_volatile(desc as *mut u64, buf_pa as u64);
core::ptr::write_volatile((desc + 8) as *mut u32, EVENT_SIZE as u32);
core::ptr::write_volatile((desc + 12) as *mut u16, 2); // WRITE flag
core::ptr::write_volatile((desc + 14) as *mut u16, 0); // no next
let desc = i * DESC_SIZE;
self.rings.write(desc, (self.events_pa.raw() + i * EVENT_SIZE) as u64);
self.rings.write(desc + 8, EVENT_SIZE as u32);
self.rings.write(desc + 12, 2u16); // WRITE flag
self.rings.write(desc + 14, 0u16); // no next
self.rings.write(AVAIL + 4 + i * 2, i as u16);
}
unsafe {
let ring_entry = avail_base + 4 + (i % QUEUE_SIZE) * 2;
core::ptr::write_volatile(ring_entry as *mut u16, i as u16);
}
}
unsafe {
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
core::ptr::write_volatile((avail_base + 2) as *mut u16, QUEUE_SIZE as u16);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
fence(Ordering::SeqCst);
self.rings.write(AVAIL + 2, QUEUE_SIZE as u16);
fence(Ordering::SeqCst);
self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
self.next_avail = QUEUE_SIZE as u16;
}
fn resubmit(&mut self, desc_idx: u16) {
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
let slot = self.next_avail as usize % QUEUE_SIZE;
unsafe {
core::ptr::write_volatile((avail_base + 4 + slot * 2) as *mut u16, desc_idx);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
self.rings.write(AVAIL + 4 + slot * 2, desc_idx);
fence(Ordering::SeqCst);
self.next_avail = self.next_avail.wrapping_add(1);
core::ptr::write_volatile((avail_base + 2) as *mut u16, self.next_avail);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
self.rings.write(AVAIL + 2, self.next_avail);
fence(Ordering::SeqCst);
self.regs.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
}
fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) {
let buf_va = EVBUF_VA + (desc_idx as usize) * EVENT_SIZE;
unsafe {
let ev_type = core::ptr::read_volatile(buf_va as *const u16);
let ev_code = core::ptr::read_volatile((buf_va + 2) as *const u16);
let ev_value = core::ptr::read_volatile((buf_va + 4) as *const u32);
(ev_type, ev_code, ev_value)
}
let at = desc_idx as usize % QUEUE_SIZE * EVENT_SIZE;
(self.events.read(at), self.events.read(at + 2), self.events.read(at + 4))
}
fn used_idx(&self) -> u16 {
let used_base = VQUEUE_VA + 4096;
unsafe { core::ptr::read_volatile((used_base + 2) as *const u16) }
self.rings.read(USED + 2)
}
fn used_entry_id(&self, idx: u16) -> u16 {
let used_base = VQUEUE_VA + 4096;
let entry = used_base + 4 + (idx as usize % QUEUE_SIZE) * 8;
unsafe { core::ptr::read_volatile(entry as *const u32) as u16 }
self.rings.read::<u32>(USED + 4 + (idx as usize % QUEUE_SIZE) * 8) as u16
}
}
@@ -163,8 +146,7 @@ impl InputDevice for VirtioInputDevice {
}
fn acknowledge(&mut self) {
let regs = MmioInput;
let status = regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
let status = self.regs.read(hw_virtio::mmio::REG_INTERRUPT_STATUS);
self.regs.write(hw_virtio::mmio::REG_INTERRUPT_ACK, status);
}
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#[cfg(test)]
mod tests;

View File

@@ -5,3 +5,4 @@ edition = "2024"
[dependencies]
types = { workspace = true }
libkos = { workspace = true }

View File

@@ -1,23 +1,26 @@
use crate::mmio::MmioAccess;
use crate::queue::{Descriptor, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
use libkos::Window;
use types::address::PhysicalAddress;
const SECTOR_SIZE: usize = 512;
#[repr(C)]
pub struct BlockReqHeader {
pub req_type: u32,
pub reserved: u32,
pub sector: u64,
}
/// One request's memory: the header (type, reserved, sector: 16 bytes),
/// the status byte the device writes back, and one sector of data.
pub struct DmaAddrs {
pub header_pa: PhysicalAddress,
pub header_va: VirtualAddress,
pub header: Window,
pub status_pa: PhysicalAddress,
pub status_va: VirtualAddress,
pub status: Window,
pub data_pa: PhysicalAddress,
pub data_va: VirtualAddress,
pub data: Window,
}
fn put_header(dma: &DmaAddrs, req_type: u32, sector: u32) {
dma.header.write(0, req_type);
dma.header.write(4, 0u32);
dma.header.write(8, sector as u64);
dma.status.write(0, 0xFFu8);
}
pub fn read_sector(
@@ -29,13 +32,7 @@ pub fn read_sector(
) -> bool {
if buf.len() < SECTOR_SIZE { return false; }
unsafe {
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
core::ptr::write_volatile(&raw mut (*hdr).req_type, 0);
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
}
put_header(dma, 0, sector);
let descs = [
Descriptor { addr: dma.header_pa.raw() as u64, len: 16, writable: false },
@@ -45,19 +42,8 @@ pub fn read_sector(
if !queue.submit(mmio, &descs) { return false; }
let status = unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) };
if status != 0 { return false; }
if dma.data_va.raw() != buf.as_ptr() as usize {
unsafe {
core::ptr::copy_nonoverlapping(
dma.data_va.raw() as *const u8,
buf.as_mut_ptr(),
SECTOR_SIZE,
);
}
}
if dma.status.read::<u8>(0) != 0 { return false; }
dma.data.copy_to(0, &mut buf[..SECTOR_SIZE]);
true
}
@@ -76,14 +62,8 @@ pub fn write_sector(
) -> bool {
if buf.len() < SECTOR_SIZE { return false; }
unsafe {
core::ptr::copy_nonoverlapping(buf.as_ptr(), dma.data_va.raw() as *mut u8, SECTOR_SIZE);
let hdr = dma.header_va.raw() as *mut BlockReqHeader;
core::ptr::write_volatile(&raw mut (*hdr).req_type, T_OUT);
core::ptr::write_volatile(&raw mut (*hdr).reserved, 0);
core::ptr::write_volatile(&raw mut (*hdr).sector, sector as u64);
core::ptr::write_volatile(dma.status_va.raw() as *mut u8, 0xFF);
}
dma.data.copy_from(0, &buf[..SECTOR_SIZE]);
put_header(dma, T_OUT, sector);
// The device reads the data this time; only the status is written back.
let descs = [
@@ -92,5 +72,5 @@ pub fn write_sector(
Descriptor { addr: dma.status_pa.raw() as u64, len: 1, writable: true },
];
if !queue.submit(mmio, &descs) { return false; }
unsafe { core::ptr::read_volatile(dma.status_va.raw() as *const u8) == 0 }
dma.status.read::<u8>(0) == 0
}

View File

@@ -1,103 +1,83 @@
use crate::mmio::MmioAccess;
use crate::queue::{Descriptor, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
use libkos::Window;
use types::address::PhysicalAddress;
use types::display::Dimension;
/// The command page: commands go out from its start, responses come back
/// at `RESPONSE`.
pub struct GpuDmaAddrs {
pub cmd_pa: PhysicalAddress,
pub cmd_va: VirtualAddress,
pub cmd: Window,
pub resp_pa: PhysicalAddress,
}
pub const RESPONSE: usize = 512;
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0101);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
}
write_cmd_header(&dma.cmd, 0x0101);
dma.cmd.write(24, resource_id);
dma.cmd.write(28, 1u32);
dma.cmd.write(32, width.raw() as u32);
dma.cmd.write(36, height.raw() as u32);
submit_cmd(mmio, queue, dma, 40)
}
pub fn attach_backing(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, fb_pa: PhysicalAddress, fb_size: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0106);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, resource_id);
core::ptr::write_volatile(p.add(28) as *mut u32, 1);
core::ptr::write_volatile(p.add(32) as *mut u64, fb_pa.raw() as u64);
core::ptr::write_volatile(p.add(40) as *mut u32, fb_size);
}
write_cmd_header(&dma.cmd, 0x0106);
dma.cmd.write(24, resource_id);
dma.cmd.write(28, 1u32);
dma.cmd.write(32, fb_pa.raw() as u64 as u64);
dma.cmd.write(40, fb_size);
submit_cmd(mmio, queue, dma, 48)
}
pub fn set_scanout(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, scanout_id: u32, resource_id: u32, width: Dimension, height: Dimension) -> bool {
write_cmd_header(dma.cmd_va, 0x0103);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(32) as *mut u32, width.raw() as u32);
core::ptr::write_volatile(p.add(36) as *mut u32, height.raw() as u32);
core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id);
core::ptr::write_volatile(p.add(44) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0103);
dma.cmd.write(32, width.raw() as u32);
dma.cmd.write(36, height.raw() as u32);
dma.cmd.write(40, scanout_id);
dma.cmd.write(44, resource_id);
submit_cmd(mmio, queue, dma, 48)
}
pub fn transfer_to_host(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0105);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, x);
core::ptr::write_volatile(p.add(28) as *mut u32, y);
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u64, 0);
core::ptr::write_volatile(p.add(48) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0105);
dma.cmd.write(24, x);
dma.cmd.write(28, y);
dma.cmd.write(32, w);
dma.cmd.write(36, h);
dma.cmd.write(40, 0u64);
dma.cmd.write(48, resource_id);
submit_cmd(mmio, queue, dma, 56)
}
pub fn resource_flush(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, x: u32, y: u32, w: u32, h: u32) -> bool {
write_cmd_header(dma.cmd_va, 0x0104);
unsafe {
let p = dma.cmd_va.raw() as *mut u8;
core::ptr::write_volatile(p.add(24) as *mut u32, x);
core::ptr::write_volatile(p.add(28) as *mut u32, y);
core::ptr::write_volatile(p.add(32) as *mut u32, w);
core::ptr::write_volatile(p.add(36) as *mut u32, h);
core::ptr::write_volatile(p.add(40) as *mut u32, resource_id);
}
write_cmd_header(&dma.cmd, 0x0104);
dma.cmd.write(24, x);
dma.cmd.write(28, y);
dma.cmd.write(32, w);
dma.cmd.write(36, h);
dma.cmd.write(40, resource_id);
submit_cmd(mmio, queue, dma, 48)
}
fn write_cmd_header(cmd_va: VirtualAddress, cmd_type: u32) {
unsafe {
let p = cmd_va.raw() as *mut u8;
// Volatile so the device sees the zeros.
for i in 0..128 {
core::ptr::write_volatile((p as *mut u32).add(i), 0);
}
core::ptr::write_volatile(p as *mut u32, cmd_type);
}
fn write_cmd_header(cmd: &Window, cmd_type: u32) {
cmd.fill_u32(0, 128, 0);
cmd.write(0, cmd_type);
}
pub fn get_display_info(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs) -> Option<(u32, u32)> {
write_cmd_header(dma.cmd_va, 0x0100);
write_cmd_header(&dma.cmd, 0x0100);
let descs = [
Descriptor { addr: dma.cmd_pa.raw() as u64, len: 24, writable: false },
Descriptor { addr: dma.resp_pa.raw() as u64, len: 408, writable: true },
];
if !queue.submit(mmio, &descs) { return None; }
unsafe {
let resp_va = dma.cmd_va.raw() + 512;
let resp_type = core::ptr::read_volatile(resp_va as *const u32);
if resp_type != 0x1101 { return None; }
let w = core::ptr::read_volatile((resp_va + 24 + 8) as *const u32);
let h = core::ptr::read_volatile((resp_va + 24 + 12) as *const u32);
if dma.cmd.read::<u32>(RESPONSE) != 0x1101 { return None; }
let w: u32 = dma.cmd.read(RESPONSE + 24 + 8);
let h: u32 = dma.cmd.read(RESPONSE + 24 + 12);
if w > 0 && h > 0 { Some((w, h)) } else { None }
}
}
fn submit_cmd(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, cmd_len: u32) -> bool {

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod mmio;
pub mod queue;
@@ -6,4 +7,4 @@ pub mod block;
pub mod gpu;
pub use mmio::MmioAccess;
pub use queue::{VirtioQueue, QueueAddrs};
pub use queue::VirtioQueue;

View File

@@ -32,31 +32,26 @@ pub const DEVICE_ID_INPUT: u32 = 18;
pub const PAGE_SIZE: usize = 4096;
pub struct DirectMmio {
base: usize,
}
/// A virtio-mmio device's registers: 32-bit accesses at register offsets.
pub struct Registers(libkos::Window);
impl DirectMmio {
pub fn new(base: usize) -> Self {
Self { base }
}
pub fn base(&self) -> usize {
self.base
impl Registers {
pub fn new(window: libkos::Window) -> Self {
Self(window)
}
}
impl MmioAccess for DirectMmio {
impl MmioAccess for Registers {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((self.base + offset) as *const u32) }
self.0.read(offset)
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((self.base + offset) as *mut u32, val) }
self.0.write(offset, val)
}
}
pub fn init_legacy_device(mmio: &impl MmioAccess, queue_phys: usize, queue_size: u32) {
pub fn init_legacy_device(mmio: &impl MmioAccess, queue: types::address::PhysicalAddress, queue_size: u32) {
mmio.write(REG_STATUS, 0);
while mmio.read(REG_STATUS) != 0 {}
@@ -71,30 +66,7 @@ pub fn init_legacy_device(mmio: &impl MmioAccess, queue_phys: usize, queue_size:
mmio.write(REG_QUEUE_SEL, 0);
let queue_max = mmio.read(REG_QUEUE_NUM_MAX);
mmio.write(REG_QUEUE_NUM, queue_size.min(queue_max));
mmio.write(REG_QUEUE_PFN, (queue_phys / PAGE_SIZE) as u32);
mmio.write(REG_QUEUE_PFN, (queue.raw() / PAGE_SIZE) as u32);
mmio.write(REG_STATUS, mmio.read(REG_STATUS) | STATUS_DRIVER_OK);
}
pub fn probe_slot(base: usize, device_id: u32) -> Option<DirectMmio> {
let mmio = DirectMmio::new(base);
if mmio.read(REG_MAGIC) == MAGIC && mmio.read(REG_DEVICE_ID) == device_id {
Some(mmio)
} else {
None
}
}
pub const MMIO_FIRST: usize = 0x10001000;
pub const MMIO_STRIDE: usize = 0x1000;
pub const MMIO_SLOTS: usize = 8;
/// Probes the MMIO slots, with slot 0 at `first` as the caller maps it.
pub fn scan_for_device(first: usize, device_id: u32) -> Option<DirectMmio> {
for slot in 0..MMIO_SLOTS {
if let Some(mmio) = probe_slot(first + slot * MMIO_STRIDE, device_id) {
return Some(mmio);
}
}
None
}

View File

@@ -1,24 +1,13 @@
use crate::mmio::{MmioAccess, REG_QUEUE_NOTIFY};
use core::sync::atomic::{Ordering, fence};
use libkos::Window;
pub const QUEUE_SIZE: usize = 8;
const DESC_SIZE: usize = 16;
const TIMEOUT: u32 = 10_000_000;
pub struct QueueAddrs {
pub desc: usize,
pub avail: usize,
pub used: usize,
}
impl QueueAddrs {
pub fn from_base(base: usize) -> Self {
Self {
desc: base,
avail: base + QUEUE_SIZE * DESC_SIZE,
used: base + 4096,
}
}
}
/// Where the legacy layout puts the available and used rings.
const AVAIL: usize = QUEUE_SIZE * DESC_SIZE;
const USED: usize = 4096;
pub struct Descriptor {
pub addr: u64,
@@ -26,55 +15,49 @@ pub struct Descriptor {
pub writable: bool,
}
/// One virtqueue in the legacy layout, over the two pages its rings sit in.
pub struct VirtioQueue {
addrs: QueueAddrs,
rings: Window,
}
impl VirtioQueue {
pub fn new(addrs: QueueAddrs) -> Self {
Self { addrs }
pub fn new(rings: Window) -> Self {
Self { rings }
}
/// Hands the device a chain of `descs` and waits until it is used.
pub fn submit(&self, mmio: &impl MmioAccess, descs: &[Descriptor]) -> bool {
if descs.is_empty() || descs.len() > QUEUE_SIZE {
return false;
}
unsafe {
let desc_base = self.addrs.desc as *mut u8;
let r = &self.rings;
for (i, d) in descs.iter().enumerate() {
let p = desc_base.add(i * DESC_SIZE);
core::ptr::write_volatile(p as *mut u64, d.addr);
core::ptr::write_volatile(p.add(8) as *mut u32, d.len);
let at = i * DESC_SIZE;
let last = i == descs.len() - 1;
let flags: u16 = if d.writable { 2 } else { 0 }
| if !last { 1 } else { 0 };
core::ptr::write_volatile(p.add(12) as *mut u16, flags);
core::ptr::write_volatile(p.add(14) as *mut u16, if last { 0 } else { (i + 1) as u16 });
let flags: u16 = if d.writable { 2 } else { 0 } | if !last { 1 } else { 0 };
r.write(at, d.addr);
r.write(at + 8, d.len);
r.write(at + 12, flags);
r.write(at + 14, if last { 0 } else { (i + 1) as u16 });
}
let avail = self.addrs.avail;
let avail_idx = core::ptr::read_volatile((avail + 2) as *const u16);
core::ptr::write_volatile(
(avail + 4 + (avail_idx as usize % QUEUE_SIZE) * 2) as *mut u16,
0,
);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
core::ptr::write_volatile((avail + 2) as *mut u16, avail_idx.wrapping_add(1));
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
let avail_idx: u16 = r.read(AVAIL + 2);
r.write(AVAIL + 4 + (avail_idx as usize % QUEUE_SIZE) * 2, 0u16);
fence(Ordering::SeqCst);
r.write(AVAIL + 2, avail_idx.wrapping_add(1));
fence(Ordering::SeqCst);
mmio.write(REG_QUEUE_NOTIFY, 0);
let expected = avail_idx.wrapping_add(1);
let used = self.addrs.used;
let mut timeout = TIMEOUT;
while core::ptr::read_volatile((used + 2) as *const u16) != expected {
while r.read::<u16>(USED + 2) != expected {
core::hint::spin_loop();
timeout -= 1;
if timeout == 0 { return false; }
if timeout == 0 {
return false;
}
}
true
}
}

View File

@@ -7,12 +7,13 @@ use hw_virtio::mmio::{
use libkos::device::{self, Device};
use libkos::syscall;
use types::badge::Badge;
use types::bootinfo::{BOOT_INFO_VA, BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
use types::address::VirtualAddress;
use types::bootinfo::{BootInfo, KIND_UART_16550, KIND_VIRTIO, NO_SLOT};
use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
/// Where init briefly maps a device to ask what it is (top of the window).
const PROBE_VA: usize = 0x01F0_0000;
const PROBE_VA: VirtualAddress = VirtualAddress::from_raw(0x01F0_0000);
pub struct Devices {
pub disk: Device,
@@ -36,10 +37,10 @@ fn virtio_id(region: CapSlot) -> u32 {
return 0;
};
let id = match device::map_mmio(copy, PROBE_VA) {
// SAFETY: PROBE_VA maps the device's registers until the drop below.
Ok(()) => unsafe {
let reg = |off: usize| core::ptr::read_volatile((PROBE_VA + off) as *const u32);
if reg(REG_MAGIC) == MAGIC { reg(REG_DEVICE_ID) } else { 0 }
// The registers stay mapped until the drop below.
Ok(regs) => match regs.read::<u32>(REG_MAGIC) == MAGIC {
true => regs.read(REG_DEVICE_ID),
false => 0,
},
Err(_) => 0,
};
@@ -47,10 +48,9 @@ fn virtio_id(region: CapSlot) -> u32 {
id
}
fn boot_info() -> &'static BootInfo {
// SAFETY: boot maps the BootInfo page read-only at BOOT_INFO_VA before
// init runs; readers check its magic.
unsafe { &*(BOOT_INFO_VA as *const BootInfo) }
/// What boot left for init; readers check its magic.
fn boot_info() -> BootInfo {
libkos::boot_info()
}
/// The capability that halts the machine, if boot said where it is.

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
@@ -16,7 +17,7 @@ use types::bounded::CapSlot;
use types::permission::{Permission, PermissionSet};
/// Where the buffer storage shares with init is mapped.
const STORAGE_BUF_VA: usize = 0x0180_0000;
const STORAGE_BUF_VA: types::address::VirtualAddress = types::address::VirtualAddress::from_raw(0x0180_0000);
/// Boot loads storage before init and leaves its endpoint here.
pub(crate) const STORAGE_EP: CapSlot = CapSlot::const_new(2);

View File

@@ -12,7 +12,7 @@ const USER_STACK_PAGES: usize = 4;
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
/// Where a program file is read to before it is loaded (16 pages: the
/// largest program init will load).
const SCRATCH_VA: usize = 0x30000;
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x30000);
const SCRATCH_PAGES: u16 = 16;
pub(crate) struct LoadedProcess {
@@ -51,13 +51,9 @@ pub(crate) fn load_binary(
let total_size = offset_in_page + segment.mem_size;
let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE;
let page_count = PageCount::new(seg_pages as u16)?;
let local_va = syscall::request_pages(page_count)?;
let local_ptr = local_va.raw() as *mut u8;
unsafe {
core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE);
}
let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?;
let local = libkos::window::request(page_count)?;
local.fill_u32(0, seg_pages * PAGE_SIZE / 4, 0);
let copy_len = segment.file_size.min(segment.mem_size);
let src = segment
@@ -65,15 +61,11 @@ pub(crate) fn load_binary(
.checked_add(copy_len)
.and_then(|end| elf_data.get(segment.file_offset..end));
if let Some(src) = src.filter(|_| copy_len > 0) {
// SAFETY: the pages just requested hold seg_pages * PAGE_SIZE
// bytes, which covers offset_in_page + copy_len.
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), local_ptr.add(offset_in_page), copy_len);
}
local.copy_from(offset_in_page, src);
}
for p in 0..seg_pages {
let src_page = VirtualAddress::from_raw(local_va.raw() + p * PAGE_SIZE);
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
}
@@ -95,12 +87,5 @@ pub(crate) fn load_binary(
/// A region of init's own to read program files into.
pub(crate) fn scratch() -> Option<&'static mut [u8]> {
let region = syscall::create_region(PageCount::new(SCRATCH_PAGES)?).ok()?;
syscall::map_region(
region,
VirtualAddress::from_raw(SCRATCH_VA),
types::mapping::PagePerms::ReadWrite,
)
.ok()?;
// SAFETY: the region is mapped read-write at SCRATCH_VA for good.
Some(unsafe { core::slice::from_raw_parts_mut(SCRATCH_VA as *mut u8, SCRATCH_PAGES as usize * PAGE_SIZE) })
libkos::window::map_buffer(region, SCRATCH_VA).ok()
}

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
init::run();
}

View File

@@ -9,18 +9,17 @@ use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
use types::permission::{Permission, PermissionSet};
const OBSERVE_VA: usize = 0x0140_0000;
const OBSERVE_VA: VirtualAddress = VirtualAddress::from_raw(0x0140_0000);
const THREADS: usize = 4;
/// QEMU virt's timebase is 10 MHz.
const TICKS_PER_MS: u64 = 10_000;
fn word(i: usize) -> &'static AtomicUsize {
// SAFETY: OBSERVE_VA maps the counter page for the whole test.
unsafe { &*((OBSERVE_VA + i * 8) as *const AtomicUsize) }
}
/// The spinner's page as init sees it: word 0 hands out counters, then
/// one counter per spinner thread.
type Words = &'static [AtomicUsize];
fn counters() -> [usize; THREADS] {
core::array::from_fn(|i| word(1 + i).load(Ordering::SeqCst))
fn counters(words: Words) -> [usize; THREADS] {
core::array::from_fn(|i| words[1 + i].load(Ordering::SeqCst))
}
/// Spins (yielding) until `done` or `ms` milliseconds pass.
@@ -36,9 +35,9 @@ fn wait_until(ms: u64, mut done: impl FnMut() -> bool) -> bool {
}
/// Starts a spinner on `page`; its process capability.
fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Result<CapSlot, &'static str> {
for i in 0..=THREADS {
word(i).store(0, Ordering::SeqCst);
fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot, words: Words) -> Result<CapSlot, &'static str> {
for w in words {
w.store(0, Ordering::SeqCst);
}
let spinner = crate::load::load_binary(storage, scratch, b"bin/smptest", crate::manifest::SMALL).ok_or("cannot load bin/smptest")?;
let rw = PermissionSet::empty()
@@ -50,17 +49,17 @@ fn start_spinner(storage: &mut Storage, scratch: &mut [u8], page: CapSlot) -> Re
return Err("cannot give the spinner its page");
}
crate::launch::start(&spinner, None);
if !wait_until(3000, || counters().iter().all(|c| *c > 0)) {
if !wait_until(3000, || counters(words).iter().all(|c| *c > 0)) {
return Err("spinner threads did not all start");
}
Ok(spinner.process_cap)
}
/// After `stop`, no counter may move.
fn check_stopped(what: &'static str) -> Result<(), &'static str> {
let before = counters();
fn check_stopped(words: Words, what: &'static str) -> Result<(), &'static str> {
let before = counters(words);
wait_until(200, || false);
if counters() != before {
if counters(words) != before {
return Err(what);
}
Ok(())
@@ -68,19 +67,19 @@ fn check_stopped(what: &'static str) -> Result<(), &'static str> {
fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static str> {
let page = syscall::create_region(PageCount::new(1).ok_or("page count")?).map_err(|_| "no counter page")?;
syscall::map_region(page, VirtualAddress::from_raw(OBSERVE_VA), PagePerms::ReadWrite)
.map_err(|_| "cannot map the counter page")?;
let window = libkos::window::map(page, OBSERVE_VA, PagePerms::ReadWrite).map_err(|_| "cannot map the counter page")?;
let words = window.atomics(0, 1 + THREADS);
let events = syscall::create_notification().map_err(|_| "no notification")?;
timer_is_on_time(events)?;
// Revoke must unmap the page on every hart; the spinner then faults and dies.
let spinner = start_spinner(storage, scratch, page)?;
let spinner = start_spinner(storage, scratch, page, words)?;
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
if !syscall::revoke(page) {
return Err("revoke failed");
}
check_stopped("a counter moved after its page was revoked (stale TLB)")?;
check_stopped(words, "a counter moved after its page was revoked (stale TLB)")?;
if syscall::kill(spinner) {
return Err("the spinner survived losing its page");
}
@@ -90,12 +89,12 @@ fn run_phases(storage: &mut Storage, scratch: &mut [u8]) -> Result<(), &'static
libkos::println!("smptest: revoke stops every hart: ok");
// Kill must stop threads running on other harts before it returns.
let spinner = start_spinner(storage, scratch, page)?;
let spinner = start_spinner(storage, scratch, page, words)?;
syscall::watch_death(spinner, events).map_err(|_| "cannot watch the spinner")?;
if !syscall::kill(spinner) {
return Err("kill failed");
}
check_stopped("a counter moved after its process was killed")?;
check_stopped(words, "a counter moved after its process was killed")?;
if syscall::poll(events) != Ok(1) {
return Err("nobody heard the spinner killed");
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
// A kernel panic takes the whole system down, so kernel code may not
// contain any way to panic. Tests and proofs are exempt.
#![cfg_attr(
@@ -85,6 +86,18 @@ pub struct Kernel<P: Platform> {
impl<P: Platform> Kernel<P> {
pub fn new(platform: P, pool_base: PhysicalAddress, pool_pages: usize) -> Self {
let num_cores = platform.num_cores();
Self::with_cores(platform, num_cores, pool_base, pool_pages)
}
/// The kernel as a constant, so boot can place it in a static without
/// building it on the stack: `platform` may be a placeholder until
/// `install_platform`.
pub const fn with_cores(
platform: P,
num_cores: u8,
pool_base: PhysicalAddress,
pool_pages: usize,
) -> Self {
Self {
platform,
processes: ProcessTable::new(),
@@ -97,7 +110,7 @@ impl<P: Platform> Kernel<P> {
regions: RegionTable::new(),
irqs: IrqRoutingTable::new(),
notifications: NotificationTable::new(),
ipc: [syscall::IpcState::default(); MAX_THREADS],
ipc: [syscall::IpcState::EMPTY; MAX_THREADS],
pending_results: [const { None }; MAX_THREADS],
homes: [types::id::CoreId::from_raw(0); MAX_THREADS],
tree: domain::capability::tree::CapTree::new(),
@@ -107,41 +120,11 @@ impl<P: Platform> Kernel<P> {
}
}
/// Builds the kernel in place, one table at a time, to stay off the boot stack.
///
/// # Safety
/// `slot` must be valid for writes and aligned; the old value is overwritten without drop.
pub unsafe fn init_in_place(
slot: *mut Self,
platform: P,
pool_base: PhysicalAddress,
pool_pages: usize,
) {
use core::ptr::addr_of_mut;
let num_cores = platform.num_cores();
// SAFETY: the caller guarantees `slot` is valid and aligned; each
// field is written exactly once.
unsafe {
addr_of_mut!((*slot).platform).write(platform);
addr_of_mut!((*slot).processes).write(ProcessTable::new());
addr_of_mut!((*slot).threads).write(ThreadTable::new());
addr_of_mut!((*slot).endpoints).write(EndpointTable::new());
addr_of_mut!((*slot).scheduler).write(MultiCoreScheduler::new(num_cores));
addr_of_mut!((*slot).frames).write(FrameTable::new(pool_base, pool_pages));
addr_of_mut!((*slot).budgets).write([Budget::EMPTY; MAX_PROCESSES]);
addr_of_mut!((*slot).parents).write([None; MAX_PROCESSES]);
addr_of_mut!((*slot).regions).write(RegionTable::new());
addr_of_mut!((*slot).irqs).write(IrqRoutingTable::new());
addr_of_mut!((*slot).notifications).write(NotificationTable::new());
addr_of_mut!((*slot).ipc).write([syscall::IpcState::default(); MAX_THREADS]);
addr_of_mut!((*slot).pending_results).write([const { None }; MAX_THREADS]);
addr_of_mut!((*slot).homes).write([types::id::CoreId::from_raw(0); MAX_THREADS]);
addr_of_mut!((*slot).tree).write(domain::capability::tree::CapTree::new());
addr_of_mut!((*slot).heap_cursors).write([0; MAX_PROCESSES]);
addr_of_mut!((*slot).death_watchers)
.write([[None; domain::timer::MAX_WATCHERS]; MAX_PROCESSES]);
addr_of_mut!((*slot).timers).write(domain::timer::Timers::new());
}
/// Hands over the real platform, which boot only has once the kernel
/// is placed (see `with_cores`); before anything runs.
pub fn install_platform(&mut self, platform: P) {
self.scheduler.set_num_cores(platform.num_cores());
self.platform = platform;
}
pub fn boot(

View File

@@ -47,6 +47,14 @@ pub struct IpcState {
pub(crate) bound: Option<NotificationId>,
}
impl IpcState {
pub const EMPTY: Self = Self {
outgoing: None,
reply_to: None,
bound: None,
};
}
impl<P: Platform> Kernel<P> {
pub(crate) fn ipc(&self, tid: ThreadId) -> Option<&IpcState> {
self.ipc.get(tid.raw() as usize)

View File

@@ -104,7 +104,7 @@ impl<P: Platform> Kernel<P> {
slot: CapSlot,
va: VirtualAddress,
perms: PagePerms,
) -> Result<(), KernelError> {
) -> Result<PageCount, KernelError> {
let cap = self.check_cap(pid, slot, Permission::Read)?;
let needed = match perms {
PagePerms::ReadOnly => Permission::Read,
@@ -118,7 +118,12 @@ impl<P: Platform> Kernel<P> {
return Err(KernelError::WrongObject);
};
let via = self.cap_ref(pid, slot);
self.map_region_via(id, pid, va, perms, via)
self.map_region_via(id, pid, va, perms, via)?;
// How much was mapped, so the caller never reaches past it.
self.regions
.get(id)
.map(|r| r.pages())
.ok_or(KernelError::NoSuchObject)
}
/// Physical base of the region behind `slot` (needs Read); pool regions are contiguous.

View File

@@ -96,7 +96,7 @@ impl<P: Platform> Kernel<P> {
}
Syscall::MapRegion { region, va, perms } => complete(
self.map_region_cap(pid, region, va, perms)
.map(|()| SyscallResult::Ok),
.map(|pages| SyscallResult::Value(pages.value() as usize)),
),
Syscall::RegionAddress { region } => complete(
self.region_address_cap(pid, region)

View File

@@ -171,9 +171,10 @@ fn init_can_map_a_device_through_its_cap() {
va: VirtualAddress::from_raw(0x10_0000),
perms: PagePerms::ReadWrite,
});
// It says how many pages it mapped: the device's one.
assert!(matches!(
outcome,
SyscallOutcome::Complete(SyscallResult::Ok)
SyscallOutcome::Complete(SyscallResult::Value(1))
));
let e = error_of(k.handle_syscall(Syscall::MapRegion {
region,

View File

@@ -2,7 +2,8 @@
//! driver's endpoint; drivers map MMIO and allocate DMA themselves.
use crate::syscall::{self, Received, Result};
use types::address::VirtualAddress;
use crate::window::Window;
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
@@ -65,18 +66,25 @@ pub fn give_device(endpoint: CapSlot, device: Device) -> Result<()> {
syscall::call(endpoint, [SETUP, DONE, 0, 0], None).map(|_| ())
}
/// Maps a device's MMIO region read-write at `va`.
pub fn map_mmio(region: CapSlot, va: usize) -> Result<()> {
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
/// Maps a device's MMIO region read-write at `va`: its registers.
pub fn map_mmio(region: CapSlot, va: VirtualAddress) -> Result<Window> {
crate::window::map(region, va, PagePerms::ReadWrite)
}
/// Memory a device reads and writes: where this process sees it, and the
/// physical address the device is told.
#[derive(Debug, Clone, Copy)]
pub struct Dma {
pub window: Window,
pub physical: PhysicalAddress,
}
/// Fresh zeroed, physically contiguous memory for DMA, mapped read-write
/// at `va`. Returns its physical address, for programming the device.
pub fn dma(pages: u16, va: usize) -> Result<usize> {
let pages = PageCount::new(pages).ok_or(syscall::Error::InvalidArgument)?;
/// at `va`.
pub fn dma(pages: PageCount, va: VirtualAddress) -> Result<Dma> {
let region = syscall::create_region(pages)?;
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)?;
syscall::region_address(region)
let window = crate::window::map(region, va, PagePerms::ReadWrite)?;
Ok(Dma { window, physical: syscall::region_address(region)? })
}
/// Routes `irq` to a fresh notification bound to the calling thread, so

View File

@@ -5,7 +5,6 @@
use crate::syscall;
use types::address::VirtualAddress;
use types::bounded::CapSlot;
use types::mapping::PagePerms;
use types::storage::{DIR_ENTRY_SIZE, DIR_FLAG_DIRECTORY, DIR_NAME_SIZE, MAX_PATH, op, reply};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -55,7 +54,7 @@ pub struct Storage {
impl Storage {
/// Attaches to the server behind `ep`, mapping the buffer it shares
/// at `va` (which must be free for `BUFFER_SIZE` bytes).
pub fn attach(ep: CapSlot, va: usize) -> Result<Self> {
pub fn attach(ep: CapSlot, va: VirtualAddress) -> Result<Self> {
let reply = syscall::call(ep, [op::ATTACH, 0, 0, 0], None).map_err(|_| FsError::Failed)?;
let [tag, size, _, _] = reply.words();
let cap = match reply {
@@ -65,11 +64,8 @@ impl Storage {
let (true, Some(region)) = (tag == reply::ATTACHED, cap) else {
return Err(FsError::Failed);
};
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite)
.map_err(|_| FsError::Failed)?;
// SAFETY: the region the server made, `size` bytes, is now mapped
// read-write at `va` and stays so while this process holds it.
let buf = unsafe { core::slice::from_raw_parts_mut(va as *mut u8, size) };
let mapped = crate::window::map_buffer(region, va).map_err(|_| FsError::Failed)?;
let buf = mapped.get_mut(..size).ok_or(FsError::Failed)?;
Ok(Self { ep, buf })
}

View File

@@ -14,6 +14,30 @@ pub mod spawn;
pub mod start;
pub mod terminal;
pub mod syscall;
pub mod thread;
pub mod window;
pub use window::Window;
/// Names a program's entry point, `fn()`: what libkos's `_start` calls.
/// Programs then need no `unsafe` (exporting a symbol is).
#[macro_export]
macro_rules! entry {
($main:path) => {
#[unsafe(export_name = "main")]
extern "C" fn __libkos_main() {
$main()
}
};
}
/// The BootInfo page boot maps into init, copied out; check its magic.
pub fn boot_info() -> types::bootinfo::BootInfo {
// SAFETY: boot maps the page read-only at BOOT_INFO_VA before init
// runs, and BootInfo is plain integers, valid for any bits. In any
// other process the page is unmapped and the read faults.
unsafe { core::ptr::read_volatile(types::bootinfo::BOOT_INFO_VA as *const types::bootinfo::BootInfo) }
}
#[cfg(target_os = "none")]
#[global_allocator]

View File

@@ -1,7 +1,7 @@
//! Pages and shared regions.
use super::{raw, slot_of, Result};
use types::address::VirtualAddress;
use types::address::{PhysicalAddress, VirtualAddress};
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
use types::syscall_abi::*;
@@ -43,9 +43,10 @@ pub fn create_region(pages: PageCount) -> Result<CapSlot> {
))
}
/// Maps the region behind `region` at `va`. The mapping lasts as long as
/// the capability: dropping or having it revoked unmaps it.
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<()> {
/// Maps the region behind `region` at `va`, returning how many pages that
/// is. The mapping lasts as long as the capability: dropping or having it
/// revoked unmaps it.
pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Result<PageCount> {
let perms = match perms {
PagePerms::ReadOnly => PERMS_READ_ONLY,
PagePerms::ReadWrite => PERMS_READ_WRITE,
@@ -55,11 +56,14 @@ pub fn map_region(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> Resu
SYS_MAP_REGION,
[region.value() as usize, va.raw(), perms, 0, 0, 0],
)
.map(|_| ())
.and_then(|r| {
let pages = u16::try_from(r[1]).ok().and_then(PageCount::new);
pages.ok_or(super::Error::InvalidArgument)
})
}
/// The physical base of the region behind `region`, for programming a
/// device's DMA. Pool regions are physically contiguous.
pub fn region_address(region: CapSlot) -> Result<usize> {
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| r[1])
pub fn region_address(region: CapSlot) -> Result<PhysicalAddress> {
raw(SYS_REGION_ADDRESS, [region.value() as usize, 0, 0, 0, 0, 0]).map(|r| PhysicalAddress::from_raw(r[1]))
}

View File

@@ -0,0 +1,39 @@
//! Threads in this process.
use alloc::boxed::Box;
use types::address::{EntryPoint, StackPointer};
use types::bounded::PageCount;
use crate::syscall;
const STACK_PAGES: u16 = 1;
/// What a new thread runs, handed over through its start argument.
struct Start<T: ?Sized + 'static> {
run: fn(&'static T) -> !,
arg: &'static T,
}
extern "C" fn trampoline<T: ?Sized + Sync + 'static>(start: usize) -> ! {
// SAFETY: `spawn` passed a leaked Box<Start<T>> of exactly this T, and
// only this thread ever takes it back.
let start = unsafe { Box::from_raw(start as *mut Start<T>) };
(start.run)(start.arg)
}
/// Starts a thread on a fresh one-page stack running `run(arg)`; whether
/// it started.
pub fn spawn<T: ?Sized + Sync + 'static>(arg: &'static T, run: fn(&'static T) -> !) -> bool {
let Some(stack) = PageCount::new(STACK_PAGES).and_then(syscall::request_pages) else {
return false;
};
let top = StackPointer::from_raw(stack.raw() + STACK_PAGES as usize * 4096);
let start = Box::into_raw(Box::new(Start { run, arg })) as usize;
let entry = EntryPoint::from_raw(trampoline::<T> as *const () as usize);
if syscall::spawn_thread(entry, top, start) == usize::MAX {
// SAFETY: the thread never started, so the Box is still ours.
drop(unsafe { Box::from_raw(start as *mut Start<T>) });
return false;
}
true
}

139
crates/libkos/src/window.rs Normal file
View File

@@ -0,0 +1,139 @@
//! Memory a process mapped that something else may touch too: a device's
//! registers, DMA memory a device reads and writes, pages shared with
//! another process. Only libkos makes a `Window`, over exactly what the
//! kernel said it mapped, so the rest of userspace needs no `unsafe`.
//! Every access is volatile and bounds-checked; out of range panics.
use core::sync::atomic::AtomicUsize;
use types::address::VirtualAddress;
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
const PAGE_SIZE: usize = 4096;
/// The sizes a window reads and writes in one access.
pub trait Scalar: Copy + private::Sealed {}
impl Scalar for u8 {}
impl Scalar for u16 {}
impl Scalar for u32 {}
impl Scalar for u64 {}
mod private {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
impl Sealed for u32 {}
impl Sealed for u64 {}
}
/// Maps `region` at `va` and returns a window over all of it.
pub fn map(region: CapSlot, va: VirtualAddress, perms: PagePerms) -> crate::syscall::Result<Window> {
let pages = crate::syscall::map_region(region, va, perms)?;
Ok(Window::mapped(va, pages.value() as usize * PAGE_SIZE))
}
/// Maps `region` read-write at `va` as plain memory, for a buffer only
/// this process writes while it uses it (or one it takes turns with a
/// server on, one call at a time).
pub fn map_buffer(region: CapSlot, va: VirtualAddress) -> crate::syscall::Result<&'static mut [u8]> {
let window = map(region, va, PagePerms::ReadWrite)?;
// SAFETY: mapped read-write for as long as the capability is held,
// and handed out once, at a fresh address.
Ok(unsafe { core::slice::from_raw_parts_mut(va.raw() as *mut u8, window.len()) })
}
/// `pages` fresh private pages, as a window: for filling pages that then
/// move to another process.
pub fn request(pages: PageCount) -> Option<Window> {
let va = crate::syscall::request_pages(pages)?;
Some(Window::mapped(va, pages.value() as usize * PAGE_SIZE))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Window {
base: VirtualAddress,
len: usize,
}
impl Window {
/// `len` bytes at `base`, which the caller just had the kernel map
/// for this process (and the kernel said how much).
pub(crate) const fn mapped(base: VirtualAddress, len: usize) -> Self {
Self { base, len }
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// Where it is mapped in this process.
pub fn address(&self) -> VirtualAddress {
self.base
}
/// The address of `size` bytes at `offset`, aligned for them.
fn at(&self, offset: usize, size: usize, align: usize) -> usize {
let end = offset.checked_add(size);
assert!(end.is_some_and(|e| e <= self.len), "window access out of range");
let addr = self.base.raw() + offset;
assert!(addr.is_multiple_of(align), "misaligned window access");
addr
}
pub fn read<T: Scalar>(&self, offset: usize) -> T {
let addr = self.at(offset, size_of::<T>(), align_of::<T>());
// SAFETY: in range of memory mapped for this process, aligned for
// T; T is a plain integer, valid for any bits.
unsafe { core::ptr::read_volatile(addr as *const T) }
}
pub fn write<T: Scalar>(&self, offset: usize, value: T) {
let addr = self.at(offset, size_of::<T>(), align_of::<T>());
// SAFETY: as in `read`; the window hands out no references to it.
unsafe { core::ptr::write_volatile(addr as *mut T, value) }
}
/// The `len` bytes at `offset`, as a window of their own.
pub fn sub(&self, offset: usize, len: usize) -> Window {
let base = VirtualAddress::from_raw(self.at(offset, len, 1));
Window { base, len }
}
pub fn copy_from(&self, offset: usize, src: &[u8]) {
self.at(offset, src.len(), 1);
for (i, b) in src.iter().enumerate() {
self.write(offset + i, *b);
}
}
pub fn copy_to(&self, offset: usize, dst: &mut [u8]) {
self.at(offset, dst.len(), 1);
for (i, b) in dst.iter_mut().enumerate() {
*b = self.read(offset + i);
}
}
/// `count` u32s from `offset` set to `value`.
pub fn fill_u32(&self, offset: usize, count: usize, value: u32) {
let bytes = count.checked_mul(4).expect("window fill too large");
self.at(offset, bytes, 4);
for i in 0..count {
self.write(offset + i * 4, value);
}
}
/// `count` words from `offset` as atomics, for memory shared with
/// threads or processes that only ever touch it atomically.
pub fn atomics(&self, offset: usize, count: usize) -> &'static [AtomicUsize] {
let bytes = count.checked_mul(size_of::<AtomicUsize>()).expect("too many atomics");
let addr = self.at(offset, bytes, align_of::<AtomicUsize>());
// SAFETY: mapped and aligned; AtomicUsize may be shared and is
// valid for any bits. A mapping revoked later faults on access;
// nothing else is mapped at a window's address while it lives.
unsafe { core::slice::from_raw_parts(addr as *const AtomicUsize, count) }
}
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
use types::{
address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress},

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SectorId(u32);

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
use types::display::{Color, Dimension, ScreenCoord};

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {

View File

@@ -2,24 +2,10 @@
mod tests;
use core::future::Future;
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
fn noop_raw_waker() -> RawWaker {
fn no_op(_: *const ()) {}
fn clone(_: *const ()) -> RawWaker { noop_raw_waker() }
RawWaker::new(
core::ptr::null(),
&RawWakerVTable::new(clone, no_op, no_op, no_op),
)
}
fn noop_waker() -> Waker {
unsafe { Waker::from_raw(noop_raw_waker()) }
}
use core::task::{Context, Poll, Waker};
pub fn block_on<F: Future>(future: F) -> F::Output {
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut cx = Context::from_waker(Waker::noop());
let mut future = core::pin::pin!(future);
loop {

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod codec;
pub mod executor;

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod focus;
pub mod font;

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -8,9 +8,6 @@ name = "input-server"
test = false
bench = false
[features]
default = ["qemu-virt"]
qemu-virt = []
[dependencies]
types = { workspace = true }

View File

@@ -1,2 +1 @@
pub mod mock;
pub mod qemu_uart;

View File

@@ -1,47 +0,0 @@
use port_input_device::{InputDevice, InputEvent, Key};
const UART_BASE: usize = 0x10000000;
const UART_LSR: usize = 5;
pub struct UartInputDevice {
base: usize,
}
impl UartInputDevice {
pub fn new() -> Self {
Self { base: UART_BASE }
}
fn read_byte(&self) -> Option<u8> {
let lsr = unsafe { core::ptr::read_volatile((self.base + UART_LSR) as *const u8) };
if lsr & 1 != 0 {
Some(unsafe { core::ptr::read_volatile(self.base as *const u8) })
} else {
None
}
}
fn byte_to_key(byte: u8) -> Key {
match byte {
b'w' | b'W' => Key::Up,
b's' | b'S' => Key::Down,
b'a' | b'A' => Key::Left,
b'd' | b'D' => Key::Right,
b'\r' | b'\n' => Key::Enter,
0x1B => Key::Escape,
c => Key::Char(c),
}
}
}
impl InputDevice for UartInputDevice {
fn poll(&mut self) -> Option<InputEvent> {
let byte = self.read_byte()?;
Some(InputEvent::KeyDown(Self::byte_to_key(byte)))
}
fn has_event(&self) -> bool {
let lsr = unsafe { core::ptr::read_volatile((self.base + UART_LSR) as *const u8) };
lsr & 1 != 0
}
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod adapters;
pub mod protocol;
@@ -9,10 +10,7 @@ pub mod server;
mod tests;
pub fn run() -> ! {
#[cfg(feature = "qemu-virt")]
let device = adapters::qemu_uart::UartInputDevice::new();
#[cfg(not(feature = "qemu-virt"))]
// The real devices are driven by bins/input-qemu.
let device = adapters::mock::MockInputDevice::new();
let mut srv = server::InputServer::new(device);

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
input_server::run();
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod protocol;
pub mod server;

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
log_server::run();
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod protocol;
pub mod server;

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
permission_manager::run();
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
pub mod protocol;
pub mod server;

View File

@@ -1,9 +1,11 @@
#![no_std]
#![forbid(unsafe_code)]
#![no_main]
extern crate libkos;
#[unsafe(no_mangle)]
extern "C" fn main() {
libkos::entry!(main);
fn main() {
session_server::run();
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;
@@ -18,11 +19,10 @@ use libkos::syscall;
use types::address::VirtualAddress;
use types::badge::Badge;
use types::bounded::{CapSlot, PageCount};
use types::mapping::PagePerms;
use types::permission::{Permission, PermissionSet};
/// Client buffers sit high in storage's window, clear of its heap.
const BUFFERS_VA: usize = 0x0100_0000;
const BUFFERS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
const EP: CapSlot = CapSlot::const_new(0);
@@ -33,12 +33,10 @@ struct Kernel;
impl server::Host for Kernel {
fn create(&mut self, client: usize) -> Option<(&'static mut [u8], Option<CapSlot>)> {
let va = BUFFERS_VA + client * protocol::BUFFER_SIZE;
let va = VirtualAddress::from_raw(BUFFERS_VA.raw() + client * protocol::BUFFER_SIZE);
let region = syscall::create_region(PageCount::new(protocol::BUFFER_PAGES as u16)?).ok()?;
syscall::map_region(region, VirtualAddress::from_raw(va), PagePerms::ReadWrite).ok()?;
// SAFETY: the region is mapped read-write at `va` for as long as
// the server holds `region`, which it never drops.
let buf = unsafe { core::slice::from_raw_parts_mut(va as *mut u8, protocol::BUFFER_SIZE) };
// Storage never drops `region`, so the buffer stays mapped.
let buf = libkos::window::map_buffer(region, va).ok()?;
Some((buf, Some(region)))
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
pub fn add(left: u64, right: u64) -> u64 {
left + right
}

View File

@@ -1,4 +1,5 @@
#![no_std]
#![forbid(unsafe_code)]
extern crate alloc;

Some files were not shown because too many files have changed in this diff Show More