Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b74cc88789 | |||
| d50cc08f86 | |||
| 38c9fa783d | |||
| da9991cd55 | |||
| 994a6952c0 | |||
| 3e3f57c8b4 | |||
| 04a47a6ff9 | |||
| 200f18b425 | |||
| a318bf0c2c | |||
| 07b62026fa | |||
| f372ed65c7 | |||
| cf104ecf28 | |||
| 6a1a6a8910 | |||
| 3f4e6cd128 | |||
| a887df2f07 | |||
| d129c460cc | |||
| 67187e038d | |||
| 227678bdc5 | |||
| df9e81e735 | |||
| 57908b189c | |||
| 898e465715 | |||
| 4f5572b7c6 | |||
| 0d8e117e22 | |||
| 865d358860 | |||
| 5ea133d2bd | |||
| 4b844a8774 | |||
| a7a1639ff7 | |||
| 0f9f8fc1bf | |||
| 97c591f58f | |||
| d45a8adc34 | |||
| 01da9e7ee5 | |||
| a82751c146 | |||
| fcbe412732 | |||
| b4b99915ef | |||
| afa7e4cecb | |||
| 94450e6537 | |||
| 2985f1429e | |||
| 2b160343b7 | |||
| b97db3a0c4 | |||
| bd3d4e964d | |||
| a577b2dcc4 | |||
| d0681eb589 | |||
| e965c25181 | |||
| 4ffbb8c115 | |||
| 4c3374de36 | |||
| 3ed41501cb | |||
| cd71ee25ab | |||
| a7a0c8bf2c | |||
| ed51f233ee | |||
| 61a92920c2 | |||
| 47b67fa93c | |||
| 7f939543fe | |||
| 564d10e1be | |||
| a7d89158cb | |||
| 349418ed36 | |||
| fabf4e8d3f | |||
| 4cfa1f2958 | |||
| da36ecef13 | |||
| 7c809f3b11 | |||
| 1820009dee | |||
| bf1a215730 | |||
| 3121cc9ba9 | |||
| 7c622a78f8 | |||
| 6eac5287a2 | |||
| 87c13d3920 | |||
| d582a9b58b | |||
| adb95ac52e |
Generated
+37
-3
@@ -22,9 +22,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cortex-a"
|
||||
version = "6.1.0"
|
||||
version = "7.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "509fc35485a2b4ddbacabe0bf2212cdfff88da93658608e5cc651afcb75b7733"
|
||||
checksum = "5bd95fd055d118f77d4e4d527201b6ceccd13586b19b4dac1270f7081fef0f98"
|
||||
dependencies = [
|
||||
"tock-registers",
|
||||
]
|
||||
@@ -35,6 +35,17 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6419a5c75e40011b9fe0174db3fe24006ab122fbe1b7e9cc5974b338a755c76"
|
||||
|
||||
[[package]]
|
||||
name = "enum-repr"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad30c9c0fa1aaf1ae5010dab11f1117b15d35faf62cda4bbbc53b9987950f18"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
@@ -58,7 +69,7 @@ checksum = "99a40cabc11c8258822a593f5c51f2d9f4923e715ca9e2a0630cf77ae15f390b"
|
||||
dependencies = [
|
||||
"endian-type-rs",
|
||||
"fallible-iterator",
|
||||
"memoffset",
|
||||
"memoffset 0.5.6",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"rustc_version",
|
||||
@@ -82,9 +93,11 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"cortex-a",
|
||||
"fdt-rs",
|
||||
"fs-macros",
|
||||
"kernel-macros",
|
||||
"libsys",
|
||||
"memfs",
|
||||
"multiboot2",
|
||||
"tock-registers",
|
||||
"vfs",
|
||||
]
|
||||
@@ -111,6 +124,7 @@ name = "libsys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"enum-repr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -119,6 +133,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libsys",
|
||||
"memoffset 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -139,6 +154,23 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiboot2"
|
||||
version = "0.12.2"
|
||||
source = "git+https://github.com/alnyan/multiboot2?branch=expose-extra-traits-for-iters#7e86b55fa5ab82e54978021f8022068a1591166b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.3.3"
|
||||
@@ -250,6 +282,8 @@ checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80"
|
||||
name = "user"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libsys",
|
||||
"libusr",
|
||||
]
|
||||
|
||||
|
||||
@@ -17,19 +17,24 @@ CARGO_COMMON_OPTS=
|
||||
ifeq ($(PROFILE),release)
|
||||
CARGO_COMMON_OPTS+=--release
|
||||
endif
|
||||
ifeq ($(VERBOSE),1)
|
||||
CARGO_COMMON_OPTS+=--features verbose
|
||||
endif
|
||||
|
||||
CARGO_BUILD_OPTS=$(CARGO_COMMON_OPTS) \
|
||||
--target=../etc/$(ARCH)-$(MACH).json
|
||||
ifeq ($(VERBOSE),1)
|
||||
CARGO_BUILD_OPTS+=--features verbose
|
||||
endif
|
||||
ifneq ($(MACH),)
|
||||
CARGO_BUILD_OPTS+=--features mach_$(MACH)
|
||||
endif
|
||||
|
||||
QEMU_OPTS=-s
|
||||
ifeq ($(ARCH),x86_64)
|
||||
$(error TODO)
|
||||
MACH=none
|
||||
QEMU_OPTS+=-cdrom $(O)/image.iso \
|
||||
-M q35 \
|
||||
-m 512 \
|
||||
-serial mon:stdio \
|
||||
-net none
|
||||
else
|
||||
ifeq ($(MACH),qemu)
|
||||
QEMU_OPTS+=-kernel $(O)/kernel.bin \
|
||||
@@ -45,6 +50,7 @@ endif
|
||||
ifeq ($(MACH),rpi3)
|
||||
QEMU_OPTS+=-kernel $(O)/kernel.bin \
|
||||
-initrd $(O)/initrd.img \
|
||||
-dtb etc/bcm2837-rpi-3-b-plus.dtb \
|
||||
-M raspi3b \
|
||||
-serial mon:stdio \
|
||||
-display none \
|
||||
@@ -62,10 +68,13 @@ endif
|
||||
ifeq ($(QEMU_PAUSE),1)
|
||||
QEMU_OPTS+=-S
|
||||
endif
|
||||
ifeq ($(QEMU_KVM),1)
|
||||
QEMU_OPTS+=-enable-kvm -cpu host
|
||||
endif
|
||||
|
||||
.PHONY: address error etc kernel src
|
||||
|
||||
all: kernel initrd
|
||||
all: image
|
||||
|
||||
kernel:
|
||||
cd kernel && cargo build $(CARGO_BUILD_OPTS)
|
||||
@@ -87,14 +96,34 @@ ifeq ($(MACH),orangepi3)
|
||||
$(O)/uImage
|
||||
endif
|
||||
|
||||
image: kernel initrd
|
||||
ifeq ($(ARCH),x86_64)
|
||||
mkdir -p $(O)/image/boot/grub
|
||||
cp etc/x86_64-none.grub $(O)/image/boot/grub/grub.cfg
|
||||
cp $(O)/kernel $(O)/image/boot/kernel
|
||||
cp $(O)/initrd.img $(O)/image/boot/initrd.img
|
||||
|
||||
grub-mkrescue -o $(O)/image.iso $(O)/image
|
||||
endif
|
||||
|
||||
initrd:
|
||||
cd user && cargo build \
|
||||
--target=../etc/$(ARCH)-osdev5.json \
|
||||
-Z build-std=core,alloc,compiler_builtins \
|
||||
$(CARGO_COMMON_OPTS)
|
||||
mkdir -p $(O)/rootfs/bin
|
||||
mkdir -p $(O)/rootfs/bin $(O)/rootfs/sbin $(O)/rootfs/dev $(O)/rootfs/etc $(O)/rootfs/sys
|
||||
cp etc/initrd/passwd $(O)/rootfs/etc
|
||||
cp etc/initrd/shadow $(O)/rootfs/etc
|
||||
touch $(O)/rootfs/dev/.do_not_remove
|
||||
touch $(O)/rootfs/sys/.do_not_remove
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/init $(O)/rootfs/init
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/shell $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/ls $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/cat $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/hexd $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/mkdir $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/fuzzy $(O)/rootfs/bin
|
||||
cp target/$(ARCH)-osdev5/$(PROFILE)/login $(O)/rootfs/sbin
|
||||
cd $(O)/rootfs && tar cf ../initrd.img `find -type f -printf "%P\n"`
|
||||
ifeq ($(MACH),orangepi3)
|
||||
$(MKIMAGE) \
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
root:0:0:root:/:/bin/shell
|
||||
alnyan:1000:1000:alnyan:/:/bin/shell
|
||||
@@ -0,0 +1,2 @@
|
||||
root:toor
|
||||
alnyan:
|
||||
@@ -1,3 +1,4 @@
|
||||
menuentry "OS" {
|
||||
multiboot2 /boot/kernel.elf
|
||||
multiboot2 /boot/kernel
|
||||
module2 /boot/initrd.img
|
||||
}
|
||||
|
||||
+5
-1
@@ -6,7 +6,8 @@ SECTIONS {
|
||||
. = 0x400000 + KERNEL_OFFSET;
|
||||
|
||||
.text : AT(. - KERNEL_OFFSET) {
|
||||
KEEP(*(.multiboot))
|
||||
KEEP(*(.text._multiboot))
|
||||
*(.text._entry)
|
||||
*(.text*)
|
||||
}
|
||||
|
||||
@@ -21,5 +22,8 @@ SECTIONS {
|
||||
.bss : AT(. - KERNEL_OFFSET) {
|
||||
*(COMMON)
|
||||
*(.bss*)
|
||||
. = ALIGN(4K);
|
||||
}
|
||||
|
||||
PROVIDE(__kernel_end = .);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"cpu": "x86-64",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
|
||||
|
||||
"disable-redzone": true,
|
||||
"executables": true,
|
||||
"panic-strategy": "abort",
|
||||
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "ld.lld",
|
||||
|
||||
"llvm-target": "x86_64-unknown-linux-gnu",
|
||||
"max-atomic-width": 64,
|
||||
"target-pointer-width": "64",
|
||||
|
||||
"os": "none",
|
||||
"pre-link-args": {
|
||||
"ld.lld": [ "-Tetc/x86_64-osdev5.ld" ]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
ENTRY(_start);
|
||||
|
||||
PHDRS {
|
||||
text PT_LOAD ;
|
||||
rodata PT_LOAD ;
|
||||
data PT_LOAD ;
|
||||
}
|
||||
|
||||
SECTIONS {
|
||||
. = 0x400000;
|
||||
|
||||
.text : {
|
||||
*(.text._start)
|
||||
*(.text*)
|
||||
*(.eh_frame*)
|
||||
} :text
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
} :rodata
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
.data : {
|
||||
*(.data*)
|
||||
} :data
|
||||
|
||||
.bss : {
|
||||
*(COMMON)
|
||||
*(.bss*)
|
||||
} :data
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::Bpb;
|
||||
use libsys::{
|
||||
stat::{Stat, OpenFlags},
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
error::Errno
|
||||
stat::{OpenFlags, Stat},
|
||||
};
|
||||
use vfs::{VnodeImpl, VnodeKind, VnodeRef};
|
||||
|
||||
|
||||
+1
-4
@@ -12,10 +12,7 @@ extern crate alloc;
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use core::any::Any;
|
||||
use core::cell::{Ref, RefCell};
|
||||
use libsys::{
|
||||
mem::read_le32,
|
||||
error::Errno,
|
||||
};
|
||||
use libsys::{error::Errno, mem::read_le32};
|
||||
use vfs::{BlockDevice, Filesystem, Vnode, VnodeKind, VnodeRef};
|
||||
|
||||
pub mod dir;
|
||||
|
||||
+161
-148
@@ -1,149 +1,162 @@
|
||||
extern crate proc_macro;
|
||||
extern crate syn;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
// extern crate proc_macro;
|
||||
// extern crate syn;
|
||||
// #[macro_use]
|
||||
// extern crate quote;
|
||||
//
|
||||
// use proc_macro::TokenStream;
|
||||
// use quote::ToTokens;
|
||||
// use std::collections::HashSet;
|
||||
// use syn::{parse_macro_input, ImplItem, ItemImpl, Ident};
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::ToTokens;
|
||||
use std::collections::HashSet;
|
||||
use syn::{parse_macro_input, ImplItem, ItemImpl, Ident};
|
||||
|
||||
fn impl_inode_fn<T: ToTokens>(name: &str, behavior: T) -> ImplItem {
|
||||
// TODO somehow know if current crate is vfs or not?
|
||||
ImplItem::Verbatim(match name {
|
||||
"create" => quote! {
|
||||
fn create(&mut self, _at: VnodeRef, _name: &str, kind: VnodeKind) ->
|
||||
Result<VnodeRef, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"remove" => quote! {
|
||||
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), libsys::error::Errno> {
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"lookup" => quote! {
|
||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) ->
|
||||
Result<VnodeRef, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"stat" => quote! {
|
||||
fn stat(&mut self, _at: VnodeRef, _stat: &mut libsys::stat::Stat) ->
|
||||
Result<(), libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"truncate" => quote! {
|
||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) ->
|
||||
Result<(), libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"size" => quote! {
|
||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, libsys::error::Errno> {
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"read" => quote! {
|
||||
fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) ->
|
||||
Result<usize, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"write" => quote! {
|
||||
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) ->
|
||||
Result<usize, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"open" => quote! {
|
||||
fn open(&mut self, _node: VnodeRef, _flags: libsys::stat::OpenFlags) ->
|
||||
Result<usize, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"close" => quote! {
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), libsys::error::Errno> {
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"ioctl" => quote! {
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
_cmd: libsys::ioctl::IoctlCmd,
|
||||
_ptr: usize,
|
||||
_len: usize) ->
|
||||
Result<usize, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
"is_ready" => quote! {
|
||||
fn is_ready(&mut self, _node: VnodeRef, _write: bool) ->
|
||||
Result<bool, libsys::error::Errno>
|
||||
{
|
||||
#behavior
|
||||
}
|
||||
},
|
||||
_ => panic!("TODO implement {:?}", name),
|
||||
})
|
||||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut impl_item = parse_macro_input!(input as ItemImpl);
|
||||
let mut missing = HashSet::<String>::new();
|
||||
let behavior = if attr.is_empty() {
|
||||
"unimplemented".to_string()
|
||||
} else {
|
||||
parse_macro_input!(attr as Ident).to_string()
|
||||
};
|
||||
let behavior = match behavior.as_str() {
|
||||
"unimplemented" => quote! { unimplemented!() },
|
||||
"panic" => quote! { panic!() },
|
||||
"error" => quote! { Err(libsys::error::Errno::NotImplemented) },
|
||||
_ => panic!("Unknown #[auto_inode] behavior: {:?}", behavior)
|
||||
};
|
||||
|
||||
missing.insert("create".to_string());
|
||||
missing.insert("remove".to_string());
|
||||
missing.insert("lookup".to_string());
|
||||
missing.insert("open".to_string());
|
||||
missing.insert("close".to_string());
|
||||
missing.insert("truncate".to_string());
|
||||
missing.insert("read".to_string());
|
||||
missing.insert("write".to_string());
|
||||
missing.insert("stat".to_string());
|
||||
missing.insert("size".to_string());
|
||||
missing.insert("ioctl".to_string());
|
||||
missing.insert("is_ready".to_string());
|
||||
|
||||
for item in &impl_item.items {
|
||||
match item {
|
||||
ImplItem::Method(method) => {
|
||||
let name = &method.sig.ident.to_string();
|
||||
if missing.contains(name) {
|
||||
missing.remove(name);
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected impl item"),
|
||||
}
|
||||
}
|
||||
|
||||
for item in &missing {
|
||||
impl_item
|
||||
.items
|
||||
.push(impl_inode_fn(item, behavior.clone()));
|
||||
}
|
||||
|
||||
impl_item.to_token_stream().into()
|
||||
}
|
||||
//fn impl_inode_fn<T: ToTokens>(name: &str, behavior: T) -> ImplItem {
|
||||
// // TODO somehow know if current crate is vfs or not?
|
||||
// ImplItem::Verbatim(match name {
|
||||
// "create" => quote! {
|
||||
// fn create(&mut self, _at: VnodeRef, _name: &str, kind: VnodeKind) ->
|
||||
// Result<VnodeRef, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "remove" => quote! {
|
||||
// fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), libsys::error::Errno> {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "lookup" => quote! {
|
||||
// fn lookup(&mut self, _at: VnodeRef, _name: &str) ->
|
||||
// Result<VnodeRef, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "stat" => quote! {
|
||||
// fn stat(&mut self, _at: VnodeRef) ->
|
||||
// Result<libsys::stat::Stat, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "truncate" => quote! {
|
||||
// fn truncate(&mut self, _node: VnodeRef, _size: usize) ->
|
||||
// Result<(), libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "size" => quote! {
|
||||
// fn size(&mut self, _node: VnodeRef) -> Result<usize, libsys::error::Errno> {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "read" => quote! {
|
||||
// fn read(&mut self, _node: VnodeRef, _pos: usize, _data: &mut [u8]) ->
|
||||
// Result<usize, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "write" => quote! {
|
||||
// fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) ->
|
||||
// Result<usize, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "open" => quote! {
|
||||
// fn open(&mut self, _node: VnodeRef, _flags: libsys::stat::OpenFlags) ->
|
||||
// Result<usize, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "close" => quote! {
|
||||
// fn close(&mut self, _node: VnodeRef) -> Result<(), libsys::error::Errno> {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "ioctl" => quote! {
|
||||
// fn ioctl(
|
||||
// &mut self,
|
||||
// _node: VnodeRef,
|
||||
// _cmd: libsys::ioctl::IoctlCmd,
|
||||
// _ptr: usize,
|
||||
// _len: usize) ->
|
||||
// Result<usize, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "is_ready" => quote! {
|
||||
// fn is_ready(&mut self, _node: VnodeRef, _write: bool) ->
|
||||
// Result<bool, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// "readdir" => quote! {
|
||||
// fn readdir(
|
||||
// &mut self,
|
||||
// _node: VnodeRef,
|
||||
// _pos: usize,
|
||||
// _entries: &mut [libsys::stat::DirectoryEntry]
|
||||
// ) ->
|
||||
// Result<usize, libsys::error::Errno>
|
||||
// {
|
||||
// #behavior
|
||||
// }
|
||||
// },
|
||||
// _ => panic!("TODO implement {:?}", name),
|
||||
// })
|
||||
//}
|
||||
//
|
||||
//#[proc_macro_attribute]
|
||||
//pub fn auto_inode(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
// let mut impl_item = parse_macro_input!(input as ItemImpl);
|
||||
// let mut missing = HashSet::<String>::new();
|
||||
// let behavior = if attr.is_empty() {
|
||||
// "unimplemented".to_string()
|
||||
// } else {
|
||||
// parse_macro_input!(attr as Ident).to_string()
|
||||
// };
|
||||
// let behavior = match behavior.as_str() {
|
||||
// "unimplemented" => quote! { unimplemented!() },
|
||||
// "panic" => quote! { panic!() },
|
||||
// "error" => quote! { Err(libsys::error::Errno::NotImplemented) },
|
||||
// _ => panic!("Unknown #[auto_inode] behavior: {:?}", behavior)
|
||||
// };
|
||||
//
|
||||
// missing.insert("create".to_string());
|
||||
// missing.insert("remove".to_string());
|
||||
// missing.insert("lookup".to_string());
|
||||
// missing.insert("open".to_string());
|
||||
// missing.insert("close".to_string());
|
||||
// missing.insert("truncate".to_string());
|
||||
// missing.insert("read".to_string());
|
||||
// missing.insert("write".to_string());
|
||||
// missing.insert("stat".to_string());
|
||||
// missing.insert("size".to_string());
|
||||
// missing.insert("ioctl".to_string());
|
||||
// missing.insert("is_ready".to_string());
|
||||
// missing.insert("readdir".to_string());
|
||||
//
|
||||
// for item in &impl_item.items {
|
||||
// match item {
|
||||
// ImplItem::Method(method) => {
|
||||
// let name = &method.sig.ident.to_string();
|
||||
// if missing.contains(name) {
|
||||
// missing.remove(name);
|
||||
// }
|
||||
// }
|
||||
// _ => panic!("Unexpected impl item"),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// for item in &missing {
|
||||
// impl_item
|
||||
// .items
|
||||
// .push(impl_inode_fn(item, behavior.clone()));
|
||||
// }
|
||||
//
|
||||
// impl_item.to_token_stream().into()
|
||||
//}
|
||||
|
||||
@@ -11,6 +11,9 @@ pub struct BlockRef<'a, A: BlockAllocator + Copy> {
|
||||
alloc: MaybeUninit<A>,
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait is unsafe to implement due to its direct memory management
|
||||
pub unsafe trait BlockAllocator {
|
||||
fn alloc(&self) -> *mut u8;
|
||||
/// # Safety
|
||||
|
||||
+76
-12
@@ -1,27 +1,41 @@
|
||||
use crate::{BlockAllocator, Bvec, FileInode};
|
||||
use alloc::boxed::Box;
|
||||
use libsys::error::Errno;
|
||||
use vfs::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
||||
use core::cell::RefCell;
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
stat::{DirectoryEntry, OpenFlags, Stat},
|
||||
};
|
||||
use vfs::{Vnode, VnodeCommon, VnodeCreateKind, VnodeData, VnodeDirectory, VnodeRef};
|
||||
|
||||
pub struct DirInode<A: BlockAllocator + Copy + 'static> {
|
||||
alloc: A,
|
||||
}
|
||||
|
||||
#[auto_inode]
|
||||
impl<A: BlockAllocator + Copy + 'static> VnodeImpl for DirInode<A> {
|
||||
impl<A: BlockAllocator + Copy + 'static> VnodeDirectory for DirInode<A> {
|
||||
fn create(
|
||||
&mut self,
|
||||
_parent: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeKind,
|
||||
kind: VnodeCreateKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
let vnode = Vnode::new(name, kind, Vnode::SEEKABLE);
|
||||
match kind {
|
||||
VnodeKind::Directory => vnode.set_data(Box::new(DirInode { alloc: self.alloc })),
|
||||
VnodeKind::Regular => vnode.set_data(Box::new(FileInode::new(Bvec::new(self.alloc)))),
|
||||
_ => todo!(),
|
||||
}
|
||||
Ok(vnode)
|
||||
let data = match kind {
|
||||
VnodeCreateKind::Directory => {
|
||||
VnodeData::Directory(RefCell::new(Some(Box::new(DirInode { alloc: self.alloc }))))
|
||||
}
|
||||
VnodeCreateKind::File => VnodeData::File(RefCell::new(Some(Box::new(FileInode::new(
|
||||
Bvec::new(self.alloc),
|
||||
))))),
|
||||
};
|
||||
Ok(Vnode::new(
|
||||
name,
|
||||
data,
|
||||
Vnode::SEEKABLE | Vnode::CACHE_READDIR,
|
||||
))
|
||||
// match kind {
|
||||
// _ => todo!(),
|
||||
// }
|
||||
// Ok(vnode)
|
||||
}
|
||||
|
||||
fn lookup(&mut self, _parent: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||
@@ -31,6 +45,56 @@ impl<A: BlockAllocator + Copy + 'static> VnodeImpl for DirInode<A> {
|
||||
fn remove(&mut self, _parent: VnodeRef, _name: &str) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Read directory entries into target buffer
|
||||
fn readdir(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
_pos: usize,
|
||||
_data: &mut [DirectoryEntry],
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: BlockAllocator + Copy + 'static> VnodeCommon for DirInode<A> {
|
||||
fn stat(&mut self, node: VnodeRef) -> Result<Stat, Errno> {
|
||||
let props = node.props();
|
||||
Ok(Stat {
|
||||
size: 0,
|
||||
blksize: 4096,
|
||||
mode: props.mode,
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
_cmd: IoctlCmd,
|
||||
_ptr: usize,
|
||||
_len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn ready(&mut self, _node: VnodeRef, _write: bool) -> Result<bool, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: BlockAllocator + Copy + 'static> DirInode<A> {
|
||||
|
||||
+34
-14
@@ -1,16 +1,17 @@
|
||||
use crate::{BlockAllocator, Bvec};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
stat::{OpenFlags, Stat},
|
||||
};
|
||||
use vfs::{VnodeImpl, VnodeKind, VnodeRef};
|
||||
use vfs::{VnodeCommon, VnodeFile, VnodeRef};
|
||||
|
||||
pub struct FileInode<'a, A: BlockAllocator + Copy + 'static> {
|
||||
data: Bvec<'a, A>,
|
||||
}
|
||||
|
||||
#[auto_inode]
|
||||
impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> {
|
||||
// #[auto_inode]
|
||||
impl<'a, A: BlockAllocator + Copy + 'static> VnodeCommon for FileInode<'a, A> {
|
||||
fn open(&mut self, _node: VnodeRef, _mode: OpenFlags) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
@@ -18,7 +19,37 @@ impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> {
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
||||
Ok(self.data.size())
|
||||
}
|
||||
|
||||
fn stat(&mut self, node: VnodeRef) -> Result<Stat, Errno> {
|
||||
let props = node.props();
|
||||
Ok(Stat {
|
||||
size: self.data.size() as u64,
|
||||
blksize: 4096,
|
||||
mode: props.mode,
|
||||
})
|
||||
}
|
||||
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
_cmd: IoctlCmd,
|
||||
_ptr: usize,
|
||||
_len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn ready(&mut self, _node: VnodeRef, _write: bool) -> Result<bool, Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: BlockAllocator + Copy + 'static> VnodeFile for FileInode<'a, A> {
|
||||
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
self.data.read(pos, data)
|
||||
}
|
||||
@@ -30,17 +61,6 @@ impl<'a, A: BlockAllocator + Copy + 'static> VnodeImpl for FileInode<'a, A> {
|
||||
fn truncate(&mut self, _node: VnodeRef, size: usize) -> Result<(), Errno> {
|
||||
self.data.resize((size + 4095) / 4096)
|
||||
}
|
||||
|
||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
||||
Ok(self.data.size())
|
||||
}
|
||||
|
||||
fn stat(&mut self, _node: VnodeRef, stat: &mut Stat) -> Result<(), Errno> {
|
||||
stat.size = self.data.size() as u64;
|
||||
stat.blksize = 4096;
|
||||
stat.mode = 0o755;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: BlockAllocator + Copy + 'static> FileInode<'a, A> {
|
||||
|
||||
+35
-35
@@ -1,9 +1,4 @@
|
||||
#![feature(
|
||||
const_fn_trait_bound,
|
||||
const_mut_refs,
|
||||
maybe_uninit_extra,
|
||||
maybe_uninit_uninit_array
|
||||
)]
|
||||
#![feature(const_fn_trait_bound, const_mut_refs, maybe_uninit_uninit_array)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
@@ -11,9 +6,6 @@ extern crate alloc;
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[macro_use]
|
||||
extern crate fs_macros;
|
||||
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use core::any::Any;
|
||||
use core::cell::{Ref, RefCell};
|
||||
@@ -22,14 +14,14 @@ use libsys::{
|
||||
path::{path_component_left, path_component_right},
|
||||
stat::FileMode,
|
||||
};
|
||||
use vfs::{BlockDevice, Filesystem, Vnode, VnodeKind, VnodeRef};
|
||||
use vfs::{BlockDevice, Filesystem, Vnode, VnodeCreateKind, VnodeData, VnodeRef};
|
||||
|
||||
mod block;
|
||||
pub use block::{BlockAllocator, BlockRef};
|
||||
mod bvec;
|
||||
use bvec::Bvec;
|
||||
mod tar;
|
||||
use tar::TarIterator;
|
||||
use tar::{Tar, TarIterator};
|
||||
mod file;
|
||||
use file::FileInode;
|
||||
mod dir;
|
||||
@@ -67,15 +59,17 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn create_node_initial(self: Rc<Self>, name: &str, kind: VnodeKind) -> VnodeRef {
|
||||
let node = Vnode::new(name, kind, Vnode::SEEKABLE);
|
||||
node.set_fs(self.clone());
|
||||
match kind {
|
||||
VnodeKind::Directory => node.set_data(Box::new(DirInode::new(self.alloc))),
|
||||
VnodeKind::Regular => {}
|
||||
VnodeKind::Char => todo!(),
|
||||
VnodeKind::Block => todo!(),
|
||||
fn create_node_initial(self: Rc<Self>, name: &str, tar: &Tar) -> VnodeRef {
|
||||
let kind = tar.node_create_kind();
|
||||
let data = match kind {
|
||||
VnodeCreateKind::Directory => {
|
||||
VnodeData::Directory(RefCell::new(Some(Box::new(DirInode::new(self.alloc)))))
|
||||
}
|
||||
VnodeCreateKind::File => VnodeData::File(RefCell::new(None)),
|
||||
};
|
||||
let node = Vnode::new(name, data, Vnode::SEEKABLE | Vnode::CACHE_READDIR);
|
||||
node.props_mut().mode = tar.mode();
|
||||
node.set_fs(self);
|
||||
node
|
||||
}
|
||||
|
||||
@@ -99,7 +93,8 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
|
||||
return Err(Errno::DoesNotExist);
|
||||
}
|
||||
// TODO file modes
|
||||
at.create(element, FileMode::default_dir(), VnodeKind::Directory)?
|
||||
at.create(element, FileMode::default_dir(), VnodeCreateKind::Directory)?
|
||||
// todo!();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,17 +106,18 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
|
||||
}
|
||||
|
||||
unsafe fn load_tar(self: Rc<Self>, base: *const u8, size: usize) -> Result<VnodeRef, Errno> {
|
||||
let root = self.clone().create_node_initial("", VnodeKind::Directory);
|
||||
let root_data =
|
||||
VnodeData::Directory(RefCell::new(Some(Box::new(DirInode::new(self.alloc)))));
|
||||
let root = Vnode::new("", root_data, Vnode::SEEKABLE | Vnode::CACHE_READDIR);
|
||||
root.set_fs(self.clone());
|
||||
root.props_mut().mode = FileMode::default_dir();
|
||||
|
||||
// 1. Create all the paths in TAR
|
||||
for block in TarIterator::new(base, base.add(size)) {
|
||||
let (dirname, basename) = path_component_right(block.path()?);
|
||||
|
||||
let parent = self.clone().make_path(root.clone(), dirname, true)?;
|
||||
let node = self
|
||||
.clone()
|
||||
.create_node_initial(basename, block.node_kind());
|
||||
assert_eq!(node.kind(), block.node_kind());
|
||||
let node = self.clone().create_node_initial(basename, block);
|
||||
parent.attach(node);
|
||||
}
|
||||
|
||||
@@ -130,16 +126,17 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
|
||||
if block.is_file() {
|
||||
// Will not create any dirs
|
||||
let node = self.clone().make_path(root.clone(), block.path()?, false)?;
|
||||
assert_eq!(node.kind(), block.node_kind());
|
||||
|
||||
#[cfg(feature = "cow")]
|
||||
{
|
||||
let data = block.data();
|
||||
node.set_data(Box::new(FileInode::new(Bvec::new_copy_on_write(
|
||||
self.alloc,
|
||||
data.as_ptr(),
|
||||
data.len(),
|
||||
))));
|
||||
node.as_file()
|
||||
.unwrap()
|
||||
.replace(Some(Box::new(FileInode::new(Bvec::new_copy_on_write(
|
||||
self.alloc,
|
||||
data.as_ptr(),
|
||||
data.len(),
|
||||
)))));
|
||||
}
|
||||
#[cfg(not(feature = "cow"))]
|
||||
{
|
||||
@@ -162,7 +159,10 @@ impl<A: BlockAllocator + Copy + 'static> Ramfs<A> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::boxed::Box;
|
||||
use libcommon::Read;
|
||||
use libsys::{
|
||||
stat::{GroupId, OpenFlags, UserId},
|
||||
traits::Read,
|
||||
};
|
||||
use vfs::Ioctx;
|
||||
|
||||
#[test]
|
||||
@@ -184,15 +184,15 @@ mod tests {
|
||||
let fs = unsafe { Ramfs::open(data.as_ptr(), data.bytes().len(), A {}).unwrap() };
|
||||
|
||||
let root = fs.root().unwrap();
|
||||
let ioctx = Ioctx::new(root.clone());
|
||||
let ioctx = Ioctx::new(root.clone(), UserId::root(), GroupId::root());
|
||||
|
||||
assert!(Rc::ptr_eq(&ioctx.find(None, "/", true).unwrap(), &root));
|
||||
|
||||
let node = ioctx.find(None, "/test1.txt", true).unwrap();
|
||||
let mut file = node.open().unwrap();
|
||||
let mut file = node.open(OpenFlags::O_RDONLY).unwrap();
|
||||
let mut buf = [0u8; 1024];
|
||||
|
||||
assert_eq!(file.read(&mut buf).unwrap(), 20);
|
||||
assert_eq!(file.borrow_mut().read(&mut buf).unwrap(), 20);
|
||||
let s = core::str::from_utf8(&buf[..20]).unwrap();
|
||||
assert_eq!(s, "This is a test file\n");
|
||||
}
|
||||
|
||||
+13
-5
@@ -1,5 +1,5 @@
|
||||
use libsys::error::Errno;
|
||||
use vfs::VnodeKind;
|
||||
use libsys::{error::Errno, stat::FileMode};
|
||||
use vfs::VnodeCreateKind;
|
||||
|
||||
#[repr(packed)]
|
||||
#[allow(dead_code)]
|
||||
@@ -73,14 +73,22 @@ impl Tar {
|
||||
core::str::from_utf8(&self.name[..zero_index]).map_err(|_| Errno::InvalidArgument)
|
||||
}
|
||||
|
||||
pub fn node_kind(&self) -> VnodeKind {
|
||||
pub fn node_create_kind(&self) -> VnodeCreateKind {
|
||||
match self.type_ {
|
||||
0 | b'0' => VnodeKind::Regular,
|
||||
b'5' => VnodeKind::Directory,
|
||||
0 | b'0' => VnodeCreateKind::File,
|
||||
b'5' => VnodeCreateKind::Directory,
|
||||
p => panic!("Unrecognized tar entry type: '{}'", p as char),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> FileMode {
|
||||
let t = match self.node_create_kind() {
|
||||
VnodeCreateKind::File => FileMode::S_IFREG,
|
||||
VnodeCreateKind::Directory => FileMode::S_IFDIR,
|
||||
};
|
||||
FileMode::from_bits(from_octal(&self.mode) as u32).unwrap() | t
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
unsafe {
|
||||
core::slice::from_raw_parts(
|
||||
|
||||
+2
-49
@@ -1,5 +1,4 @@
|
||||
use crate::{VnodeImpl, VnodeKind, VnodeRef};
|
||||
use libsys::{error::Errno, ioctl::IoctlCmd, stat::OpenFlags};
|
||||
use libsys::{error::Errno, ioctl::IoctlCmd};
|
||||
|
||||
/// Generic character device trait
|
||||
pub trait CharDevice {
|
||||
@@ -19,52 +18,6 @@ pub trait CharDevice {
|
||||
/// Performs a TTY control request
|
||||
fn ioctl(&self, cmd: IoctlCmd, ptr: usize, lim: usize) -> Result<usize, Errno>;
|
||||
|
||||
/// Returns `true` if the device is ready for an operation
|
||||
fn is_ready(&self, write: bool) -> Result<bool, Errno>;
|
||||
}
|
||||
|
||||
/// Wrapper struct to attach [VnodeImpl] implementation
|
||||
/// to [CharDevice]s
|
||||
pub struct CharDeviceWrapper {
|
||||
device: &'static dyn CharDevice,
|
||||
}
|
||||
|
||||
#[auto_inode(error)]
|
||||
impl VnodeImpl for CharDeviceWrapper {
|
||||
fn open(&mut self, _node: VnodeRef, _opts: OpenFlags) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read(&mut self, _node: VnodeRef, _pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
self.device.read(true, data)
|
||||
}
|
||||
|
||||
fn write(&mut self, _node: VnodeRef, _pos: usize, data: &[u8]) -> Result<usize, Errno> {
|
||||
self.device.write(true, data)
|
||||
}
|
||||
|
||||
fn is_ready(&mut self, _node: VnodeRef, write: bool) -> Result<bool, Errno> {
|
||||
self.device.is_ready(write)
|
||||
}
|
||||
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
cmd: IoctlCmd,
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
self.device.ioctl(cmd, ptr, len)
|
||||
}
|
||||
}
|
||||
|
||||
impl CharDeviceWrapper {
|
||||
/// Creates a wrapper for static [CharDevice] trait object to
|
||||
/// auto-implement [VnodeImpl] trait for the device
|
||||
pub const fn new(device: &'static dyn CharDevice) -> Self {
|
||||
Self { device }
|
||||
}
|
||||
}
|
||||
|
||||
+114
-23
@@ -1,9 +1,11 @@
|
||||
use crate::{VnodeKind, VnodeRef};
|
||||
use crate::{Vnode, VnodeRef};
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::RefCell;
|
||||
use core::cmp::min;
|
||||
use core::str::FromStr;
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
stat::DirectoryEntry,
|
||||
traits::{Read, Seek, SeekDir, Write},
|
||||
};
|
||||
|
||||
@@ -37,9 +39,10 @@ impl Read for File {
|
||||
match &mut self.inner {
|
||||
FileInner::Normal(inner) => {
|
||||
let count = inner.vnode.read(inner.pos, data)?;
|
||||
if inner.vnode.kind() != VnodeKind::Char {
|
||||
inner.pos += count;
|
||||
}
|
||||
// TODO
|
||||
// if inner.vnode.kind() != VnodeKind::Char {
|
||||
inner.pos += count;
|
||||
// }
|
||||
Ok(count)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
@@ -56,9 +59,10 @@ impl Write for File {
|
||||
match &mut self.inner {
|
||||
FileInner::Normal(inner) => {
|
||||
let count = inner.vnode.write(inner.pos, data)?;
|
||||
if inner.vnode.kind() != VnodeKind::Char {
|
||||
inner.pos += count;
|
||||
}
|
||||
// TODO
|
||||
// if inner.vnode.kind() != VnodeKind::Char {
|
||||
inner.pos += count;
|
||||
// }
|
||||
Ok(count)
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
@@ -97,6 +101,11 @@ impl File {
|
||||
/// File has to be closed on execve() calls
|
||||
pub const CLOEXEC: u32 = 1 << 2;
|
||||
|
||||
/// Special position for cache-readdir: "." entry
|
||||
pub const POS_CACHE_DOT: usize = usize::MAX - 1;
|
||||
/// Special position for cache-readdir: ".." entry
|
||||
pub const POS_CACHE_DOT_DOT: usize = usize::MAX;
|
||||
|
||||
/// Constructs a new file handle for a regular file
|
||||
pub fn normal(vnode: VnodeRef, pos: usize, flags: u32) -> FileRef {
|
||||
Rc::new(RefCell::new(Self {
|
||||
@@ -119,9 +128,68 @@ impl File {
|
||||
self.flags & Self::CLOEXEC != 0
|
||||
}
|
||||
|
||||
pub fn is_ready(&self, write: bool) -> Result<bool, Errno> {
|
||||
/// Returns `true` if the file is ready for an operation
|
||||
pub fn ready(&self, write: bool) -> Result<bool, Errno> {
|
||||
match &self.inner {
|
||||
FileInner::Normal(inner) => inner.vnode.is_ready(write),
|
||||
FileInner::Normal(inner) => inner.vnode.ready(write),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cache_readdir(
|
||||
inner: &mut NormalFile,
|
||||
entries: &mut [DirectoryEntry],
|
||||
) -> Result<usize, Errno> {
|
||||
let mut count = entries.len();
|
||||
let mut offset = 0usize;
|
||||
|
||||
if inner.pos == Self::POS_CACHE_DOT {
|
||||
if count == 0 {
|
||||
return Ok(offset);
|
||||
}
|
||||
|
||||
entries[offset] = DirectoryEntry::from_str(".").unwrap();
|
||||
inner.pos = Self::POS_CACHE_DOT_DOT;
|
||||
|
||||
offset += 1;
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if inner.pos == Self::POS_CACHE_DOT_DOT {
|
||||
if count == 0 {
|
||||
return Ok(offset);
|
||||
}
|
||||
|
||||
entries[offset] = DirectoryEntry::from_str("..").unwrap();
|
||||
inner.pos = 0;
|
||||
|
||||
offset += 1;
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return Ok(offset);
|
||||
}
|
||||
|
||||
let count = inner.vnode.for_each_entry(inner.pos, count, |i, e| {
|
||||
entries[offset + i] = DirectoryEntry::from_str(e.name()).unwrap();
|
||||
});
|
||||
inner.pos += count;
|
||||
Ok(offset + count)
|
||||
}
|
||||
|
||||
/// Reads directory entries into the target buffer
|
||||
pub fn readdir(&mut self, entries: &mut [DirectoryEntry]) -> Result<usize, Errno> {
|
||||
match &mut self.inner {
|
||||
FileInner::Normal(inner) => {
|
||||
// assert_eq!(inner.vnode.kind(), VnodeKind::Directory);
|
||||
|
||||
if inner.vnode.flags() & Vnode::CACHE_READDIR != 0 {
|
||||
Self::cache_readdir(inner, entries)
|
||||
} else {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -141,24 +209,38 @@ impl Drop for File {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
||||
use libsys::{stat::OpenFlags, ioctl::IoctlCmd, stat::Stat};
|
||||
use crate::node::{VnodeCommon, VnodeFile};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::rc::Rc;
|
||||
use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
|
||||
|
||||
struct DummyInode;
|
||||
|
||||
#[auto_inode]
|
||||
impl VnodeImpl for DummyInode {
|
||||
fn create(
|
||||
impl VnodeCommon for DummyInode {
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_at: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
let node = Vnode::new(name, kind, 0);
|
||||
node.set_data(Box::new(DummyInode {}));
|
||||
Ok(node)
|
||||
node: VnodeRef,
|
||||
cmd: IoctlCmd,
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Retrieves file status
|
||||
fn stat(&mut self, node: VnodeRef) -> Result<Stat, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn is_ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result<usize, Errno> {
|
||||
@@ -168,7 +250,9 @@ mod tests {
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VnodeFile for DummyInode {
|
||||
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
#[cfg(test)]
|
||||
println!("read {} at {}", data.len(), pos);
|
||||
@@ -186,12 +270,19 @@ mod tests {
|
||||
fn write(&mut self, _node: VnodeRef, _pos: usize, _data: &[u8]) -> Result<usize, Errno> {
|
||||
Err(Errno::NotImplemented)
|
||||
}
|
||||
|
||||
fn truncate(&mut self, node: VnodeRef, size: usize) -> Result<(), Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normal_read() {
|
||||
let node = Vnode::new("", VnodeKind::Regular, 0);
|
||||
node.set_data(Box::new(DummyInode {}));
|
||||
let node = Vnode::new(
|
||||
"",
|
||||
VnodeData::File(RefCell::new(Some(Box::new(DummyInode {})))),
|
||||
0,
|
||||
);
|
||||
let mut file = node.open(OpenFlags::O_RDONLY).unwrap();
|
||||
let mut buf = [0u8; 4096];
|
||||
|
||||
|
||||
+216
-196
@@ -1,8 +1,8 @@
|
||||
use crate::{FileRef, VnodeKind, VnodeRef};
|
||||
use crate::{FileRef, VnodeCreateKind, VnodeRef};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
stat::{OpenFlags, FileMode},
|
||||
path::{path_component_left, path_component_right},
|
||||
stat::{FileMode, GroupId, OpenFlags, UserId},
|
||||
};
|
||||
|
||||
/// I/O context structure
|
||||
@@ -10,13 +10,19 @@ use libsys::{
|
||||
pub struct Ioctx {
|
||||
root: VnodeRef,
|
||||
cwd: VnodeRef,
|
||||
/// Process user ID
|
||||
pub uid: UserId,
|
||||
/// Process group ID
|
||||
pub gid: GroupId,
|
||||
}
|
||||
|
||||
impl Ioctx {
|
||||
/// Creates a new I/O context with given root node
|
||||
pub fn new(root: VnodeRef) -> Self {
|
||||
pub fn new(root: VnodeRef, uid: UserId, gid: GroupId) -> Self {
|
||||
Self {
|
||||
cwd: root.clone(),
|
||||
uid,
|
||||
gid,
|
||||
root,
|
||||
}
|
||||
}
|
||||
@@ -28,9 +34,10 @@ impl Ioctx {
|
||||
loop {
|
||||
(element, rest) = path_component_left(rest);
|
||||
|
||||
if !at.is_directory() {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
at.as_directory()?;
|
||||
// if !at.is_directory() {
|
||||
// return Err(Errno::NotADirectory);
|
||||
// }
|
||||
|
||||
match element {
|
||||
".." => {
|
||||
@@ -41,6 +48,11 @@ impl Ioctx {
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(target) = at.target() {
|
||||
// assert!(at.kind() == VnodeKind::Directory);
|
||||
at = target;
|
||||
}
|
||||
|
||||
if element.is_empty() && rest.is_empty() {
|
||||
return Ok(at);
|
||||
}
|
||||
@@ -49,7 +61,7 @@ impl Ioctx {
|
||||
let mut node = at.lookup_or_load(element)?;
|
||||
|
||||
while let Some(target) = node.target() {
|
||||
assert!(node.kind() == VnodeKind::Directory);
|
||||
// assert!(node.kind() == VnodeKind::Directory);
|
||||
node = target;
|
||||
}
|
||||
|
||||
@@ -90,7 +102,7 @@ impl Ioctx {
|
||||
self.find(at, parent, true)?.create(
|
||||
name.trim_start_matches('/'),
|
||||
mode,
|
||||
VnodeKind::Directory,
|
||||
VnodeCreateKind::Directory,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -103,202 +115,210 @@ impl Ioctx {
|
||||
opts: OpenFlags,
|
||||
) -> Result<FileRef, Errno> {
|
||||
let node = match self.find(at.clone(), path, true) {
|
||||
Err(Errno::DoesNotExist) => {
|
||||
Err(Errno::DoesNotExist) if opts.contains(OpenFlags::O_CREAT) => {
|
||||
let (parent, name) = path_component_right(path);
|
||||
let at = self.find(at, parent, true)?;
|
||||
at.create(name, mode, VnodeKind::Regular)
|
||||
at.create(name, mode, VnodeCreateKind::File)
|
||||
}
|
||||
o => o,
|
||||
}?;
|
||||
|
||||
node.open(opts)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{Vnode, VnodeImpl, VnodeKind};
|
||||
use alloc::{boxed::Box, rc::Rc};
|
||||
use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
|
||||
|
||||
pub struct DummyInode;
|
||||
|
||||
#[auto_inode]
|
||||
impl VnodeImpl for DummyInode {
|
||||
fn create(
|
||||
&mut self,
|
||||
_at: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
let vnode = Vnode::new(name, kind, 0);
|
||||
vnode.set_data(Box::new(DummyInode {}));
|
||||
Ok(vnode)
|
||||
}
|
||||
|
||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||
Err(Errno::DoesNotExist)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_existing_absolute() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let d1 = Vnode::new("dir1", VnodeKind::Directory, 0);
|
||||
let d0d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let d0f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
let d1f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
|
||||
root.attach(d0.clone());
|
||||
root.attach(d1.clone());
|
||||
d0.attach(d0d0.clone());
|
||||
d0.attach(d0f0.clone());
|
||||
d1.attach(d1f0.clone());
|
||||
|
||||
let ioctx = Ioctx::new(root.clone());
|
||||
|
||||
assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/.", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/./.", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(
|
||||
&root,
|
||||
&ioctx.find(None, "/.///.", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/..", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/../", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(
|
||||
&root,
|
||||
&ioctx.find(None, "/../.", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&root,
|
||||
&ioctx.find(None, "/../..", false).unwrap()
|
||||
));
|
||||
|
||||
assert!(Rc::ptr_eq(&d0, &ioctx.find(None, "/dir0", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(&d1, &ioctx.find(None, "/dir1", false).unwrap()));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0,
|
||||
&ioctx.find(None, "/dir1/../dir0", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d1,
|
||||
&ioctx
|
||||
.find(None, "/dir1/../dir0/./../../.././dir1", false)
|
||||
.unwrap()
|
||||
));
|
||||
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0d0,
|
||||
&ioctx.find(None, "/dir0/dir0", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0d0,
|
||||
&ioctx.find(None, "/dir0/dir0/.", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0,
|
||||
&ioctx.find(None, "/dir0/dir0/..", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0,
|
||||
&ioctx.find(None, "/dir0/dir0/../", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0,
|
||||
&ioctx.find(None, "/dir0/dir0/../.", false).unwrap()
|
||||
));
|
||||
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0f0,
|
||||
&ioctx.find(None, "/dir0/file0", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&d0f0,
|
||||
&ioctx.find(None, "/dir1/../dir0/./file0", false).unwrap()
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_rejects_file_dots() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let d0f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
|
||||
root.attach(d0.clone());
|
||||
d0.attach(d0f0.clone());
|
||||
|
||||
let ioctx = Ioctx::new(root.clone());
|
||||
|
||||
assert_eq!(
|
||||
ioctx.find(None, "/dir0/file0/.", false).unwrap_err(),
|
||||
Errno::NotADirectory
|
||||
);
|
||||
assert_eq!(
|
||||
ioctx.find(None, "/dir0/file0/..", false).unwrap_err(),
|
||||
Errno::NotADirectory
|
||||
);
|
||||
|
||||
// TODO handle this case
|
||||
// assert_eq!(ioctx.find(None, "/dir0/file0/").unwrap_err(), Errno::NotADirectory);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mkdir() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let ioctx = Ioctx::new(root.clone());
|
||||
|
||||
root.set_data(Box::new(DummyInode {}));
|
||||
|
||||
assert!(ioctx.mkdir(None, "/dir0", FileMode::default_dir()).is_ok());
|
||||
assert_eq!(
|
||||
ioctx
|
||||
.mkdir(None, "/dir0", FileMode::default_dir())
|
||||
.unwrap_err(),
|
||||
Errno::AlreadyExists
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_mount() {
|
||||
let root_outer = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let dir0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let root_inner = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let dir1 = Vnode::new("dir1", VnodeKind::Directory, 0);
|
||||
|
||||
root_outer.clone().attach(dir0.clone());
|
||||
root_inner.clone().attach(dir1.clone());
|
||||
|
||||
let ioctx = Ioctx::new(root_outer.clone());
|
||||
|
||||
assert_eq!(
|
||||
ioctx.find(None, "/dir0/dir1", false).unwrap_err(),
|
||||
Errno::DoesNotExist
|
||||
);
|
||||
|
||||
dir0.mount(root_inner.clone()).unwrap();
|
||||
|
||||
assert!(Rc::ptr_eq(
|
||||
&root_inner,
|
||||
&ioctx.find(None, "/dir0", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&dir1,
|
||||
&ioctx.find(None, "/dir0/dir1", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&root_inner,
|
||||
&ioctx.find(None, "/dir0/dir1/..", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&dir0,
|
||||
&ioctx.find(None, "/dir0/dir1/../..", false).unwrap()
|
||||
));
|
||||
assert!(Rc::ptr_eq(
|
||||
&root_outer,
|
||||
&ioctx.find(None, "/dir0/dir1/../../..", false).unwrap()
|
||||
));
|
||||
/// Changes current working directory of the process
|
||||
pub fn chdir(&mut self, path: &str) -> Result<(), Errno> {
|
||||
let node = self.find(None, path, true)?;
|
||||
let _dir = node.as_directory()?;
|
||||
self.cwd = node;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{Vnode, VnodeImpl, VnodeKind};
|
||||
// use alloc::{boxed::Box, rc::Rc};
|
||||
// use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
|
||||
//
|
||||
// pub struct DummyInode;
|
||||
//
|
||||
// #[auto_inode]
|
||||
// impl VnodeImpl for DummyInode {
|
||||
// fn create(
|
||||
// &mut self,
|
||||
// _at: VnodeRef,
|
||||
// name: &str,
|
||||
// kind: VnodeKind,
|
||||
// ) -> Result<VnodeRef, Errno> {
|
||||
// let vnode = Vnode::new(name, kind, 0);
|
||||
// vnode.set_data(Box::new(DummyInode {}));
|
||||
// Ok(vnode)
|
||||
// }
|
||||
//
|
||||
// fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||
// Err(Errno::DoesNotExist)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_find_existing_absolute() {
|
||||
// let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
// let d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
// let d1 = Vnode::new("dir1", VnodeKind::Directory, 0);
|
||||
// let d0d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
// let d0f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
// let d1f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
//
|
||||
// root.attach(d0.clone());
|
||||
// root.attach(d1.clone());
|
||||
// d0.attach(d0d0.clone());
|
||||
// d0.attach(d0f0.clone());
|
||||
// d1.attach(d1f0.clone());
|
||||
//
|
||||
// let ioctx = Ioctx::new(root.clone());
|
||||
//
|
||||
// assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/.", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/./.", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root,
|
||||
// &ioctx.find(None, "/.///.", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/..", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(&root, &ioctx.find(None, "/../", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root,
|
||||
// &ioctx.find(None, "/../.", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root,
|
||||
// &ioctx.find(None, "/../..", false).unwrap()
|
||||
// ));
|
||||
//
|
||||
// assert!(Rc::ptr_eq(&d0, &ioctx.find(None, "/dir0", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(&d1, &ioctx.find(None, "/dir1", false).unwrap()));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0,
|
||||
// &ioctx.find(None, "/dir1/../dir0", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d1,
|
||||
// &ioctx
|
||||
// .find(None, "/dir1/../dir0/./../../.././dir1", false)
|
||||
// .unwrap()
|
||||
// ));
|
||||
//
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0d0,
|
||||
// &ioctx.find(None, "/dir0/dir0", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0d0,
|
||||
// &ioctx.find(None, "/dir0/dir0/.", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0,
|
||||
// &ioctx.find(None, "/dir0/dir0/..", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0,
|
||||
// &ioctx.find(None, "/dir0/dir0/../", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0,
|
||||
// &ioctx.find(None, "/dir0/dir0/../.", false).unwrap()
|
||||
// ));
|
||||
//
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0f0,
|
||||
// &ioctx.find(None, "/dir0/file0", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &d0f0,
|
||||
// &ioctx.find(None, "/dir1/../dir0/./file0", false).unwrap()
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_find_rejects_file_dots() {
|
||||
// let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
// let d0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
// let d0f0 = Vnode::new("file0", VnodeKind::Regular, 0);
|
||||
//
|
||||
// root.attach(d0.clone());
|
||||
// d0.attach(d0f0.clone());
|
||||
//
|
||||
// let ioctx = Ioctx::new(root.clone());
|
||||
//
|
||||
// assert_eq!(
|
||||
// ioctx.find(None, "/dir0/file0/.", false).unwrap_err(),
|
||||
// Errno::NotADirectory
|
||||
// );
|
||||
// assert_eq!(
|
||||
// ioctx.find(None, "/dir0/file0/..", false).unwrap_err(),
|
||||
// Errno::NotADirectory
|
||||
// );
|
||||
//
|
||||
// // TODO handle this case
|
||||
// // assert_eq!(ioctx.find(None, "/dir0/file0/").unwrap_err(), Errno::NotADirectory);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_mkdir() {
|
||||
// let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
// let ioctx = Ioctx::new(root.clone());
|
||||
//
|
||||
// root.set_data(Box::new(DummyInode {}));
|
||||
//
|
||||
// assert!(ioctx.mkdir(None, "/dir0", FileMode::default_dir()).is_ok());
|
||||
// assert_eq!(
|
||||
// ioctx
|
||||
// .mkdir(None, "/dir0", FileMode::default_dir())
|
||||
// .unwrap_err(),
|
||||
// Errno::AlreadyExists
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_find_mount() {
|
||||
// let root_outer = Vnode::new("", VnodeKind::Directory, 0);
|
||||
// let dir0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
// let root_inner = Vnode::new("", VnodeKind::Directory, 0);
|
||||
// let dir1 = Vnode::new("dir1", VnodeKind::Directory, 0);
|
||||
//
|
||||
// root_outer.clone().attach(dir0.clone());
|
||||
// root_inner.clone().attach(dir1.clone());
|
||||
//
|
||||
// let ioctx = Ioctx::new(root_outer.clone());
|
||||
//
|
||||
// assert_eq!(
|
||||
// ioctx.find(None, "/dir0/dir1", false).unwrap_err(),
|
||||
// Errno::DoesNotExist
|
||||
// );
|
||||
//
|
||||
// dir0.mount(root_inner.clone()).unwrap();
|
||||
//
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root_inner,
|
||||
// &ioctx.find(None, "/dir0", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &dir1,
|
||||
// &ioctx.find(None, "/dir0/dir1", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root_inner,
|
||||
// &ioctx.find(None, "/dir0/dir1/..", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &dir0,
|
||||
// &ioctx.find(None, "/dir0/dir1/../..", false).unwrap()
|
||||
// ));
|
||||
// assert!(Rc::ptr_eq(
|
||||
// &root_outer,
|
||||
// &ioctx.find(None, "/dir0/dir1/../../..", false).unwrap()
|
||||
// ));
|
||||
// }
|
||||
// }
|
||||
|
||||
+5
-6
@@ -1,15 +1,12 @@
|
||||
//! Virtual filesystem API and facilities
|
||||
#![warn(missing_docs)]
|
||||
#![feature(destructuring_assignment, const_fn_trait_bound)]
|
||||
#![feature(const_fn_trait_bound, const_discriminant)]
|
||||
#![no_std]
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate std;
|
||||
|
||||
#[macro_use]
|
||||
extern crate fs_macros;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
// pub use libsys::stat::{FileMode, OpenFlags, Stat};
|
||||
@@ -20,10 +17,12 @@ pub use block::BlockDevice;
|
||||
mod fs;
|
||||
pub use fs::Filesystem;
|
||||
mod node;
|
||||
pub use node::{Vnode, VnodeImpl, VnodeKind, VnodeRef};
|
||||
pub use node::{
|
||||
Vnode, VnodeCommon, VnodeCreateKind, VnodeData, VnodeDirectory, VnodeFile, VnodeRef,
|
||||
};
|
||||
mod ioctx;
|
||||
pub use ioctx::Ioctx;
|
||||
mod file;
|
||||
pub use file::{File, FileRef};
|
||||
mod char;
|
||||
pub use crate::char::{CharDevice, CharDeviceWrapper};
|
||||
pub use crate::char::CharDevice;
|
||||
|
||||
+407
-177
@@ -1,29 +1,91 @@
|
||||
use crate::{File, FileRef, Filesystem};
|
||||
use crate::{CharDevice, File, FileRef, Filesystem, Ioctx};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, rc::Rc, string::String, vec::Vec};
|
||||
use core::cell::{RefCell, RefMut};
|
||||
use core::cell::{Ref, RefCell, RefMut};
|
||||
use core::fmt;
|
||||
use core::mem::Discriminant;
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
stat::{FileMode, OpenFlags, Stat},
|
||||
stat::{AccessMode, DirectoryEntry, FileMode, OpenFlags, Stat},
|
||||
};
|
||||
|
||||
/// Convenience type alias for [Rc<Vnode>]
|
||||
pub type VnodeRef = Rc<Vnode>;
|
||||
pub type VnodeKind = Discriminant<VnodeData>;
|
||||
|
||||
/// List of possible vnode types
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum VnodeKind {
|
||||
/// Node is a directory with create/lookup/remove operations
|
||||
Directory,
|
||||
/// Node is a regular file
|
||||
Regular,
|
||||
/// Node is a character device
|
||||
Char,
|
||||
/// Node is a block device
|
||||
Block,
|
||||
/// Trait implemented by both regular files and directories
|
||||
pub trait VnodeCommon {
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
node: VnodeRef,
|
||||
cmd: IoctlCmd,
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
) -> Result<usize, Errno>;
|
||||
|
||||
/// Retrieves file status
|
||||
fn stat(&mut self, node: VnodeRef) -> Result<Stat, Errno>;
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno>;
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno>;
|
||||
|
||||
/// Opens a vnode for access. Returns initial file position.
|
||||
fn open(&mut self, node: VnodeRef, opts: OpenFlags) -> Result<usize, Errno>;
|
||||
/// Closes a vnode
|
||||
fn close(&mut self, node: VnodeRef) -> Result<(), Errno>;
|
||||
}
|
||||
|
||||
/// Regular file access interface
|
||||
pub trait VnodeFile: VnodeCommon {
|
||||
/// Changes file's underlying storage size
|
||||
fn truncate(&mut self, node: VnodeRef, size: usize) -> Result<(), Errno>;
|
||||
/// Reads `data.len()` bytes into the buffer from file offset `pos`
|
||||
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno>;
|
||||
/// Writes `data.len()` bytes from the buffer to file offset `pos`.
|
||||
/// Resizes the file storage if necessary.
|
||||
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>;
|
||||
}
|
||||
|
||||
/// Directory access interface
|
||||
pub trait VnodeDirectory: VnodeCommon {
|
||||
/// Creates entry `name` of type `kind` in directory `at`
|
||||
fn create(
|
||||
&mut self,
|
||||
at: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeCreateKind,
|
||||
) -> Result<VnodeRef, Errno>;
|
||||
/// Removes entry `name` for directory `at`
|
||||
fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno>;
|
||||
/// Loads an entry `name` in directory `at` from filesystem
|
||||
fn lookup(&mut self, at: VnodeRef, name: &str) -> Result<VnodeRef, Errno>;
|
||||
|
||||
/// Read directory entries into target buffer
|
||||
fn readdir(
|
||||
&mut self,
|
||||
node: VnodeRef,
|
||||
pos: usize,
|
||||
data: &mut [DirectoryEntry],
|
||||
) -> Result<usize, Errno>;
|
||||
}
|
||||
|
||||
// /// List of possible vnode types
|
||||
// #[derive(Debug, Clone, Copy, PartialEq)]
|
||||
// pub enum VnodeKind {
|
||||
// /// Node is a directory with create/lookup/remove operations
|
||||
// Directory,
|
||||
// /// Node is a regular file
|
||||
// Regular,
|
||||
// /// Node is a character device
|
||||
// Char,
|
||||
// /// Node is a block device
|
||||
// Block,
|
||||
// }
|
||||
|
||||
pub(crate) struct TreeNode {
|
||||
parent: Option<VnodeRef>,
|
||||
children: Vec<VnodeRef>,
|
||||
@@ -31,7 +93,27 @@ pub(crate) struct TreeNode {
|
||||
|
||||
/// File property cache struct
|
||||
pub struct VnodeProps {
|
||||
mode: FileMode,
|
||||
/// Node permissions and type
|
||||
pub mode: FileMode,
|
||||
}
|
||||
|
||||
/// Specific node implementation data
|
||||
pub enum VnodeData {
|
||||
/// Directory node
|
||||
Directory(RefCell<Option<Box<dyn VnodeDirectory>>>),
|
||||
/// Regular file node
|
||||
File(RefCell<Option<Box<dyn VnodeFile>>>),
|
||||
/// Character device node
|
||||
Char(&'static dyn CharDevice),
|
||||
}
|
||||
|
||||
/// Node types for create() calls
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum VnodeCreateKind {
|
||||
/// Directory node
|
||||
Directory,
|
||||
/// Regular file node
|
||||
File,
|
||||
}
|
||||
|
||||
/// Virtual filesystem node struct, generalizes access to
|
||||
@@ -41,55 +123,13 @@ pub struct Vnode {
|
||||
tree: RefCell<TreeNode>,
|
||||
props: RefCell<VnodeProps>,
|
||||
|
||||
kind: VnodeKind,
|
||||
// kind: VnodeKind,
|
||||
data: VnodeData,
|
||||
flags: u32,
|
||||
|
||||
target: RefCell<Option<VnodeRef>>,
|
||||
fs: RefCell<Option<Rc<dyn Filesystem>>>,
|
||||
data: RefCell<Option<Box<dyn VnodeImpl>>>,
|
||||
}
|
||||
|
||||
/// Interface for "inode" of a real filesystem
|
||||
pub trait VnodeImpl {
|
||||
// Directory-only operations
|
||||
/// Creates a new vnode, sets it up, attaches it (in real FS) to `at` with `name` and
|
||||
/// returns it
|
||||
fn create(&mut self, at: VnodeRef, name: &str, kind: VnodeKind) -> Result<VnodeRef, Errno>;
|
||||
/// Removes the filesystem inode from its parent by erasing its directory entry
|
||||
fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno>;
|
||||
/// Looks up a corresponding directory entry for `name`. If present, loads its inode from
|
||||
/// storage medium and returns a new vnode associated with it.
|
||||
fn lookup(&mut self, at: VnodeRef, name: &str) -> Result<VnodeRef, Errno>;
|
||||
|
||||
/// Opens a vnode for access. Returns initial file position.
|
||||
fn open(&mut self, node: VnodeRef, opts: OpenFlags) -> Result<usize, Errno>;
|
||||
/// Closes a vnode
|
||||
fn close(&mut self, node: VnodeRef) -> Result<(), Errno>;
|
||||
|
||||
/// Changes file's underlying storage size
|
||||
fn truncate(&mut self, node: VnodeRef, size: usize) -> Result<(), Errno>;
|
||||
/// Reads `data.len()` bytes into the buffer from file offset `pos`
|
||||
fn read(&mut self, node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno>;
|
||||
/// Writes `data.len()` bytes from the buffer to file offset `pos`.
|
||||
/// Resizes the file storage if necessary.
|
||||
fn write(&mut self, node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno>;
|
||||
|
||||
/// Retrieves file status
|
||||
fn stat(&mut self, node: VnodeRef, stat: &mut Stat) -> Result<(), Errno>;
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno>;
|
||||
|
||||
fn is_ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno>;
|
||||
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
node: VnodeRef,
|
||||
cmd: IoctlCmd,
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
) -> Result<usize, Errno>;
|
||||
// data: RefCell<Option<Box<dyn VnodeImpl>>>,
|
||||
}
|
||||
|
||||
impl Vnode {
|
||||
@@ -97,12 +137,17 @@ impl Vnode {
|
||||
/// be seeked to arbitrary offsets
|
||||
pub const SEEKABLE: u32 = 1 << 0;
|
||||
|
||||
/// If set, readdir() uses only in-memory node tree
|
||||
pub const CACHE_READDIR: u32 = 1 << 1;
|
||||
/// If set, stat() uses only in-memory stat data
|
||||
pub const CACHE_STAT: u32 = 1 << 2;
|
||||
|
||||
/// Constructs a new [Vnode], wrapping it in [Rc]. The resulting node
|
||||
/// then needs to have [Vnode::set_data()] called on it to be usable.
|
||||
pub fn new(name: &str, kind: VnodeKind, flags: u32) -> VnodeRef {
|
||||
pub fn new(name: &str, data: VnodeData, flags: u32) -> VnodeRef {
|
||||
Rc::new(Self {
|
||||
name: name.to_owned(),
|
||||
kind,
|
||||
data,
|
||||
flags,
|
||||
props: RefCell::new(VnodeProps {
|
||||
mode: FileMode::empty(),
|
||||
@@ -113,7 +158,6 @@ impl Vnode {
|
||||
}),
|
||||
target: RefCell::new(None),
|
||||
fs: RefCell::new(None),
|
||||
data: RefCell::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,19 +166,40 @@ impl Vnode {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Sets an associated [VnodeImpl] for the [Vnode]
|
||||
pub fn set_data(&self, data: Box<dyn VnodeImpl>) {
|
||||
*self.data.borrow_mut() = Some(data);
|
||||
/// Returns a borrowed reference to cached file properties
|
||||
pub fn props_mut(&self) -> RefMut<VnodeProps> {
|
||||
self.props.borrow_mut()
|
||||
}
|
||||
|
||||
/// Returns a borrowed reference to cached file properties
|
||||
pub fn props(&self) -> Ref<VnodeProps> {
|
||||
self.props.borrow()
|
||||
}
|
||||
|
||||
// /// Sets an associated [VnodeImpl] for the [Vnode]
|
||||
// pub fn set_data(&self, data: Box<dyn VnodeImpl>) {
|
||||
// *self.data.borrow_mut() = Some(data);
|
||||
// }
|
||||
|
||||
/// Sets an associated [Filesystem] for the [Vnode]
|
||||
pub fn set_fs(&self, fs: Rc<dyn Filesystem>) {
|
||||
*self.fs.borrow_mut() = Some(fs);
|
||||
}
|
||||
|
||||
/// Returns a reference to the associated [VnodeImpl]
|
||||
pub fn data(&self) -> RefMut<Option<Box<dyn VnodeImpl>>> {
|
||||
self.data.borrow_mut()
|
||||
/// Returns node's directory implementation data
|
||||
pub fn as_directory(&self) -> Result<&RefCell<Option<Box<dyn VnodeDirectory>>>, Errno> {
|
||||
match &self.data {
|
||||
VnodeData::Directory(data) => Ok(data),
|
||||
_ => Err(Errno::NotADirectory),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns node's regular file implementation data
|
||||
pub fn as_file(&self) -> Result<&RefCell<Option<Box<dyn VnodeFile>>>, Errno> {
|
||||
match &self.data {
|
||||
VnodeData::File(data) => Ok(data),
|
||||
_ => Err(Errno::IsADirectory),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the associated [Fileystem]
|
||||
@@ -142,11 +207,6 @@ impl Vnode {
|
||||
self.fs.borrow().clone()
|
||||
}
|
||||
|
||||
/// Returns `true` if the vnode represents a directory
|
||||
pub fn is_directory(&self) -> bool {
|
||||
self.kind == VnodeKind::Directory
|
||||
}
|
||||
|
||||
/// Returns `true` if the vnode allows arbitrary seeking
|
||||
pub fn is_seekable(&self) -> bool {
|
||||
self.flags & Self::SEEKABLE != 0
|
||||
@@ -155,7 +215,13 @@ impl Vnode {
|
||||
/// Returns kind of the vnode
|
||||
#[inline(always)]
|
||||
pub const fn kind(&self) -> VnodeKind {
|
||||
self.kind
|
||||
core::mem::discriminant(&self.data)
|
||||
}
|
||||
|
||||
/// Returns flags of the vnode
|
||||
#[inline(always)]
|
||||
pub const fn flags(&self) -> u32 {
|
||||
self.flags
|
||||
}
|
||||
|
||||
// Tree operations
|
||||
@@ -189,12 +255,9 @@ impl Vnode {
|
||||
|
||||
/// Attaches some filesystem's root directory node at another directory
|
||||
pub fn mount(self: &VnodeRef, root: VnodeRef) -> Result<(), Errno> {
|
||||
if !self.is_directory() {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
if !root.is_directory() {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
let _dir = self.as_directory()?;
|
||||
let _root_dir = root.as_directory()?;
|
||||
|
||||
if self.target.borrow().is_some() {
|
||||
return Err(Errno::Busy);
|
||||
}
|
||||
@@ -221,7 +284,7 @@ impl Vnode {
|
||||
|
||||
/// Looks up a child `name` in in-memory tree cache
|
||||
pub fn lookup(self: &VnodeRef, name: &str) -> Option<VnodeRef> {
|
||||
assert!(self.is_directory());
|
||||
// assert!(self.is_directory());
|
||||
self.tree
|
||||
.borrow()
|
||||
.children
|
||||
@@ -230,15 +293,39 @@ impl Vnode {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub(crate) fn for_each_entry<F: FnMut(usize, &VnodeRef)>(
|
||||
&self,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
mut f: F,
|
||||
) -> usize {
|
||||
// TODO
|
||||
// assert!(self.is_directory());
|
||||
let mut count = 0;
|
||||
for (index, item) in self
|
||||
.tree
|
||||
.borrow()
|
||||
.children
|
||||
.iter()
|
||||
.skip(offset)
|
||||
.take(limit)
|
||||
.enumerate()
|
||||
{
|
||||
f(index, item);
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Looks up a child `name` in `self`. Will first try looking up a cached
|
||||
/// vnode and will load it from disk if it's missing.
|
||||
pub fn lookup_or_load(self: &VnodeRef, name: &str) -> Result<VnodeRef, Errno> {
|
||||
if let Some(node) = self.lookup(name) {
|
||||
return Ok(node);
|
||||
}
|
||||
let dir = self.as_directory()?;
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
let vnode = data.lookup(self.clone(), name)?;
|
||||
if let Some(node) = self.lookup(name) {
|
||||
Ok(node)
|
||||
} else if let Some(ref mut dir) = *dir.borrow_mut() {
|
||||
let vnode = dir.lookup(self.clone(), name)?;
|
||||
if let Some(fs) = self.fs() {
|
||||
vnode.set_fs(fs);
|
||||
}
|
||||
@@ -254,11 +341,10 @@ impl Vnode {
|
||||
self: &VnodeRef,
|
||||
name: &str,
|
||||
mode: FileMode,
|
||||
kind: VnodeKind,
|
||||
kind: VnodeCreateKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
if self.kind != VnodeKind::Directory {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
let dir = self.as_directory()?;
|
||||
|
||||
if name.contains('/') {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
@@ -269,8 +355,8 @@ impl Vnode {
|
||||
e => return e,
|
||||
};
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
let vnode = data.create(self.clone(), name, kind)?;
|
||||
if let Some(ref mut dir) = *dir.borrow_mut() {
|
||||
let vnode = dir.create(self.clone(), name, kind)?;
|
||||
if let Some(fs) = self.fs() {
|
||||
vnode.set_fs(fs);
|
||||
}
|
||||
@@ -284,16 +370,14 @@ impl Vnode {
|
||||
|
||||
/// Removes a directory entry `name` from `self`
|
||||
pub fn unlink(self: &VnodeRef, name: &str) -> Result<(), Errno> {
|
||||
if self.kind != VnodeKind::Directory {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
let dir = self.as_directory()?;
|
||||
if name.contains('/') {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
if let Some(ref mut dir) = *dir.borrow_mut() {
|
||||
let vnode = self.lookup(name).ok_or(Errno::DoesNotExist)?;
|
||||
data.remove(self.clone(), name)?;
|
||||
dir.remove(self.clone(), name)?;
|
||||
vnode.detach();
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -303,111 +387,205 @@ impl Vnode {
|
||||
|
||||
/// Opens a vnode for access
|
||||
pub fn open(self: &VnodeRef, flags: OpenFlags) -> Result<FileRef, Errno> {
|
||||
if self.kind == VnodeKind::Directory {
|
||||
return Err(Errno::IsADirectory);
|
||||
let mut open_flags = 0;
|
||||
|
||||
if flags.contains(OpenFlags::O_CLOEXEC) {
|
||||
open_flags |= File::CLOEXEC;
|
||||
}
|
||||
|
||||
let mut open_flags = 0;
|
||||
match flags & OpenFlags::O_ACCESS {
|
||||
OpenFlags::O_RDONLY => open_flags |= File::READ,
|
||||
OpenFlags::O_WRONLY => open_flags |= File::WRITE,
|
||||
OpenFlags::O_RDWR => open_flags |= File::READ | File::WRITE,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
if flags.contains(OpenFlags::O_CLOEXEC) {
|
||||
open_flags |= File::CLOEXEC;
|
||||
}
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
let pos = data.open(self.clone(), flags)?;
|
||||
Ok(File::normal(self.clone(), pos, open_flags))
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::Directory(_) => {
|
||||
if !flags.contains(OpenFlags::O_DIRECTORY) {
|
||||
return Err(Errno::NotADirectory);
|
||||
}
|
||||
|
||||
if self.flags & Vnode::CACHE_READDIR != 0 {
|
||||
Ok(File::normal(self.clone(), File::POS_CACHE_DOT, open_flags))
|
||||
} else {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
VnodeData::File(file) => {
|
||||
if flags.contains(OpenFlags::O_DIRECTORY) {
|
||||
return Err(Errno::IsADirectory);
|
||||
}
|
||||
|
||||
if let Some(ref mut file) = *file.borrow_mut() {
|
||||
let pos = file.open(self.clone(), flags)?;
|
||||
Ok(File::normal(self.clone(), pos, open_flags))
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
}
|
||||
}
|
||||
VnodeData::Char(_) => {
|
||||
if flags.contains(OpenFlags::O_DIRECTORY) {
|
||||
return Err(Errno::IsADirectory);
|
||||
}
|
||||
|
||||
Ok(File::normal(self.clone(), 0, open_flags))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Closes a vnode
|
||||
pub fn close(self: &VnodeRef) -> Result<(), Errno> {
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.close(self.clone())
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::Directory(dir) => {
|
||||
if let Some(ref mut dir) = *dir.borrow_mut() {
|
||||
return dir.close(self.clone());
|
||||
}
|
||||
}
|
||||
VnodeData::File(file) => {
|
||||
if let Some(ref mut file) = *file.borrow_mut() {
|
||||
return file.close(self.clone());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(Errno::NotImplemented)
|
||||
}
|
||||
|
||||
/// Reads data from offset `pos` into `buf`
|
||||
pub fn read(self: &VnodeRef, pos: usize, buf: &mut [u8]) -> Result<usize, Errno> {
|
||||
if self.kind == VnodeKind::Directory {
|
||||
return Err(Errno::IsADirectory);
|
||||
}
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.read(self.clone(), pos, buf)
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.read(self.clone(), pos, buf),
|
||||
VnodeData::Char(chr) => chr.read(true, buf),
|
||||
_ => Err(Errno::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes data from `buf` to offset `pos`
|
||||
pub fn write(self: &VnodeRef, pos: usize, buf: &[u8]) -> Result<usize, Errno> {
|
||||
if self.kind == VnodeKind::Directory {
|
||||
return Err(Errno::IsADirectory);
|
||||
}
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.write(self.clone(), pos, buf)
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.write(self.clone(), pos, buf),
|
||||
VnodeData::Char(chr) => chr.write(true, buf),
|
||||
_ => Err(Errno::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resizes the vnode data
|
||||
pub fn truncate(self: &VnodeRef, size: usize) -> Result<(), Errno> {
|
||||
if self.kind != VnodeKind::Regular {
|
||||
return Err(Errno::IsADirectory);
|
||||
}
|
||||
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.truncate(self.clone(), size)
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.truncate(self.clone(), size),
|
||||
_ => Err(Errno::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current vnode data size
|
||||
pub fn size(self: &VnodeRef) -> Result<usize, Errno> {
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.size(self.clone())
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.size(self.clone()),
|
||||
VnodeData::Directory(dir) => dir
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.size(self.clone()),
|
||||
VnodeData::Char(_) => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reports file status
|
||||
pub fn stat(self: &VnodeRef, stat: &mut Stat) -> Result<(), Errno> {
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.stat(self.clone(), stat)
|
||||
pub fn stat(self: &VnodeRef) -> Result<Stat, Errno> {
|
||||
if self.flags & Self::CACHE_STAT != 0 {
|
||||
let props = self.props();
|
||||
Ok(Stat {
|
||||
blksize: 0,
|
||||
size: 0,
|
||||
mode: props.mode,
|
||||
})
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotADirectory)?
|
||||
.stat(self.clone()),
|
||||
VnodeData::Directory(dir) => dir
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.stat(self.clone()),
|
||||
// TODO stat() for char/blk devs
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs node-specific requests
|
||||
pub fn ioctl(self: &VnodeRef, cmd: IoctlCmd, ptr: usize, len: usize) -> Result<usize, Errno> {
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.ioctl(self.clone(), cmd, ptr, len)
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
match &self.data {
|
||||
VnodeData::Char(chr) => chr.ioctl(cmd, ptr, len),
|
||||
_ => Err(Errno::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_ready(self: &VnodeRef, write: bool) -> Result<bool, Errno> {
|
||||
if let Some(ref mut data) = *self.data() {
|
||||
data.is_ready(self.clone(), write)
|
||||
} else {
|
||||
Err(Errno::NotImplemented)
|
||||
/// Returns `true` if the node is ready for operation
|
||||
pub fn ready(self: &VnodeRef, write: bool) -> Result<bool, Errno> {
|
||||
match &self.data {
|
||||
VnodeData::File(file) => file
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.ok_or(Errno::NotImplemented)?
|
||||
.ready(self.clone(), write),
|
||||
_ => Err(Errno::InvalidOperation),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if given [Ioctx] has `access` permissions to the vnode
|
||||
pub fn check_access(&self, _ioctx: &Ioctx, access: AccessMode) -> Result<(), Errno> {
|
||||
let props = self.props.borrow();
|
||||
let mode = props.mode;
|
||||
|
||||
if access.contains(AccessMode::F_OK) {
|
||||
if access.intersects(AccessMode::R_OK | AccessMode::W_OK | AccessMode::X_OK) {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
} else {
|
||||
if access.contains(AccessMode::F_OK) {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
// Check user
|
||||
if access.contains(AccessMode::R_OK) && !mode.contains(FileMode::USER_READ) {
|
||||
return Err(Errno::PermissionDenied);
|
||||
}
|
||||
if access.contains(AccessMode::W_OK) && !mode.contains(FileMode::USER_WRITE) {
|
||||
return Err(Errno::PermissionDenied);
|
||||
}
|
||||
if access.contains(AccessMode::X_OK) && !mode.contains(FileMode::USER_EXEC) {
|
||||
return Err(Errno::PermissionDenied);
|
||||
}
|
||||
|
||||
// TODO check group
|
||||
// TODO check other
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Vnode {
|
||||
@@ -423,32 +601,80 @@ mod tests {
|
||||
use libsys::{ioctl::IoctlCmd, stat::OpenFlags, stat::Stat};
|
||||
pub struct DummyInode;
|
||||
|
||||
#[auto_inode]
|
||||
impl VnodeImpl for DummyInode {
|
||||
fn create(
|
||||
// TODO derive macro for this
|
||||
impl VnodeCommon for DummyInode {
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_at: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
let node = Vnode::new(name, kind, 0);
|
||||
node.set_data(Box::new(DummyInode {}));
|
||||
Ok(node)
|
||||
node: VnodeRef,
|
||||
cmd: IoctlCmd,
|
||||
ptr: usize,
|
||||
len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn remove(&mut self, _at: VnodeRef, _name: &str) -> Result<(), Errno> {
|
||||
/// Retrieves file status
|
||||
fn stat(&mut self, node: VnodeRef) -> Result<Stat, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, node: VnodeRef) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn ready(&mut self, node: VnodeRef, write: bool) -> Result<bool, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn open(&mut self, _node: VnodeRef, _flags: OpenFlags) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&mut self, _at: VnodeRef, _name: &str) -> Result<VnodeRef, Errno> {
|
||||
impl VnodeDirectory for DummyInode {
|
||||
fn create(
|
||||
&mut self,
|
||||
at: VnodeRef,
|
||||
name: &str,
|
||||
kind: VnodeCreateKind,
|
||||
) -> Result<VnodeRef, Errno> {
|
||||
let data = match kind {
|
||||
VnodeCreateKind::Directory => {
|
||||
VnodeData::Directory(RefCell::new(Some(Box::new(DummyInode {}))))
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
Ok(Vnode::new(name, data, 0))
|
||||
}
|
||||
fn remove(&mut self, at: VnodeRef, name: &str) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
fn lookup(&mut self, at: VnodeRef, name: &str) -> Result<VnodeRef, Errno> {
|
||||
Err(Errno::DoesNotExist)
|
||||
}
|
||||
|
||||
/// Read directory entries into target buffer
|
||||
fn readdir(
|
||||
&mut self,
|
||||
node: VnodeRef,
|
||||
pos: usize,
|
||||
data: &mut [DirectoryEntry],
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parent() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let node = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let root = Vnode::new("", VnodeData::Directory(RefCell::new(None)), 0);
|
||||
let node = Vnode::new("dir0", VnodeData::Directory(RefCell::new(None)), 0);
|
||||
|
||||
root.attach(node.clone());
|
||||
|
||||
@@ -458,23 +684,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_mkdir_unlink() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
|
||||
root.set_data(Box::new(DummyInode {}));
|
||||
let root = Vnode::new(
|
||||
"",
|
||||
VnodeData::Directory(RefCell::new(Some(Box::new(DummyInode {})))),
|
||||
0,
|
||||
);
|
||||
|
||||
let node = root
|
||||
.create("test", FileMode::default_dir(), VnodeKind::Directory)
|
||||
.create("test", FileMode::default_dir(), VnodeCreateKind::Directory)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
root.create("test", FileMode::default_dir(), VnodeKind::Directory)
|
||||
root.create("test", FileMode::default_dir(), VnodeCreateKind::Directory)
|
||||
.unwrap_err(),
|
||||
Errno::AlreadyExists
|
||||
);
|
||||
|
||||
assert_eq!(node.props.borrow().mode, FileMode::default_dir());
|
||||
assert!(Rc::ptr_eq(&node, &root.lookup("test").unwrap()));
|
||||
assert!(node.data.borrow().is_some());
|
||||
let inner = node.as_directory().unwrap();
|
||||
assert!(matches!(*inner.borrow_mut(), Some(_)));
|
||||
// assert!(node.data.borrow().is_some());
|
||||
|
||||
root.unlink("test").unwrap();
|
||||
|
||||
@@ -483,9 +713,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_lookup_attach_detach() {
|
||||
let root = Vnode::new("", VnodeKind::Directory, 0);
|
||||
let dir0 = Vnode::new("dir0", VnodeKind::Directory, 0);
|
||||
let dir1 = Vnode::new("dir1", VnodeKind::Directory, 0);
|
||||
let root = Vnode::new("", VnodeData::Directory(RefCell::new(None)), 0);
|
||||
let dir0 = Vnode::new("dir0", VnodeData::Directory(RefCell::new(None)), 0);
|
||||
let dir1 = Vnode::new("dir1", VnodeData::Directory(RefCell::new(None)), 0);
|
||||
|
||||
root.attach(dir0.clone());
|
||||
root.attach(dir1.clone());
|
||||
|
||||
+8
-2
@@ -15,17 +15,23 @@ memfs = { path = "../fs/memfs" }
|
||||
libsys = { path = "../libsys" }
|
||||
cfg-if = "1.x.x"
|
||||
tock-registers = "0.7.x"
|
||||
fdt-rs = { version = "0.x.x", default-features = false }
|
||||
bitflags = "^1.3.0"
|
||||
kernel-macros = { path = "macros" }
|
||||
fs-macros = { path = "../fs/macros" }
|
||||
|
||||
[target.'cfg(target_arch = "x86_64")'.dependencies]
|
||||
multiboot2 = { git = "https://github.com/alnyan/multiboot2", branch = "expose-extra-traits-for-iters" }
|
||||
|
||||
[target.'cfg(target_arch = "aarch64")'.dependencies]
|
||||
cortex-a = { version = "6.x.x" }
|
||||
cortex-a = { version = "7.0.x" }
|
||||
fdt-rs = { version = "0.x.x", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["aggressive_syscall"]
|
||||
pl011 = []
|
||||
pl031 = []
|
||||
verbose = []
|
||||
aggressive_syscall = []
|
||||
|
||||
mach_qemu = ["pl011", "pl031"]
|
||||
mach_orangepi3 = []
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.macro MOV_L reg, value
|
||||
mov \reg, #((\value) & 0xFFFF)
|
||||
movk \reg, #((\value) >> 16), lsl #16
|
||||
.endm
|
||||
|
||||
.macro ADR_REL reg, sym
|
||||
adrp \reg, \sym
|
||||
add \reg, \reg, #:lo12:\sym
|
||||
.endm
|
||||
|
||||
.macro ADR_ABS reg, sym
|
||||
movz \reg, #:abs_g3:\sym
|
||||
movk \reg, #:abs_g2_nc:\sym
|
||||
movk \reg, #:abs_g1_nc:\sym
|
||||
movk \reg, #:abs_g0_nc:\sym
|
||||
.endm
|
||||
@@ -5,12 +5,14 @@ use crate::arch::{
|
||||
machine,
|
||||
};
|
||||
use crate::config::{ConfigKey, CONFIG};
|
||||
use crate::dev::pseudo;
|
||||
use crate::dev::{
|
||||
fdt::{find_prop, DeviceTree},
|
||||
irq::IntSource,
|
||||
Device,
|
||||
};
|
||||
use crate::fs::devfs;
|
||||
use crate::fs::{devfs, sysfs};
|
||||
use core::arch::global_asm;
|
||||
use libsys::error::Errno;
|
||||
//use crate::debug::Level;
|
||||
use crate::mem::{
|
||||
@@ -23,22 +25,16 @@ use cortex_a::asm::barrier::{self, dsb, isb};
|
||||
use cortex_a::registers::{SCTLR_EL1, VBAR_EL1};
|
||||
use tock_registers::interfaces::{ReadWriteable, Writeable};
|
||||
|
||||
fn init_device_tree(fdt_base_phys: usize) -> Result<(), Errno> {
|
||||
fn init_device_tree(fdt_base_phys: usize) -> Result<Option<DeviceTree>, Errno> {
|
||||
use fdt_rs::prelude::*;
|
||||
|
||||
let fdt = if fdt_base_phys != 0 {
|
||||
DeviceTree::from_phys(fdt_base_phys + 0xFFFFFF8000000000)?
|
||||
} else {
|
||||
warnln!("No FDT present");
|
||||
return Ok(());
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
#[cfg(feature = "verbose")]
|
||||
{
|
||||
use crate::debug::Level;
|
||||
fdt.dump(Level::Debug);
|
||||
}
|
||||
|
||||
let mut cfg = CONFIG.lock();
|
||||
|
||||
if let Some(chosen) = fdt.node_by_path("/chosen") {
|
||||
@@ -56,7 +52,7 @@ fn init_device_tree(fdt_base_phys: usize) -> Result<(), Errno> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(Some(fdt))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -87,7 +83,7 @@ extern "C" fn __aa64_bsp_main(fdt_base: usize) -> ! {
|
||||
// Enable MMU
|
||||
virt::enable().expect("Failed to initialize virtual memory");
|
||||
|
||||
init_device_tree(fdt_base).expect("Device tree init failed");
|
||||
let _fdt = init_device_tree(fdt_base).expect("Device tree init failed");
|
||||
|
||||
// Most basic machine init: initialize proper debug output
|
||||
// physical memory
|
||||
@@ -102,9 +98,19 @@ extern "C" fn __aa64_bsp_main(fdt_base: usize) -> ! {
|
||||
}
|
||||
|
||||
devfs::init();
|
||||
sysfs::init();
|
||||
|
||||
machine::init_board().unwrap();
|
||||
|
||||
// #[cfg(feature = "verbose")]
|
||||
// if let Some(fdt) = fdt {
|
||||
// use crate::debug::Level;
|
||||
// fdt.dump(Level::Debug);
|
||||
// }
|
||||
|
||||
devfs::add_named_char_device(&pseudo::ZERO, "zero").unwrap();
|
||||
devfs::add_named_char_device(&pseudo::RANDOM, "random").unwrap();
|
||||
|
||||
infoln!("Machine init finished");
|
||||
|
||||
unsafe {
|
||||
@@ -115,6 +121,4 @@ extern "C" fn __aa64_bsp_main(fdt_base: usize) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(include_str!("macros.S"));
|
||||
global_asm!(include_str!("uboot.S"));
|
||||
global_asm!(include_str!("upper.S"));
|
||||
|
||||
@@ -1,5 +1,54 @@
|
||||
// vi:ft=a64asm.asm:
|
||||
|
||||
.macro MOV_L reg, value
|
||||
mov \reg, #((\value) & 0xFFFF)
|
||||
movk \reg, #((\value) >> 16), lsl #16
|
||||
.endm
|
||||
|
||||
.macro ADR_REL reg, sym
|
||||
adrp \reg, \sym
|
||||
add \reg, \reg, #:lo12:\sym
|
||||
.endm
|
||||
|
||||
.macro ADR_ABS reg, sym
|
||||
movz \reg, #:abs_g3:\sym
|
||||
movk \reg, #:abs_g2_nc:\sym
|
||||
movk \reg, #:abs_g1_nc:\sym
|
||||
movk \reg, #:abs_g0_nc:\sym
|
||||
.endm
|
||||
|
||||
.set PTE_BLOCK_AF, 1 << 10
|
||||
.set PTE_BLOCK_ISH, 3 << 8
|
||||
.set PTE_PRESENT, 1 << 0
|
||||
|
||||
.set MAIR_EL1_Attr0_Normal_Inner_NC, (4 << 0)
|
||||
.set MAIR_EL1_Attr0_Normal_Outer_NC, (4 << 4)
|
||||
.set MAIR_EL1_Attr1_Device, (0 << 12)
|
||||
.set MAIR_EL1_Attr1_Device_nGnRE, (1 << 8)
|
||||
|
||||
.set ID_AA64MMFR0_EL1_TGran4, (0xF << 28)
|
||||
|
||||
.set TCR_EL1_IPS_SHIFT, 32
|
||||
|
||||
.set TCR_EL1_TG1_4K, (2 << 30)
|
||||
.set TCR_EL1_SH1_Outer, (2 << 28)
|
||||
.set TCR_EL1_ORGN1_NC, (0 << 26)
|
||||
.set TCR_EL1_IRGN1_NC, (0 << 24)
|
||||
.set TCR_EL1_T1SZ_SHIFT, 16
|
||||
|
||||
.set TCR_EL1_TG0_4K, (0 << 14)
|
||||
.set TCR_EL1_SH0_Outer, (2 << 12)
|
||||
.set TCR_EL1_ORGN0_NC, (0 << 10)
|
||||
.set TCR_EL1_IRGN0_NC, (0 << 8)
|
||||
.set TCR_EL1_T0SZ_SHIFT, 0
|
||||
|
||||
.set TCR_EL1_ATTRS, (TCR_EL1_TG1_4K | TCR_EL1_SH1_Outer | TCR_EL1_TG0_4K | TCR_EL1_SH0_Outer | (25 << TCR_EL1_T1SZ_SHIFT) | (25 << TCR_EL1_T0SZ_SHIFT))
|
||||
|
||||
.set SCTLR_EL1_I, (1 << 12)
|
||||
.set SCTLR_EL1_C, (1 << 2)
|
||||
.set SCTLR_EL1_M, (1 << 0)
|
||||
|
||||
|
||||
.set SCTLR_EL2_RES1, 0x30C50830
|
||||
|
||||
.set SPSR_EL2_EL1h, 0x5
|
||||
@@ -61,6 +110,68 @@ _entry:
|
||||
ADR_ABS x9, __aa64_entry_upper
|
||||
b __aa64_enter_upper
|
||||
|
||||
.global __aa64_enter_upper
|
||||
.type __aa64_enter_upper, %function
|
||||
__aa64_enter_upper:
|
||||
// x8 -- FDT base
|
||||
// x9 -- upper entry point
|
||||
|
||||
// Setup TTBR1_EL1
|
||||
// TODO fix macros
|
||||
ADR_ABS x5, KERNEL_TTBR1
|
||||
ADR_ABS x6, KERNEL_OFFSET
|
||||
|
||||
// x5 = KERNEL_TTBR1 physical address
|
||||
sub x5, x5, x6
|
||||
|
||||
// Fill KERNEL_TTBR1 table with upper-mapped Normal memory
|
||||
.fill_ttbr1:
|
||||
mov x2, #256
|
||||
1:
|
||||
sub x2, x2, #1
|
||||
|
||||
// x0 = (x2 << 30) | attrs...
|
||||
lsl x1, x2, #30
|
||||
mov x0, #(PTE_BLOCK_ISH | PTE_BLOCK_AF | PTE_PRESENT)
|
||||
orr x0, x0, x1
|
||||
|
||||
str x0, [x5, x2, lsl #3]
|
||||
|
||||
cbnz x2, 1b
|
||||
|
||||
.init_mmu_regs:
|
||||
mov x0, #(MAIR_EL1_Attr0_Normal_Outer_NC | MAIR_EL1_Attr0_Normal_Inner_NC | MAIR_EL1_Attr1_Device | MAIR_EL1_Attr1_Device_nGnRE)
|
||||
msr mair_el1, x0
|
||||
|
||||
// Test for 4KiB page support
|
||||
mrs x0, ID_AA64MMFR0_EL1
|
||||
mov x1, ID_AA64MMFR0_EL1_TGran4
|
||||
tst x0, x1
|
||||
bne .no_4k_gran
|
||||
|
||||
// x0 = PARange
|
||||
and x0, x0, #0xF
|
||||
lsl x0, x0, #TCR_EL1_IPS_SHIFT
|
||||
MOV_L x1, TCR_EL1_ATTRS
|
||||
orr x0, x0, x1
|
||||
msr tcr_el1, x0
|
||||
|
||||
msr ttbr0_el1, x5
|
||||
msr ttbr1_el1, x5
|
||||
|
||||
dsb ish
|
||||
isb
|
||||
|
||||
mrs x0, sctlr_el1
|
||||
orr x0, x0, #SCTLR_EL1_M
|
||||
msr sctlr_el1, x0
|
||||
|
||||
mov x0, x8
|
||||
br x9
|
||||
.no_4k_gran:
|
||||
b .
|
||||
.size __aa64_enter_upper, . - __aa64_enter_upper
|
||||
|
||||
.section .text._entry_upper
|
||||
__aa64_entry_upper:
|
||||
// x0 -- fdt address
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
// vi:ft=a64asm:
|
||||
|
||||
.set PTE_BLOCK_AF, 1 << 10
|
||||
.set PTE_BLOCK_ISH, 3 << 8
|
||||
.set PTE_PRESENT, 1 << 0
|
||||
|
||||
.set MAIR_EL1_Attr0_Normal_Inner_NC, (4 << 0)
|
||||
.set MAIR_EL1_Attr0_Normal_Outer_NC, (4 << 4)
|
||||
.set MAIR_EL1_Attr1_Device, (0 << 12)
|
||||
.set MAIR_EL1_Attr1_Device_nGnRE, (1 << 8)
|
||||
|
||||
.set ID_AA64MMFR0_EL1_TGran4, (0xF << 28)
|
||||
|
||||
.set TCR_EL1_IPS_SHIFT, 32
|
||||
|
||||
.set TCR_EL1_TG1_4K, (2 << 30)
|
||||
.set TCR_EL1_SH1_Outer, (2 << 28)
|
||||
.set TCR_EL1_ORGN1_NC, (0 << 26)
|
||||
.set TCR_EL1_IRGN1_NC, (0 << 24)
|
||||
.set TCR_EL1_T1SZ_SHIFT, 16
|
||||
|
||||
.set TCR_EL1_TG0_4K, (0 << 14)
|
||||
.set TCR_EL1_SH0_Outer, (2 << 12)
|
||||
.set TCR_EL1_ORGN0_NC, (0 << 10)
|
||||
.set TCR_EL1_IRGN0_NC, (0 << 8)
|
||||
.set TCR_EL1_T0SZ_SHIFT, 0
|
||||
|
||||
.set TCR_EL1_ATTRS, (TCR_EL1_TG1_4K | TCR_EL1_SH1_Outer | TCR_EL1_TG0_4K | TCR_EL1_SH0_Outer | (25 << TCR_EL1_T1SZ_SHIFT) | (25 << TCR_EL1_T0SZ_SHIFT))
|
||||
|
||||
.set SCTLR_EL1_I, (1 << 12)
|
||||
.set SCTLR_EL1_C, (1 << 2)
|
||||
.set SCTLR_EL1_M, (1 << 0)
|
||||
|
||||
.section .text._entry
|
||||
.global __aa64_enter_upper
|
||||
.type __aa64_enter_upper, %function
|
||||
__aa64_enter_upper:
|
||||
// x8 -- FDT base
|
||||
// x9 -- upper entry point
|
||||
|
||||
// Setup TTBR1_EL1
|
||||
// TODO fix macros
|
||||
ADR_ABS x5, KERNEL_TTBR1
|
||||
ADR_ABS x6, KERNEL_OFFSET
|
||||
|
||||
// x5 = KERNEL_TTBR1 physical address
|
||||
sub x5, x5, x6
|
||||
|
||||
// Fill KERNEL_TTBR1 table with upper-mapped Normal memory
|
||||
.fill_ttbr1:
|
||||
mov x2, #256
|
||||
1:
|
||||
sub x2, x2, #1
|
||||
|
||||
// x0 = (x2 << 30) | attrs...
|
||||
lsl x1, x2, #30
|
||||
mov x0, #(PTE_BLOCK_ISH | PTE_BLOCK_AF | PTE_PRESENT)
|
||||
orr x0, x0, x1
|
||||
|
||||
str x0, [x5, x2, lsl #3]
|
||||
|
||||
cbnz x2, 1b
|
||||
|
||||
.init_mmu_regs:
|
||||
mov x0, #(MAIR_EL1_Attr0_Normal_Outer_NC | MAIR_EL1_Attr0_Normal_Inner_NC | MAIR_EL1_Attr1_Device | MAIR_EL1_Attr1_Device_nGnRE)
|
||||
msr mair_el1, x0
|
||||
|
||||
// Test for 4KiB page support
|
||||
mrs x0, ID_AA64MMFR0_EL1
|
||||
mov x1, ID_AA64MMFR0_EL1_TGran4
|
||||
tst x0, x1
|
||||
bne .no_4k_gran
|
||||
|
||||
// x0 = PARange
|
||||
and x0, x0, #0xF
|
||||
lsl x0, x0, #TCR_EL1_IPS_SHIFT
|
||||
MOV_L x1, TCR_EL1_ATTRS
|
||||
orr x0, x0, x1
|
||||
msr tcr_el1, x0
|
||||
|
||||
msr ttbr0_el1, x5
|
||||
msr ttbr1_el1, x5
|
||||
|
||||
dsb ish
|
||||
isb
|
||||
|
||||
mrs x0, sctlr_el1
|
||||
orr x0, x0, #SCTLR_EL1_M
|
||||
msr sctlr_el1, x0
|
||||
|
||||
mov x0, x8
|
||||
br x9
|
||||
.no_4k_gran:
|
||||
b .
|
||||
.size __aa64_enter_upper, . - __aa64_enter_upper
|
||||
@@ -32,27 +32,6 @@ __aa64_ctx_enter_kernel:
|
||||
eret
|
||||
|
||||
__aa64_ctx_enter_from_fork:
|
||||
// stack.push(frame.x[18]);
|
||||
// stack.push(frame.x[17]);
|
||||
// stack.push(frame.x[16]);
|
||||
// stack.push(frame.x[15]);
|
||||
// stack.push(frame.x[14]);
|
||||
// stack.push(frame.x[13]);
|
||||
// stack.push(frame.x[12]);
|
||||
// stack.push(frame.x[11]);
|
||||
// stack.push(frame.x[10]);
|
||||
// stack.push(frame.x[9]);
|
||||
// stack.push(frame.x[8]);
|
||||
// stack.push(frame.x[7]);
|
||||
// stack.push(frame.x[6]);
|
||||
// stack.push(frame.x[5]);
|
||||
// stack.push(frame.x[4]);
|
||||
// stack.push(frame.x[3]);
|
||||
// stack.push(frame.x[2]);
|
||||
// stack.push(frame.x[1]);
|
||||
|
||||
// stack.push(frame.elr_el1 as usize);
|
||||
// stack.push(frame.sp_el0 as usize);
|
||||
ldp x0, x1, [sp, #16 * 0]
|
||||
msr sp_el0, x0
|
||||
msr elr_el1, x1
|
||||
@@ -82,7 +61,8 @@ __aa64_ctx_switch:
|
||||
stp x27, x28, [sp, #16 * 4]
|
||||
stp x29, x30, [sp, #16 * 5]
|
||||
mrs x19, TTBR0_EL1
|
||||
stp x19, xzr, [sp, #16 * 6]
|
||||
mrs x20, TPIDR_EL0
|
||||
stp x19, x20, [sp, #16 * 6]
|
||||
|
||||
mov x19, sp
|
||||
str x19, [x1]
|
||||
@@ -90,8 +70,9 @@ __aa64_ctx_switch_to:
|
||||
ldr x0, [x0]
|
||||
mov sp, x0
|
||||
|
||||
ldp x19, xzr, [sp, #16 * 6]
|
||||
ldp x19, x20, [sp, #16 * 6]
|
||||
msr TTBR0_EL1, x19
|
||||
msr TPIDR_EL0, x20
|
||||
ldp x19, x20, [sp, #16 * 0]
|
||||
ldp x21, x22, [sp, #16 * 1]
|
||||
ldp x23, x24, [sp, #16 * 2]
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
};
|
||||
use core::arch::global_asm;
|
||||
use core::mem::size_of;
|
||||
|
||||
struct Stack {
|
||||
@@ -67,7 +68,7 @@ impl Context {
|
||||
stack.push(frame.sp_el0 as usize);
|
||||
|
||||
// Setup common
|
||||
stack.push(0);
|
||||
stack.push(0); // tpidr_el0
|
||||
stack.push(ttbr0);
|
||||
stack.push(__aa64_ctx_enter_from_fork as usize); // x30/lr
|
||||
stack.push(frame.x[29]); // x29
|
||||
@@ -96,7 +97,7 @@ impl Context {
|
||||
|
||||
stack.push(entry);
|
||||
stack.push(arg);
|
||||
stack.push(/* ttbr0 */ 0);
|
||||
stack.push(0);
|
||||
stack.push(ustack);
|
||||
|
||||
stack.setup_common(__aa64_ctx_enter_user as usize, ttbr0);
|
||||
@@ -115,7 +116,7 @@ impl Context {
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
stack_base: stack.bp,
|
||||
stack_page_count: 8
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +125,13 @@ impl Context {
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: may clobber an already active context
|
||||
pub unsafe fn setup_signal_entry(&mut self, entry: usize, arg: usize, ttbr0: usize, ustack: usize) {
|
||||
pub unsafe fn setup_signal_entry(
|
||||
&mut self,
|
||||
entry: usize,
|
||||
arg: usize,
|
||||
ttbr0: usize,
|
||||
ustack: usize,
|
||||
) {
|
||||
let mut stack = Stack::from_base_size(self.stack_base, self.stack_page_count);
|
||||
|
||||
stack.push(entry);
|
||||
@@ -171,12 +178,12 @@ impl Stack {
|
||||
pub unsafe fn from_base_size(bp: usize, page_count: usize) -> Stack {
|
||||
Stack {
|
||||
bp,
|
||||
sp: bp + page_count * mem::PAGE_SIZE
|
||||
sp: bp + page_count * mem::PAGE_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_common(&mut self, entry: usize, ttbr: usize) {
|
||||
self.push(0);
|
||||
self.push(0); // tpidr_el0
|
||||
self.push(ttbr);
|
||||
self.push(entry); // x30/lr
|
||||
self.push(0); // x29
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//! AArch64 exception handling
|
||||
|
||||
use crate::arch::machine;
|
||||
use crate::arch::{intrin, machine};
|
||||
use crate::debug::Level;
|
||||
use crate::dev::irq::{IntController, IrqContext};
|
||||
use crate::mem;
|
||||
use crate::proc::{sched, Process};
|
||||
use crate::mem::{self, virt::table::Space};
|
||||
use crate::proc::{sched, Thread};
|
||||
use crate::syscall;
|
||||
use core::arch::global_asm;
|
||||
use cortex_a::registers::{ESR_EL1, FAR_EL1};
|
||||
use libsys::{abi, signal::Signal};
|
||||
use libsys::{abi::SystemCall, error::Errno, signal::Signal};
|
||||
use tock_registers::interfaces::Readable;
|
||||
|
||||
/// Trapped SIMD/FP functionality
|
||||
@@ -58,7 +59,7 @@ const fn data_abort_access_size(iss: u64) -> &'static str {
|
||||
#[no_mangle]
|
||||
extern "C" fn __aa64_exc_irq_handler(_exc: &mut ExceptionFrame) {
|
||||
unsafe {
|
||||
let ic = IrqContext::new();
|
||||
let ic = IrqContext::new(0);
|
||||
machine::intc().handle_pending_irqs(&ic);
|
||||
}
|
||||
}
|
||||
@@ -88,56 +89,73 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) {
|
||||
match err_code {
|
||||
EC_DATA_ABORT_EL0 | EC_DATA_ABORT_ELX => {
|
||||
let far = FAR_EL1.get() as usize;
|
||||
let iss = esr & 0x1FFFFFF;
|
||||
|
||||
if far < mem::KERNEL_OFFSET && sched::is_ready() {
|
||||
let proc = Process::current();
|
||||
if iss & (1 << 6) != 0 && far < mem::KERNEL_OFFSET && sched::is_ready() {
|
||||
let thread = Thread::current();
|
||||
let proc = thread.owner().unwrap();
|
||||
let asid = proc.asid();
|
||||
|
||||
if proc
|
||||
.manipulate_space(|space| space.try_cow_copy(far))
|
||||
.is_err()
|
||||
{
|
||||
let res = proc.manipulate_space(|space| {
|
||||
space.try_cow_copy(far)?;
|
||||
unsafe {
|
||||
intrin::flush_tlb_asid(asid);
|
||||
}
|
||||
Result::<(), Errno>::Ok(())
|
||||
});
|
||||
|
||||
if res.is_err() {
|
||||
// Kill program
|
||||
errorln!("Data abort from {:#x}", exc.elr_el1);
|
||||
dump_data_abort(Level::Error, esr, far as u64);
|
||||
proc.enter_signal(Signal::SegmentationFault);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
use cortex_a::registers::TTBR0_EL1;
|
||||
let ttbr = TTBR0_EL1.get() as usize;
|
||||
let asid = (ttbr >> 48) & 0xFF;
|
||||
asm!("tlbi aside1, {}", in(reg) (asid << 48));
|
||||
proc.enter_fault_signal(thread, Signal::SegmentationFault);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
errorln!("Unresolved data abort");
|
||||
errorln!("Data abort from {:#x}", exc.elr_el1);
|
||||
dump_data_abort(Level::Error, esr, far as u64);
|
||||
}
|
||||
EC_SVC_AA64 => {
|
||||
unsafe {
|
||||
if exc.x[8] == abi::SYS_FORK {
|
||||
match syscall::sys_fork(exc) {
|
||||
Ok(pid) => exc.x[0] = pid.value() as usize,
|
||||
Err(err) => {
|
||||
exc.x[0] = err.to_negative_isize() as usize;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
let num = SystemCall::from_repr(exc.x[8]);
|
||||
if num.is_none() {
|
||||
todo!();
|
||||
}
|
||||
let num = num.unwrap();
|
||||
|
||||
match syscall::syscall(exc.x[8], &exc.x[..6]) {
|
||||
Ok(val) => exc.x[0] = val,
|
||||
if num == SystemCall::Fork {
|
||||
match unsafe { syscall::sys_fork(exc) } {
|
||||
Ok(pid) => exc.x[0] = u32::from(pid) as usize,
|
||||
Err(err) => {
|
||||
exc.x[0] = err.to_negative_isize() as usize;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match syscall::syscall(num, &exc.x[..6]) {
|
||||
Ok(val) => exc.x[0] = val,
|
||||
Err(err) => {
|
||||
exc.x[0] = err.to_negative_isize() as usize;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if sched::is_ready() {
|
||||
let thread = Thread::current();
|
||||
errorln!(
|
||||
"Unhandled exception in thread {:?}, {:?}",
|
||||
thread.id(),
|
||||
thread.owner().map(|e| e.id())
|
||||
);
|
||||
}
|
||||
|
||||
errorln!(
|
||||
"Unhandled exception at ELR={:#018x}, ESR={:#010x}",
|
||||
exc.elr_el1,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
//! AArch64-specific assembly functions
|
||||
use core::arch::asm;
|
||||
|
||||
/// Disables delievery of IRQs
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: requires EL0
|
||||
#[inline(always)]
|
||||
pub unsafe fn irq_disable() {
|
||||
asm!("msr daifset, {bits}", bits = const 2, options(nomem, nostack, preserves_flags));
|
||||
}
|
||||
|
||||
/// Discards an entry related to `addr` from TLB cache
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: requires EL0
|
||||
#[inline(always)]
|
||||
pub unsafe fn flush_tlb_virt(addr: usize) {
|
||||
asm!("tlbi vaae1, {}", in(reg) addr);
|
||||
}
|
||||
|
||||
/// Discards all entries related to `asid` from TLB cache
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to use for known [Process]es and their ASIDs
|
||||
// TODO non-portable
|
||||
#[inline(always)]
|
||||
pub unsafe fn flush_tlb_asid(asid: usize) {
|
||||
asm!("tlbi aside1, {}", in(reg) asid);
|
||||
}
|
||||
@@ -28,7 +28,6 @@ pub struct Gic {
|
||||
gicd: InitOnce<Gicd>,
|
||||
gicd_base: usize,
|
||||
gicc_base: usize,
|
||||
scheduler_irq: IrqNumber,
|
||||
table: IrqSafeSpinLock<[Option<&'static (dyn IntSource + Sync)>; MAX_IRQ]>,
|
||||
}
|
||||
|
||||
@@ -86,9 +85,7 @@ impl IntController for Gic {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.scheduler_irq.0 == irq_number {
|
||||
gicc.clear_irq(irq_number as u32, ic);
|
||||
}
|
||||
gicc.clear_irq(irq_number as u32, ic);
|
||||
|
||||
{
|
||||
let table = self.table.lock();
|
||||
@@ -100,8 +97,6 @@ impl IntController for Gic {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gicc.clear_irq(irq_number as u32, ic);
|
||||
}
|
||||
|
||||
fn register_handler(
|
||||
@@ -128,13 +123,12 @@ impl Gic {
|
||||
/// # Safety
|
||||
///
|
||||
/// Does not perform `gicd_base` and `gicc_base` validation.
|
||||
pub const unsafe fn new(gicd_base: usize, gicc_base: usize, scheduler_irq: IrqNumber) -> Self {
|
||||
pub const unsafe fn new(gicd_base: usize, gicc_base: usize) -> Self {
|
||||
Self {
|
||||
gicc: InitOnce::new(),
|
||||
gicd: InitOnce::new(),
|
||||
gicd_base,
|
||||
gicc_base,
|
||||
scheduler_irq,
|
||||
table: IrqSafeSpinLock::new([None; MAX_IRQ]),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,4 @@ static UART0: Uart = unsafe { Uart::new(UART0_BASE, IrqNumber::new(32)) };
|
||||
static LOCAL_TIMER: GenericTimer = GenericTimer::new(LOCAL_TIMER_IRQ);
|
||||
pub(super) static GPIO: Gpio = unsafe { Gpio::new(PIO_BASE) };
|
||||
static RTC: Rtc = unsafe { Rtc::new(RTC_BASE, RTC_IRQ) };
|
||||
static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE, LOCAL_TIMER_IRQ) };
|
||||
static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE) };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::arch::intrin;
|
||||
use crate::dev::Device;
|
||||
use crate::mem::virt::DeviceMemoryIo;
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
@@ -72,7 +73,7 @@ impl RWdog {
|
||||
regs.CTRL.write(CTRL::KEY::Value + CTRL::RESTART::SET);
|
||||
|
||||
loop {
|
||||
asm!("wfe");
|
||||
intrin::hang();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,6 @@ pub fn intc() -> &'static impl IntController<IrqNumber = IrqNumber> {
|
||||
|
||||
static UART0: Pl011 = unsafe { Pl011::new(UART0_BASE, UART0_IRQ) };
|
||||
static RTC: Pl031 = unsafe { Pl031::new(RTC_BASE, RTC_IRQ) };
|
||||
static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE, LOCAL_TIMER_IRQ) };
|
||||
static GIC: Gic = unsafe { Gic::new(GICD_BASE, GICC_BASE) };
|
||||
static PCIE: GenericPcieHost = unsafe { GenericPcieHost::new(ECAM_BASE, 8) };
|
||||
static LOCAL_TIMER: GenericTimer = GenericTimer::new(LOCAL_TIMER_IRQ);
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::dev::{
|
||||
serial::{pl011::Pl011, SerialDevice},
|
||||
Device,
|
||||
};
|
||||
use crate::fs::devfs::{self, CharDeviceType};
|
||||
use crate::mem::phys;
|
||||
use libsys::error::Errno;
|
||||
|
||||
@@ -37,6 +38,7 @@ pub fn init_board() -> Result<(), Errno> {
|
||||
unsafe {
|
||||
IRQCHIP.enable()?;
|
||||
UART.init_irqs()?;
|
||||
devfs::add_char_device(&UART, CharDeviceType::TtySerial)?;
|
||||
|
||||
EMMC.enable()?;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@ use tock_registers::interfaces::{Readable, Writeable};
|
||||
pub mod boot;
|
||||
pub mod context;
|
||||
pub mod exception;
|
||||
pub mod intrin;
|
||||
pub mod irq;
|
||||
pub mod reg;
|
||||
pub mod timer;
|
||||
pub mod virt;
|
||||
|
||||
pub use exception::ExceptionFrame as ForkFrame;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "mach_qemu")] {
|
||||
@@ -34,7 +38,7 @@ cfg_if! {
|
||||
#[inline(always)]
|
||||
pub unsafe fn irq_mask_save() -> u64 {
|
||||
let state = DAIF.get();
|
||||
asm!("msr daifset, {bits}", bits = const 2, options(nomem, nostack, preserves_flags));
|
||||
intrin::irq_disable();
|
||||
state
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
//! CNTKCTL_EL1 register
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
/// Counter-timer Kernel Control Register
|
||||
pub CNTKCTL_EL1 [
|
||||
/// If set, disables CNTPCT and CNTFRQ trapping from EL0
|
||||
EL0PCTEN OFFSET(0) NUMBITS(1) []
|
||||
]
|
||||
}
|
||||
|
||||
/// CNTKCTL_EL1 register
|
||||
pub struct Reg;
|
||||
|
||||
impl Readable for Reg {
|
||||
type T = u64;
|
||||
type R = CNTKCTL_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut tmp;
|
||||
unsafe {
|
||||
asm!("mrs {}, cntkctl_el1", out(reg) tmp);
|
||||
}
|
||||
tmp
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Reg {
|
||||
type T = u64;
|
||||
type R = CNTKCTL_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!("msr cntkctl_el1, {}", in(reg) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CNTKCTL_EL1 register
|
||||
pub const CNTKCTL_EL1: Reg = Reg;
|
||||
@@ -1,57 +0,0 @@
|
||||
//! CPACR_EL1 register
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
/// EL1 Architectural Feature Access Control Register
|
||||
pub CPACR_EL1 [
|
||||
/// Enable EL0 and EL1 SIMD/FP accesses to EL1
|
||||
FPEN OFFSET(20) NUMBITS(2) [
|
||||
/// Trap both EL0 and EL1
|
||||
TrapAll = 0,
|
||||
/// Trap EL0
|
||||
TrapEl0 = 1,
|
||||
/// Trap EL1
|
||||
TrapEl1 = 2,
|
||||
/// Do not trap any SIMD/FP instructions
|
||||
TrapNone = 3
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
/// CPACR_EL1 register
|
||||
pub struct Reg;
|
||||
|
||||
impl Readable for Reg {
|
||||
type T = u64;
|
||||
type R = CPACR_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut tmp;
|
||||
unsafe {
|
||||
asm!("mrs {}, cpacr_el1", out(reg) tmp);
|
||||
}
|
||||
tmp
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Reg {
|
||||
type T = u64;
|
||||
type R = CPACR_EL1::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!("msr cpacr_el1, {}", in(reg) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CPACR_EL1 register
|
||||
pub const CPACR_EL1: Reg = Reg;
|
||||
@@ -1,7 +1,68 @@
|
||||
//! AArch64 architectural registers
|
||||
|
||||
pub mod cpacr_el1;
|
||||
pub use cpacr_el1::CPACR_EL1;
|
||||
use core::arch::asm;
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
|
||||
pub mod cntkctl_el1;
|
||||
pub use cntkctl_el1::CNTKCTL_EL1;
|
||||
macro_rules! wrap_msr {
|
||||
($struct_name:ident, $name:ident, $reg:literal, $fields:tt) => {
|
||||
#[allow(missing_docs)]
|
||||
pub struct $struct_name;
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
#[allow(missing_docs)]
|
||||
pub $name $fields
|
||||
}
|
||||
|
||||
impl Readable for $struct_name {
|
||||
type T = u64;
|
||||
type R = $name::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut value;
|
||||
unsafe {
|
||||
asm!(concat!("mrs {}, ", $reg), out(reg) value)
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for $struct_name {
|
||||
type T = u64;
|
||||
type R = $name::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!(concat!("msr ", $reg, ", {}"), in(reg) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub const $name: $struct_name = $struct_name;
|
||||
};
|
||||
}
|
||||
|
||||
wrap_msr!(CpacrEl1, CPACR_EL1, "cpacr_el1", [
|
||||
/// Enable EL0 and EL1 SIMD/FP accesses to EL1
|
||||
FPEN OFFSET(20) NUMBITS(2) [
|
||||
/// Trap both EL0 and EL1
|
||||
TrapAll = 0,
|
||||
/// Trap EL0
|
||||
TrapEl0 = 1,
|
||||
/// Trap EL1
|
||||
TrapEl1 = 2,
|
||||
/// Do not trap any SIMD/FP instructions
|
||||
TrapNone = 3
|
||||
]
|
||||
]);
|
||||
|
||||
wrap_msr!(CntkctlEl1, CNTKCTL_EL1, "cntkctl_el1", [
|
||||
/// If set, disables CNTPCT and CNTFRQ trapping from EL0
|
||||
EL0PCTEN OFFSET(0) NUMBITS(1) []
|
||||
]);
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
use crate::arch::machine::{self, IrqNumber};
|
||||
use crate::dev::{
|
||||
irq::{IntController, IntSource},
|
||||
pseudo,
|
||||
timer::TimestampSource,
|
||||
Device,
|
||||
};
|
||||
use crate::proc;
|
||||
use core::time::Duration;
|
||||
use cortex_a::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0};
|
||||
use libsys::error::Errno;
|
||||
@@ -34,9 +36,9 @@ impl IntSource for GenericTimer {
|
||||
fn handle_irq(&self) -> Result<(), Errno> {
|
||||
CNTP_TVAL_EL0.set(TIMER_TICK);
|
||||
CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET);
|
||||
use crate::proc;
|
||||
proc::wait::tick();
|
||||
proc::switch();
|
||||
pseudo::RANDOM.set_state(CNTPCT_EL0.get() as u32);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,9 +52,11 @@ impl IntSource for GenericTimer {
|
||||
|
||||
impl TimestampSource for GenericTimer {
|
||||
fn timestamp(&self) -> Result<Duration, Errno> {
|
||||
let cnt = CNTPCT_EL0.get() * 1_000_000_000;
|
||||
let frq = CNTFRQ_EL0.get();
|
||||
Ok(Duration::from_nanos(cnt / frq))
|
||||
let cnt = (CNTPCT_EL0.get() as u128) * 1_000_000_000u128;
|
||||
let frq = CNTFRQ_EL0.get() as u128;
|
||||
let secs = ((cnt / frq) / 1_000_000_000) as u64;
|
||||
let nanos = ((cnt / frq) % 1_000_000_000) as u32;
|
||||
Ok(Duration::new(secs, nanos))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
//! Fixed-size table group for device MMIO mappings
|
||||
|
||||
use crate::mem::{
|
||||
self,
|
||||
virt::{Entry, MapAttributes, Table},
|
||||
};
|
||||
use super::{table::TableImpl, EntryImpl};
|
||||
use crate::mem;
|
||||
use crate::mem::virt::table::{Entry, MapAttributes};
|
||||
use cortex_a::asm::barrier::{self, dsb, isb};
|
||||
use libsys::error::Errno;
|
||||
|
||||
const DEVICE_MAP_OFFSET: usize = mem::KERNEL_OFFSET + (256usize << 30);
|
||||
|
||||
/// Fixed-layout group of tables describing device MMIO and kernel identity
|
||||
/// mappings
|
||||
#[repr(C, align(0x1000))]
|
||||
pub struct FixedTableGroup {
|
||||
l0: Table,
|
||||
l1: Table,
|
||||
l2: Table,
|
||||
l0: TableImpl,
|
||||
l1: TableImpl,
|
||||
l2: TableImpl,
|
||||
|
||||
pages_4k: usize,
|
||||
pages_2m: usize,
|
||||
@@ -27,9 +22,9 @@ impl FixedTableGroup {
|
||||
/// entries
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
l0: Table::empty(),
|
||||
l1: Table::empty(),
|
||||
l2: Table::empty(),
|
||||
l0: TableImpl::empty(),
|
||||
l1: TableImpl::empty(),
|
||||
l2: TableImpl::empty(),
|
||||
|
||||
pages_4k: 0,
|
||||
pages_2m: 1,
|
||||
@@ -44,7 +39,7 @@ impl FixedTableGroup {
|
||||
pub fn map_region(&mut self, phys: usize, count: usize) -> Result<usize, Errno> {
|
||||
// TODO generalize region allocation
|
||||
let phys_page = phys & !0xFFF;
|
||||
let attrs = MapAttributes::SH_OUTER | MapAttributes::DEVICE | MapAttributes::ACCESS;
|
||||
let attrs = MapAttributes::SHARE_OUTER | MapAttributes::DEVICE_MEMORY;
|
||||
|
||||
match count {
|
||||
262144 => {
|
||||
@@ -54,7 +49,7 @@ impl FixedTableGroup {
|
||||
}
|
||||
self.pages_1g += 1;
|
||||
|
||||
self.l0[count + 256] = Entry::block(phys_page, attrs);
|
||||
self.l0[count + 256] = EntryImpl::block(phys_page, attrs | MapAttributes::ACCESS);
|
||||
unsafe {
|
||||
dsb(barrier::SY);
|
||||
isb(barrier::SY);
|
||||
@@ -69,7 +64,7 @@ impl FixedTableGroup {
|
||||
}
|
||||
self.pages_2m += 1;
|
||||
|
||||
self.l1[count] = Entry::block(phys_page, attrs);
|
||||
self.l1[count] = EntryImpl::block(phys_page, attrs | MapAttributes::ACCESS);
|
||||
unsafe {
|
||||
dsb(barrier::SY);
|
||||
isb(barrier::SY);
|
||||
@@ -84,7 +79,7 @@ impl FixedTableGroup {
|
||||
}
|
||||
self.pages_4k += 1;
|
||||
|
||||
self.l2[count] = Entry::table(phys_page, attrs);
|
||||
self.l2[count] = EntryImpl::normal(phys_page, attrs | MapAttributes::ACCESS);
|
||||
unsafe {
|
||||
dsb(barrier::SY);
|
||||
isb(barrier::SY);
|
||||
@@ -95,13 +90,28 @@ impl FixedTableGroup {
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up initial mappings for 4K, 2M and 1G device memory page translation
|
||||
pub fn init_device_map(&mut self) {
|
||||
let l1_phys = (&self.l1 as *const _) as usize - mem::KERNEL_OFFSET;
|
||||
let l2_phys = (&self.l2 as *const _) as usize - mem::KERNEL_OFFSET;
|
||||
|
||||
self.l0[256] = Entry::table(l1_phys, MapAttributes::empty());
|
||||
self.l1[0] = Entry::table(l2_phys, MapAttributes::empty());
|
||||
}
|
||||
}
|
||||
|
||||
const DEVICE_MAP_OFFSET: usize = mem::KERNEL_OFFSET + (256usize << 30);
|
||||
|
||||
#[no_mangle]
|
||||
static mut KERNEL_TTBR1: FixedTableGroup = FixedTableGroup::empty();
|
||||
|
||||
/// Allocates a range of virtual memory of requested size and maps
|
||||
/// it to specified device memory
|
||||
pub fn map_device_memory(phys: usize, count: usize) -> Result<usize, Errno> {
|
||||
unsafe { KERNEL_TTBR1.map_region(phys, count) }
|
||||
}
|
||||
|
||||
/// Sets up initial mappings for device-memory virtual tables.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to be called once during virtual memory init.
|
||||
pub unsafe fn init_device_map() {
|
||||
let l1_phys = (&KERNEL_TTBR1.l1 as *const _) as usize - mem::KERNEL_OFFSET;
|
||||
let l2_phys = (&KERNEL_TTBR1.l2 as *const _) as usize - mem::KERNEL_OFFSET;
|
||||
|
||||
KERNEL_TTBR1.l0[256] = Entry::normal(l1_phys, MapAttributes::empty());
|
||||
KERNEL_TTBR1.l1[0] = Entry::normal(l2_phys, MapAttributes::empty());
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//! AArch64 virtual memory management implementation
|
||||
|
||||
use crate::mem::virt::table::MapAttributes;
|
||||
use cortex_a::{
|
||||
asm::barrier::{self, isb, dsb},
|
||||
registers::TTBR0_EL1
|
||||
};
|
||||
use tock_registers::interfaces::Writeable;
|
||||
|
||||
mod fixed;
|
||||
mod table;
|
||||
|
||||
pub use fixed::{init_device_map, map_device_memory};
|
||||
pub use table::{EntryImpl, SpaceImpl};
|
||||
|
||||
bitflags! {
|
||||
/// Raw attributes for AArch64 [Entry] implementation
|
||||
pub struct RawAttributesImpl: u64 {
|
||||
// TODO use 2 lower bits to determine mapping size?
|
||||
/// nG bit -- determines whether a TLB entry associated with this mapping
|
||||
/// applies only to current ASID or all ASIDs.
|
||||
const NOT_GLOBAL = 1 << 11;
|
||||
/// AF bit -- must be set by software, otherwise Access Error exception is
|
||||
/// generated when the page is accessed
|
||||
const ACCESS = 1 << 10;
|
||||
/// The memory region is outer-shareable
|
||||
const SH_OUTER = 2 << 8;
|
||||
/// This page is used for device-MMIO mapping and uses MAIR attribute #1
|
||||
const DEVICE = 1 << 2;
|
||||
|
||||
/// Pages marked with this bit are Copy-on-Write
|
||||
const EX_COW = 1 << 55;
|
||||
|
||||
/// UXN bit -- if set, page may not be used for instruction fetching from EL0
|
||||
const UXN = 1 << 54;
|
||||
/// PXN bit -- if set, page may not be used for instruction fetching from EL1
|
||||
const PXN = 1 << 53;
|
||||
|
||||
// AP field
|
||||
// Default behavior is: read-write for EL1, no access for EL0
|
||||
/// If set, the page referred to by this entry is read-only for both EL0/EL1
|
||||
const AP_BOTH_READONLY = 3 << 6;
|
||||
/// If set, the page referred to by this entry is read-write for both EL0/EL1
|
||||
const AP_BOTH_READWRITE = 1 << 6;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapAttributes> for RawAttributesImpl {
|
||||
fn from(src: MapAttributes) -> Self {
|
||||
let mut res = RawAttributesImpl::empty();
|
||||
|
||||
if src.contains(MapAttributes::SHARE_OUTER) {
|
||||
res |= RawAttributesImpl::SH_OUTER;
|
||||
}
|
||||
|
||||
if !src.contains(MapAttributes::GLOBAL) {
|
||||
res |= RawAttributesImpl::NOT_GLOBAL;
|
||||
}
|
||||
|
||||
if !src.contains(MapAttributes::USER_EXEC) {
|
||||
res |= RawAttributesImpl::UXN;
|
||||
}
|
||||
|
||||
if !src.contains(MapAttributes::KERNEL_EXEC) {
|
||||
res |= RawAttributesImpl::PXN;
|
||||
}
|
||||
|
||||
if src.contains(MapAttributes::USER_READ) {
|
||||
if src.contains(MapAttributes::USER_WRITE) {
|
||||
res |= RawAttributesImpl::AP_BOTH_READWRITE;
|
||||
} else {
|
||||
res |= RawAttributesImpl::AP_BOTH_READONLY;
|
||||
}
|
||||
}
|
||||
|
||||
if src.contains(MapAttributes::DEVICE_MEMORY) {
|
||||
res |= RawAttributesImpl::DEVICE;
|
||||
}
|
||||
|
||||
if src.contains(MapAttributes::ACCESS) {
|
||||
res |= RawAttributesImpl::ACCESS;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs initialization of virtual memory control by kernel
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to be called once during virtual memory init.
|
||||
pub unsafe fn enable() {
|
||||
fixed::init_device_map();
|
||||
|
||||
dsb(barrier::ISH);
|
||||
isb(barrier::SY);
|
||||
|
||||
// Disable lower-half translation
|
||||
TTBR0_EL1.set(0);
|
||||
//TCR_EL1.modify(TCR_EL1::EPD0::SET);
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
use crate::arch::aarch64::intrin::flush_tlb_virt;
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
virt::table::{Entry, MapAttributes, Space},
|
||||
};
|
||||
use core::ops::{Index, IndexMut};
|
||||
use libsys::{error::Errno, mem::memset};
|
||||
|
||||
use super::RawAttributesImpl;
|
||||
|
||||
/// Transparent wrapper structure representing a single
|
||||
/// translation table entry
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct EntryImpl(u64);
|
||||
|
||||
/// Structure describing a single level of translation mappings
|
||||
#[repr(C, align(0x1000))]
|
||||
pub struct TableImpl {
|
||||
entries: [EntryImpl; 512],
|
||||
}
|
||||
|
||||
/// Top-level translation table wrapper
|
||||
#[repr(transparent)]
|
||||
pub struct SpaceImpl(TableImpl);
|
||||
|
||||
impl EntryImpl {
|
||||
const PRESENT: u64 = 1 << 0;
|
||||
const TABLE: u64 = 1 << 1;
|
||||
const PHYS_MASK: u64 = 0x0000FFFFFFFFF000;
|
||||
}
|
||||
|
||||
impl Entry for EntryImpl {
|
||||
type RawAttributes = RawAttributesImpl;
|
||||
const EMPTY: Self = Self(0);
|
||||
|
||||
#[inline]
|
||||
fn normal(addr: usize, attrs: MapAttributes) -> Self {
|
||||
Self((addr as u64) | RawAttributesImpl::from(attrs).bits() | (1 << 1) | (1 << 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn block(addr: usize, attrs: MapAttributes) -> Self {
|
||||
Self((addr as u64) | RawAttributesImpl::from(attrs).bits() | (1 << 0))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn address(self) -> usize {
|
||||
(self.0 & Self::PHYS_MASK) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_address(&mut self, virt: usize) {
|
||||
self.0 = (self.0 & !Self::PHYS_MASK) | ((virt as u64) & Self::PHYS_MASK);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_present(self) -> bool {
|
||||
self.0 & Self::PRESENT != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_normal(self) -> bool {
|
||||
self.0 & Self::TABLE != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fork_with_cow(&mut self) -> Self {
|
||||
self.0 |= (RawAttributesImpl::AP_BOTH_READONLY | RawAttributesImpl::EX_COW).bits();
|
||||
*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn copy_from_cow(self, new_addr: usize) -> Self {
|
||||
let attrs = self.0
|
||||
& !(Self::PHYS_MASK
|
||||
| RawAttributesImpl::AP_BOTH_READONLY.bits()
|
||||
| RawAttributesImpl::EX_COW.bits());
|
||||
Self(
|
||||
((new_addr as u64) & Self::PHYS_MASK)
|
||||
| (attrs | RawAttributesImpl::AP_BOTH_READWRITE.bits()),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_cow(self) -> bool {
|
||||
self.0 & RawAttributesImpl::EX_COW.bits() != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_user_writable(self) -> bool {
|
||||
self.0 & RawAttributesImpl::AP_BOTH_READONLY.bits()
|
||||
== RawAttributesImpl::AP_BOTH_READWRITE.bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl Space for SpaceImpl {
|
||||
type Entry = EntryImpl;
|
||||
|
||||
fn alloc_empty() -> Result<&'static mut Self, Errno> {
|
||||
let phys = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(phys) as *mut Self) };
|
||||
res.0.entries.fill(EntryImpl::EMPTY);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
unsafe fn release(space: &'static mut Self) {
|
||||
for l0i in 0..512 {
|
||||
let l0_entry = space.0[l0i];
|
||||
if !l0_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(l0_entry.is_normal());
|
||||
let l1_table = &mut *(mem::virtualize(l0_entry.address()) as *mut TableImpl);
|
||||
|
||||
for l1i in 0..512 {
|
||||
let l1_entry = l1_table[l1i];
|
||||
if !l1_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
assert!(l1_entry.is_normal());
|
||||
let l2_table = &mut *(mem::virtualize(l1_entry.address()) as *mut TableImpl);
|
||||
|
||||
for l2i in 0..512 {
|
||||
let entry = l2_table[l2i];
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_normal());
|
||||
phys::free_page(entry.address()).unwrap();
|
||||
}
|
||||
phys::free_page(l1_entry.address()).unwrap();
|
||||
}
|
||||
phys::free_page(l0_entry.address()).unwrap();
|
||||
}
|
||||
memset(space as *mut Self as *mut u8, 0, 4096);
|
||||
}
|
||||
|
||||
fn fork(&mut self) -> Result<&'static mut Self, Errno> {
|
||||
let res = Self::alloc_empty()?;
|
||||
for l0i in 0..512 {
|
||||
if let Some(l1_table) = self.0.next_level_table_mut(l0i) {
|
||||
for l1i in 0..512 {
|
||||
if let Some(l2_table) = l1_table.next_level_table_mut(l1i) {
|
||||
for l2i in 0..512 {
|
||||
let entry = &mut l2_table[l2i];
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_normal());
|
||||
let src_phys = entry.address();
|
||||
let virt_addr = (l0i << 30) | (l1i << 21) | (l2i << 12);
|
||||
let dst_phys = unsafe { phys::fork_page(src_phys)? };
|
||||
|
||||
let new_entry = if dst_phys != src_phys {
|
||||
todo!()
|
||||
} else if entry.is_user_writable() {
|
||||
entry.fork_with_cow()
|
||||
} else {
|
||||
*entry
|
||||
};
|
||||
|
||||
unsafe {
|
||||
flush_tlb_virt(virt_addr);
|
||||
res.write_last_level(virt_addr, new_entry, true, false)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
unsafe fn write_last_level(
|
||||
&mut self,
|
||||
virt: usize,
|
||||
entry: Self::Entry,
|
||||
_create_intermediate: bool, // TODO handle this properly
|
||||
overwrite: bool,
|
||||
) -> Result<(), Errno> {
|
||||
let l0i = virt >> 30;
|
||||
let l1i = (virt >> 21) & 0x1FF;
|
||||
let l2i = (virt >> 12) & 0x1FF;
|
||||
|
||||
let l1_table = self.0.next_level_table_or_alloc(l0i)?;
|
||||
let l2_table = l1_table.next_level_table_or_alloc(l1i)?;
|
||||
|
||||
if l2_table[l2i].is_present() && !overwrite {
|
||||
return Err(Errno::AlreadyExists);
|
||||
};
|
||||
|
||||
l2_table[l2i] = entry;
|
||||
#[cfg(feature = "verbose")]
|
||||
debugln!(
|
||||
"{:#p} Map {:#x} -> {:#x}, {:#x}",
|
||||
self,
|
||||
virt,
|
||||
entry.address(),
|
||||
entry.0 & !EntryImpl::PHYS_MASK
|
||||
);
|
||||
flush_tlb_virt(virt);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_last_level(&self, virt: usize) -> Result<Self::Entry, Errno> {
|
||||
let l0i = virt >> 30;
|
||||
let l1i = (virt >> 21) & 0x1FF;
|
||||
let l2i = (virt >> 12) & 0x1FF;
|
||||
|
||||
let l1_table = self.0.next_level_table(l0i).ok_or(Errno::DoesNotExist)?;
|
||||
let l2_table = l1_table.next_level_table(l1i).ok_or(Errno::DoesNotExist)?;
|
||||
|
||||
let entry = l2_table[l2i];
|
||||
if entry.is_present() {
|
||||
Ok(entry)
|
||||
} else {
|
||||
Err(Errno::DoesNotExist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableImpl {
|
||||
/// Constructs a table with no valid mappings
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
entries: [EntryImpl::EMPTY; 512],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// If `index` represents a `Block`-type mapping, will return an error.
|
||||
/// If `index` does not map to any translation table, will try to allocate, init and
|
||||
/// map a new one, returning it after doing so.
|
||||
pub fn next_level_table_or_alloc(&mut self, index: usize) -> Result<&'static mut Self, Errno> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
Ok(unsafe { &mut *(mem::virtualize(entry.address()) as *mut _) })
|
||||
} else {
|
||||
let phys = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(phys) as *mut Self) };
|
||||
self[index] = EntryImpl::normal(phys, MapAttributes::empty());
|
||||
res.entries.fill(EntryImpl::EMPTY);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// Same as [next_level_table_or_alloc], but returns `None` if no table is mapped.
|
||||
pub fn next_level_table(&self, index: usize) -> Option<&'static Self> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
panic!("Entry is not a table: idx={}", index);
|
||||
}
|
||||
|
||||
Some(unsafe { &*(mem::virtualize(entry.address()) as *const _) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns mutable next-level translation table reference for `index`,
|
||||
/// if one is present. Same as [next_level_table_or_alloc], but returns
|
||||
/// `None` if no table is mapped.
|
||||
pub fn next_level_table_mut(&mut self, index: usize) -> Option<&'static mut Self> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
panic!("Entry is not a table: idx={}", index);
|
||||
}
|
||||
|
||||
Some(unsafe { &mut *(mem::virtualize(entry.address()) as *mut _) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for TableImpl {
|
||||
type Output = EntryImpl;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.entries[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for TableImpl {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.entries[index]
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,13 @@ cfg_if! {
|
||||
pub mod aarch64;
|
||||
|
||||
pub use aarch64 as platform;
|
||||
pub use aarch64::machine;
|
||||
pub use aarch64::{machine, intrin};
|
||||
} else if #[cfg(target_arch = "x86_64")] {
|
||||
pub mod x86_64;
|
||||
|
||||
pub use x86_64 as platform;
|
||||
pub use x86_64 as machine;
|
||||
pub use x86_64::intrin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
.section .text._multiboot
|
||||
.set MAGIC, 0xE85250D6
|
||||
.set ARCH, 0x0
|
||||
.set HDRLEN, 16
|
||||
.set CHKSUM, (-(MAGIC + ARCH + HDRLEN)) & 0xFFFFFFFF
|
||||
|
||||
.long MAGIC
|
||||
.long ARCH
|
||||
.long HDRLEN
|
||||
.long CHKSUM
|
||||
|
||||
.short 5
|
||||
.short 0
|
||||
.long 20
|
||||
.long 800
|
||||
.long 600
|
||||
.long 32
|
||||
|
||||
.short 0
|
||||
.long 8
|
||||
|
||||
.section .text._entry
|
||||
.global _entry
|
||||
_entry:
|
||||
.code32
|
||||
cli
|
||||
lea (multiboot_registers - KERNEL_OFFSET), %edi
|
||||
mov %eax, 0(%edi)
|
||||
mov %ebx, 4(%edi)
|
||||
|
||||
// Setup paging tables
|
||||
lea (_entry_upper - KERNEL_OFFSET), %ebx
|
||||
jmp __x86_64_enter_upper
|
||||
|
||||
.code64
|
||||
_entry_upper:
|
||||
movabsq $1f, %rax
|
||||
jmp *%rax
|
||||
1:
|
||||
lea bsp_stack_top(%rip), %rax
|
||||
mov %rax, %rsp
|
||||
|
||||
mov multiboot_registers(%rip), %edi
|
||||
mov (4 + multiboot_registers)(%rip), %esi
|
||||
call __x86_64_bsp_main
|
||||
|
||||
.section .bss
|
||||
.align 16
|
||||
bsp_stack_bottom:
|
||||
.skip 65536
|
||||
bsp_stack_top:
|
||||
multiboot_registers:
|
||||
.skip 8
|
||||
@@ -0,0 +1,7 @@
|
||||
.set KERNEL_OFFSET, 0xFFFFFF8000000000
|
||||
|
||||
.set PTE_PRESENT, 1 << 0
|
||||
.set PTE_WRITABLE, 1 << 1
|
||||
.set PTE_USERSPACE, 1 << 2
|
||||
.set PTE_BLOCK, 1 << 7
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
//! x86_64 common boot logic
|
||||
use crate::arch::x86_64::{
|
||||
self, gdt, idt, intc,
|
||||
reg::{CR0, CR4},
|
||||
syscall,
|
||||
};
|
||||
use crate::config::{ConfigKey, CONFIG};
|
||||
use crate::debug;
|
||||
use crate::dev::{display::FramebufferInfo, irq::IntSource, pseudo, Device};
|
||||
use crate::font;
|
||||
use crate::fs::{
|
||||
devfs::{self, CharDeviceType},
|
||||
sysfs,
|
||||
};
|
||||
use crate::mem::{
|
||||
self, heap,
|
||||
phys::{self, MemoryRegion, PageUsage, ReservedRegion},
|
||||
virt,
|
||||
};
|
||||
use crate::proc;
|
||||
use core::arch::global_asm;
|
||||
use core::mem::MaybeUninit;
|
||||
use tock_registers::interfaces::ReadWriteable;
|
||||
|
||||
static mut RESERVED_REGION_MB2: MaybeUninit<ReservedRegion> = MaybeUninit::uninit();
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn __x86_64_bsp_main(_mb_checksum: u32, mb_info_ptr: u32) -> ! {
|
||||
CR4.modify(CR4::OSXMMEXCPT::SET + CR4::OSFXSR::SET);
|
||||
CR0.modify(CR0::EM::CLEAR + CR0::MP::SET);
|
||||
|
||||
unsafe {
|
||||
// Setup a proper GDT
|
||||
gdt::init();
|
||||
idt::init(intc::map_isr_entries);
|
||||
}
|
||||
|
||||
virt::enable().expect("Failed to initialize virtual memory");
|
||||
|
||||
let mb_info = unsafe {
|
||||
multiboot2::load_with_offset(mb_info_ptr as usize, mem::KERNEL_OFFSET)
|
||||
.expect("Failed to load multiboot info structure")
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let mb_info_page = (mb_info_ptr & !0xFFF) as usize;
|
||||
RESERVED_REGION_MB2.write(ReservedRegion::new(
|
||||
mb_info_page,
|
||||
mb_info_page + ((mb_info.total_size() + 0xFFF) & !0xFFF),
|
||||
));
|
||||
phys::reserve("multiboot2", RESERVED_REGION_MB2.as_mut_ptr());
|
||||
|
||||
phys::init_from_iter(
|
||||
mb_info
|
||||
.memory_map_tag()
|
||||
.unwrap()
|
||||
.memory_areas()
|
||||
.map(|entry| MemoryRegion {
|
||||
start: ((entry.start_address() + 0xFFF) & !0xFFF) as usize,
|
||||
end: (entry.end_address() & !0xFFF) as usize,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Setup a heap
|
||||
unsafe {
|
||||
let heap_base_phys = phys::alloc_contiguous_pages(PageUsage::KernelHeap, 4096)
|
||||
.expect("Failed to allocate memory for heap");
|
||||
let heap_base_virt = mem::virtualize(heap_base_phys);
|
||||
heap::init(heap_base_virt, 16 * 1024 * 1024);
|
||||
}
|
||||
|
||||
let initrd_info = mb_info.module_tags().next().unwrap();
|
||||
{
|
||||
let mut cfg = CONFIG.lock();
|
||||
|
||||
cfg.set_usize(ConfigKey::InitrdBase, initrd_info.start_address() as usize);
|
||||
cfg.set_usize(ConfigKey::InitrdSize, initrd_info.module_size() as usize);
|
||||
}
|
||||
|
||||
// Setup hardware
|
||||
unsafe {
|
||||
x86_64::INTC.enable().ok();
|
||||
}
|
||||
|
||||
let fb_info = mb_info.framebuffer_tag().unwrap();
|
||||
let virt = mem::virtualize(fb_info.address as usize);
|
||||
debugln!(
|
||||
"Framebuffer base: phys={:#x}, virt={:#x}",
|
||||
fb_info.address,
|
||||
virt
|
||||
);
|
||||
x86_64::DISPLAY.set_framebuffer(FramebufferInfo {
|
||||
width: fb_info.width as usize,
|
||||
height: fb_info.height as usize,
|
||||
phys_base: fb_info.address as usize,
|
||||
virt_base: virt,
|
||||
});
|
||||
unsafe {
|
||||
x86_64::COM1.init_irqs().unwrap();
|
||||
x86_64::local_timer().init_irqs().unwrap();
|
||||
x86_64::local_timer().enable().unwrap();
|
||||
}
|
||||
font::init();
|
||||
debug::set_display(&x86_64::DISPLAY);
|
||||
syscall::init();
|
||||
|
||||
devfs::init();
|
||||
sysfs::init();
|
||||
|
||||
devfs::add_char_device(&x86_64::COM1, CharDeviceType::TtySerial).unwrap();
|
||||
devfs::add_named_char_device(&pseudo::ZERO, "zero").unwrap();
|
||||
devfs::add_named_char_device(&pseudo::RANDOM, "random").unwrap();
|
||||
|
||||
unsafe {
|
||||
proc::enter();
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(include_str!("macros.S"), options(att_syntax));
|
||||
global_asm!(include_str!("entry.S"), options(att_syntax));
|
||||
global_asm!(include_str!("upper.S"), options(att_syntax));
|
||||
@@ -0,0 +1,94 @@
|
||||
.code32
|
||||
|
||||
.section .text._entry
|
||||
__x86_64_enter_upper:
|
||||
mov $(PTE_PRESENT | PTE_WRITABLE | PTE_USERSPACE), %edx
|
||||
// Setup PML4
|
||||
lea (KERNEL_FIXED - KERNEL_OFFSET), %edi
|
||||
lea (KERNEL_FIXED + 4096 - KERNEL_OFFSET), %esi
|
||||
mov %edx, %eax
|
||||
or %esi, %eax
|
||||
// pml4[0] = %eax
|
||||
mov %eax, (%edi)
|
||||
// pml4[511] = %eax
|
||||
mov %eax, 4088(%edi)
|
||||
|
||||
// Setup PDPT
|
||||
mov %esi, %edi
|
||||
lea (KERNEL_FIXED + 8192 - KERNEL_OFFSET), %esi
|
||||
xor %ecx, %ecx
|
||||
1:
|
||||
// %eax = &table[%ecx] | attrs
|
||||
mov %esi, %eax
|
||||
or %edx, %eax
|
||||
mov %eax, (%edi, %ecx, 8)
|
||||
|
||||
add $4096, %esi
|
||||
inc %ecx
|
||||
cmp $16, %ecx
|
||||
jne 1b
|
||||
|
||||
// Setup PDs
|
||||
lea (KERNEL_FIXED + 8192 - KERNEL_OFFSET), %edi
|
||||
mov $(PTE_PRESENT | PTE_BLOCK | PTE_WRITABLE), %edx
|
||||
mov $(512 * 16), %ecx
|
||||
1:
|
||||
dec %ecx
|
||||
|
||||
// %eax = attrs | (i << 21)
|
||||
mov %ecx, %eax
|
||||
shl $21, %eax
|
||||
or %edx, %eax
|
||||
|
||||
mov %eax, (%edi, %ecx, 8)
|
||||
|
||||
test %ecx, %ecx
|
||||
jnz 1b
|
||||
|
||||
// Enable PAE/PSE
|
||||
mov %cr4, %eax
|
||||
or $((1 << 5) | (1 << 4)), %eax
|
||||
mov %eax, %cr4
|
||||
|
||||
// Enable EFER.LME
|
||||
mov $0xC0000080, %ecx
|
||||
rdmsr
|
||||
or $(1 << 8), %eax
|
||||
wrmsr
|
||||
|
||||
// Set CR3
|
||||
lea (KERNEL_FIXED - KERNEL_OFFSET), %edi
|
||||
mov %edi, %cr3
|
||||
|
||||
// Enable paging
|
||||
mov %cr0, %eax
|
||||
or $(1 << 31), %eax
|
||||
mov %eax, %cr0
|
||||
|
||||
lgdt (gdtr64 - KERNEL_OFFSET)
|
||||
ljmp $0x08, $(1f - KERNEL_OFFSET)
|
||||
1:
|
||||
mov $0x10, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
|
||||
.code64
|
||||
movabsq $KERNEL_OFFSET, %rax
|
||||
add %rax, %rbx
|
||||
jmp *%rbx
|
||||
|
||||
.section .rodata
|
||||
.code32
|
||||
.align 16
|
||||
gdt64:
|
||||
.quad 0
|
||||
.quad 0x00209A0000000000
|
||||
.quad 0x0000920000000000
|
||||
gdt_end64:
|
||||
.align 16
|
||||
gdtr64:
|
||||
.short gdt_end64 - gdt64 - 1
|
||||
.long gdt64 - KERNEL_OFFSET
|
||||
@@ -0,0 +1,115 @@
|
||||
.section .text
|
||||
.global __x86_64_ctx_switch
|
||||
.global __x86_64_ctx_switch_to
|
||||
.global __x86_64_ctx_enter_kernel
|
||||
.global __x86_64_ctx_enter_from_fork
|
||||
|
||||
__x86_64_ctx_enter_user:
|
||||
pop %rcx
|
||||
pop %rdi
|
||||
pop %rdi
|
||||
pop %rdx
|
||||
|
||||
push %rdx
|
||||
|
||||
push $0x1B
|
||||
push %rcx
|
||||
|
||||
push $0x200
|
||||
|
||||
push $0x23
|
||||
push %rdx
|
||||
|
||||
iretq
|
||||
|
||||
__x86_64_ctx_enter_kernel:
|
||||
pop %rdi
|
||||
pop %rdx
|
||||
|
||||
// 8-byte fixup for proper stack alignment
|
||||
push %rdx
|
||||
|
||||
mov %rsp, %rcx
|
||||
|
||||
push $0x10
|
||||
push %rcx
|
||||
|
||||
push $0x200
|
||||
|
||||
push $0x08
|
||||
push %rdx
|
||||
|
||||
iretq
|
||||
|
||||
__x86_64_ctx_enter_from_fork:
|
||||
pop %rdi
|
||||
pop %rsi
|
||||
pop %rdx
|
||||
pop %r10
|
||||
pop %r8
|
||||
pop %r9
|
||||
|
||||
pop %rax
|
||||
xor %rax, %rax
|
||||
|
||||
pop %r11 // rsp3
|
||||
pop %rcx // rip3
|
||||
|
||||
push $0x1B
|
||||
push %r11
|
||||
|
||||
push $0x200
|
||||
|
||||
push $0x23
|
||||
push %rcx
|
||||
|
||||
iretq
|
||||
|
||||
__x86_64_ctx_switch:
|
||||
// %rsi -- src ctx ptr
|
||||
// %rdi -- dst ctx ptr
|
||||
|
||||
// push %tss_rsp0
|
||||
// TODO save gs_base
|
||||
mov (4 + TSS)(%rip), %rax
|
||||
push %rax
|
||||
|
||||
// push %cr3
|
||||
mov %cr3, %rax
|
||||
push %rax
|
||||
|
||||
push %r15
|
||||
push %r14
|
||||
push %r13
|
||||
push %r12
|
||||
push %rbx
|
||||
push %rbp
|
||||
|
||||
// TODO SAVE FP CONTEXT
|
||||
|
||||
mov %rsp, (%rsi)
|
||||
__x86_64_ctx_switch_to:
|
||||
mov (%rdi), %rsp
|
||||
|
||||
// TODO RESTORE FP CONTEXT
|
||||
|
||||
pop %rbp
|
||||
pop %rbx
|
||||
pop %r12
|
||||
pop %r13
|
||||
pop %r14
|
||||
pop %r15
|
||||
|
||||
// pop %cr3
|
||||
pop %rax
|
||||
test %rax, %rax
|
||||
jz 1f
|
||||
mov %rax, %cr3
|
||||
1:
|
||||
// TODO set gs_base = rax
|
||||
|
||||
// pop %tss_rsp0
|
||||
pop %rax
|
||||
mov %rax, (4 + TSS)(%rip)
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,206 @@
|
||||
//! Thread context
|
||||
use crate::arch::platform::ForkFrame;
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
};
|
||||
use core::arch::global_asm;
|
||||
use core::mem::size_of;
|
||||
|
||||
struct Stack {
|
||||
bp: usize,
|
||||
sp: usize,
|
||||
}
|
||||
|
||||
/// Structure representing thread context
|
||||
#[repr(C)]
|
||||
pub struct Context {
|
||||
/// Thread's kernel stack pointer
|
||||
pub k_sp: usize, // 0x00
|
||||
|
||||
stack_base: usize,
|
||||
stack_page_count: usize,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Constructs a new kernel-space thread context
|
||||
pub fn kernel(entry: usize, arg: usize) -> Self {
|
||||
let mut stack = Stack::new(8);
|
||||
|
||||
stack.push(entry);
|
||||
stack.push(arg);
|
||||
|
||||
stack.setup_common(__x86_64_ctx_enter_kernel as usize, 0, 0);
|
||||
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
|
||||
stack_base: stack.bp,
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new user-space thread context
|
||||
pub fn user(entry: usize, arg: usize, cr3: usize, ustack: usize) -> Self {
|
||||
let cr3 = cr3 & 0xFFFFFFFF;
|
||||
let mut stack = Stack::new(8);
|
||||
let stack_top = stack.sp;
|
||||
|
||||
stack.push(entry);
|
||||
stack.push(arg);
|
||||
stack.push(0);
|
||||
stack.push(ustack);
|
||||
|
||||
stack.setup_common(__x86_64_ctx_enter_user as usize, cr3, stack_top);
|
||||
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
|
||||
stack_base: stack.bp,
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an uninitialized thread context
|
||||
pub fn empty() -> Self {
|
||||
let stack = Stack::new(8);
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
stack_base: stack.bp,
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up a context for signal entry
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: may clobber an already active context
|
||||
pub unsafe fn setup_signal_entry(
|
||||
&mut self,
|
||||
entry: usize,
|
||||
arg: usize,
|
||||
cr3: usize,
|
||||
ustack: usize,
|
||||
) {
|
||||
let cr3 = cr3 & 0xFFFFFFFF;
|
||||
let mut stack = Stack::from_base_size(self.stack_base, self.stack_page_count);
|
||||
let stack_top = stack.sp;
|
||||
|
||||
stack.push(entry);
|
||||
stack.push(arg);
|
||||
stack.push(0);
|
||||
stack.push(ustack);
|
||||
|
||||
stack.setup_common(__x86_64_ctx_enter_user as usize, cr3, stack_top);
|
||||
|
||||
self.k_sp = stack.sp;
|
||||
}
|
||||
|
||||
/// Clones a process context from given `frame`
|
||||
pub fn fork(frame: &ForkFrame, cr3: usize) -> Self {
|
||||
let cr3 = cr3 & 0xFFFFFFFF;
|
||||
let mut stack = Stack::new(8);
|
||||
let stack_top = stack.sp;
|
||||
|
||||
stack.push(frame.saved_rip);
|
||||
stack.push(frame.saved_rsp);
|
||||
|
||||
stack.push(frame.x[6]); // rax
|
||||
stack.push(frame.x[5]); // r9
|
||||
stack.push(frame.x[4]); // r8
|
||||
stack.push(frame.x[3]); // r10
|
||||
stack.push(frame.x[2]); // rdx
|
||||
stack.push(frame.x[1]); // rsi
|
||||
stack.push(frame.x[0]); // rdi
|
||||
|
||||
// Setup common
|
||||
stack.push(__x86_64_ctx_enter_from_fork as usize); // return address
|
||||
stack.push(stack_top); // gs_base
|
||||
stack.push(cr3);
|
||||
stack.push(frame.x[9]); // r15
|
||||
stack.push(frame.x[10]); // r14
|
||||
stack.push(frame.x[11]); // r13
|
||||
stack.push(frame.x[12]); // r12
|
||||
stack.push(frame.x[7]); // rbx
|
||||
stack.push(frame.x[8]); // rbp
|
||||
|
||||
Self {
|
||||
k_sp: stack.sp,
|
||||
|
||||
stack_base: stack.bp,
|
||||
stack_page_count: 8,
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs initial thread entry
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: does not check if any context has already been activated
|
||||
/// before, so must only be called once.
|
||||
pub unsafe extern "C" fn enter(&mut self) -> ! {
|
||||
__x86_64_ctx_switch_to(self);
|
||||
panic!("This code should not run");
|
||||
}
|
||||
|
||||
/// Performs context switch from `self` to `to`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: does not check if `self` is actually an active context.
|
||||
pub unsafe extern "C" fn switch(&mut self, to: &mut Context) {
|
||||
__x86_64_ctx_switch(to, self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
pub fn new(page_count: usize) -> Stack {
|
||||
let phys = phys::alloc_contiguous_pages(PageUsage::Kernel, page_count).unwrap();
|
||||
let bp = mem::virtualize(phys);
|
||||
Stack {
|
||||
bp,
|
||||
sp: bp + page_count * mem::PAGE_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn from_base_size(bp: usize, page_count: usize) -> Stack {
|
||||
Stack {
|
||||
bp,
|
||||
sp: bp + page_count * mem::PAGE_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_common(&mut self, entry: usize, cr3: usize, tss_rsp0: usize) {
|
||||
self.push(entry); // return address
|
||||
self.push(tss_rsp0); // gs_base
|
||||
self.push(cr3);
|
||||
self.push(0); // r15
|
||||
self.push(0); // r14
|
||||
self.push(0); // r13
|
||||
self.push(0); // r12
|
||||
self.push(0); // rbx
|
||||
self.push(0); // rbp
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: usize) {
|
||||
if self.bp == self.sp {
|
||||
panic!("Stack overflow");
|
||||
}
|
||||
|
||||
self.sp -= size_of::<usize>();
|
||||
unsafe {
|
||||
*(self.sp as *mut usize) = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn __x86_64_ctx_enter_from_fork();
|
||||
fn __x86_64_ctx_enter_kernel();
|
||||
fn __x86_64_ctx_enter_user();
|
||||
fn __x86_64_ctx_switch(dst: *mut Context, src: *mut Context);
|
||||
fn __x86_64_ctx_switch_to(dst: *mut Context);
|
||||
}
|
||||
|
||||
global_asm!(include_str!("context.S"), options(att_syntax));
|
||||
@@ -0,0 +1,117 @@
|
||||
use crate::arch::{intrin, x86_64};
|
||||
use crate::debug::Level;
|
||||
use crate::dev::irq::{IntController, IrqContext};
|
||||
use crate::mem::{self, virt::table::Space};
|
||||
use crate::proc::{sched, Thread};
|
||||
use core::arch::asm;
|
||||
use libsys::{error::Errno, signal::Signal};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
struct ExceptionFrame {
|
||||
r15: u64,
|
||||
r14: u64,
|
||||
r13: u64,
|
||||
r12: u64,
|
||||
r11: u64,
|
||||
r10: u64,
|
||||
r9: u64,
|
||||
r8: u64,
|
||||
rdi: u64,
|
||||
rsi: u64,
|
||||
rbp: u64,
|
||||
rbx: u64,
|
||||
rdx: u64,
|
||||
rcx: u64,
|
||||
rax: u64,
|
||||
|
||||
err_no: u64,
|
||||
err_code: u64,
|
||||
rip: u64,
|
||||
cs: u64,
|
||||
rflags: u64,
|
||||
rsp: u64,
|
||||
ss: u64,
|
||||
}
|
||||
|
||||
fn pfault_read_cr2() -> u64 {
|
||||
let mut res;
|
||||
unsafe {
|
||||
asm!("mov %cr2, {}", out(reg) res, options(att_syntax));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn pfault_access_type(code: u64) -> &'static str {
|
||||
if code & (1 << 4) != 0 {
|
||||
"INSTRUCTION FETCH"
|
||||
} else if code & (1 << 1) != 0 {
|
||||
"WRITE"
|
||||
} else {
|
||||
"READ"
|
||||
}
|
||||
}
|
||||
|
||||
fn pfault_dump(level: Level, frame: &ExceptionFrame, cr2: usize) {
|
||||
println!(level, "\x1B[41;1mPage fault:");
|
||||
println!(
|
||||
level,
|
||||
" Illegal {} at {:#018x}\x1B[0m",
|
||||
pfault_access_type(frame.err_code),
|
||||
cr2
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn __x86_64_exception_handler(frame: &mut ExceptionFrame) {
|
||||
if frame.err_no == 14 {
|
||||
let cr2 = pfault_read_cr2() as usize;
|
||||
let is_write = frame.err_code & (1 << 1) != 0;
|
||||
|
||||
if is_write && cr2 < mem::KERNEL_OFFSET && sched::is_ready() {
|
||||
let thread = Thread::current();
|
||||
let proc = thread.owner().unwrap();
|
||||
|
||||
let res = proc.manipulate_space(|space| {
|
||||
space.try_cow_copy(cr2 & !0xFFF)?;
|
||||
unsafe {
|
||||
intrin::flush_tlb_virt(cr2 & !0xFFF);
|
||||
}
|
||||
Result::<(), Errno>::Ok(())
|
||||
});
|
||||
|
||||
if res.is_err() {
|
||||
errorln!(
|
||||
"Page fault at {:#x} in user {:?}",
|
||||
frame.rip,
|
||||
thread.owner_id()
|
||||
);
|
||||
pfault_dump(Level::Error, frame, cr2);
|
||||
proc.enter_fault_signal(thread, Signal::SegmentationFault);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
errorln!("Unresolved page fault:");
|
||||
pfault_dump(Level::Error, frame, cr2);
|
||||
}
|
||||
|
||||
errorln!(
|
||||
"Exception occurred: err_no={}, err_code={:#x}",
|
||||
frame.err_no,
|
||||
frame.err_code,
|
||||
);
|
||||
errorln!("cs:rip = {:02x}:{:#x}", frame.cs, frame.rip);
|
||||
errorln!("ss:rsp = {:02x}:{:#x}", frame.ss, frame.rsp);
|
||||
|
||||
panic!("Unhandled exception");
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn __x86_64_irq_handler(frame: &mut ExceptionFrame) {
|
||||
unsafe {
|
||||
let ic = IrqContext::new(frame.err_no as usize);
|
||||
x86_64::intc().handle_pending_irqs(&ic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use core::arch::asm;
|
||||
use core::mem::size_of_val;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
struct Entry {
|
||||
limit_lo: u16,
|
||||
base_lo: u16,
|
||||
base_mi: u8,
|
||||
access: u8,
|
||||
flags: u8,
|
||||
base_hi: u8,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
struct Tss {
|
||||
__res0: u32,
|
||||
rsp0: u64,
|
||||
rsp1: u64,
|
||||
rsp2: u64,
|
||||
__res1: u32,
|
||||
ist1: u64,
|
||||
ist2: u64,
|
||||
ist3: u64,
|
||||
ist4: u64,
|
||||
ist5: u64,
|
||||
ist6: u64,
|
||||
ist7: u64,
|
||||
__res2: u64,
|
||||
__res3: u16,
|
||||
iopb_base: u16,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
struct Pointer {
|
||||
size: u16,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
const FLAG_LONG: u8 = 1 << 5;
|
||||
const ACC_PRESENT: u8 = 1 << 7;
|
||||
const ACC_SYSTEM: u8 = 1 << 4;
|
||||
const ACC_EXECUTE: u8 = 1 << 3;
|
||||
const ACC_WRITE: u8 = 1 << 1;
|
||||
const ACC_RING3: u8 = 3 << 5;
|
||||
const ACC_ACCESS: u8 = 1 << 0;
|
||||
|
||||
const fn new(base: u32, limit: u32, flags: u8, access: u8) -> Self {
|
||||
Self {
|
||||
base_lo: (base & 0xFFFF) as u16,
|
||||
base_mi: ((base >> 16) & 0xFF) as u8,
|
||||
base_hi: ((base >> 24) & 0xFF) as u8,
|
||||
access,
|
||||
flags: (flags & 0xF0) | (((limit >> 16) & 0xF) as u8),
|
||||
limit_lo: (limit & 0xFFFF) as u16,
|
||||
}
|
||||
}
|
||||
|
||||
const fn null() -> Self {
|
||||
Self {
|
||||
base_lo: 0,
|
||||
base_mi: 0,
|
||||
base_hi: 0,
|
||||
access: 0,
|
||||
flags: 0,
|
||||
limit_lo: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tss {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
__res0: 0,
|
||||
rsp0: 0,
|
||||
rsp1: 0,
|
||||
rsp2: 0,
|
||||
__res1: 0,
|
||||
ist1: 0,
|
||||
ist2: 0,
|
||||
ist3: 0,
|
||||
ist4: 0,
|
||||
ist5: 0,
|
||||
ist6: 0,
|
||||
ist7: 0,
|
||||
__res2: 0,
|
||||
__res3: 0,
|
||||
iopb_base: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SIZE: usize = 7;
|
||||
#[no_mangle]
|
||||
static mut TSS: Tss = Tss::new();
|
||||
static mut GDT: [Entry; SIZE] = [
|
||||
Entry::null(),
|
||||
Entry::new(
|
||||
0,
|
||||
0,
|
||||
Entry::FLAG_LONG,
|
||||
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_EXECUTE,
|
||||
),
|
||||
Entry::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_WRITE,
|
||||
),
|
||||
Entry::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_RING3 | Entry::ACC_WRITE,
|
||||
),
|
||||
Entry::new(
|
||||
0,
|
||||
0,
|
||||
Entry::FLAG_LONG,
|
||||
Entry::ACC_PRESENT | Entry::ACC_SYSTEM | Entry::ACC_RING3 | Entry::ACC_EXECUTE,
|
||||
),
|
||||
Entry::null(),
|
||||
Entry::null(),
|
||||
];
|
||||
|
||||
#[inline]
|
||||
unsafe fn load_gdt(ptr: &Pointer, tr: u16) {
|
||||
asm!(r#"
|
||||
lgdt ({})
|
||||
ltr %ax
|
||||
"#, in(reg) ptr, in("ax") tr, options(att_syntax));
|
||||
}
|
||||
|
||||
pub unsafe fn init() {
|
||||
let tss_addr = &TSS as *const _ as usize;
|
||||
|
||||
GDT[5] = Entry::new(
|
||||
(tss_addr & 0xFFFFFFFF) as u32,
|
||||
size_of_val(&TSS) as u32 - 1,
|
||||
Entry::FLAG_LONG,
|
||||
Entry::ACC_ACCESS | Entry::ACC_PRESENT | Entry::ACC_EXECUTE,
|
||||
);
|
||||
core::ptr::write(&mut GDT[6] as *mut _ as *mut u64, (tss_addr >> 32) as u64);
|
||||
|
||||
let gdtr = Pointer {
|
||||
size: size_of_val(&GDT) as u16 - 1,
|
||||
offset: &GDT as *const _ as usize,
|
||||
};
|
||||
load_gdt(&gdtr, 0x28);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
.macro isr_nerr, n
|
||||
exc_isr_\n:
|
||||
cli
|
||||
pushq $0
|
||||
pushq $\n
|
||||
jmp __x86_64_isr_common
|
||||
.endm
|
||||
|
||||
// ISR for exception with an error code
|
||||
.macro isr_yerr, n
|
||||
exc_isr_\n:
|
||||
cli
|
||||
pushq $\n
|
||||
jmp __x86_64_isr_common
|
||||
.endm
|
||||
|
||||
.section .text
|
||||
__x86_64_isr_common:
|
||||
push %rax
|
||||
push %rcx
|
||||
push %rdx
|
||||
push %rbx
|
||||
push %rbp
|
||||
push %rsi
|
||||
push %rdi
|
||||
push %r8
|
||||
push %r9
|
||||
push %r10
|
||||
push %r11
|
||||
push %r12
|
||||
push %r13
|
||||
push %r14
|
||||
push %r15
|
||||
|
||||
mov %rsp, %rdi
|
||||
call __x86_64_exception_handler
|
||||
|
||||
pop %r15
|
||||
pop %r14
|
||||
pop %r13
|
||||
pop %r12
|
||||
pop %r11
|
||||
pop %r10
|
||||
pop %r9
|
||||
pop %r8
|
||||
pop %rdi
|
||||
pop %rsi
|
||||
pop %rbp
|
||||
pop %rbx
|
||||
pop %rdx
|
||||
pop %rcx
|
||||
pop %rax
|
||||
|
||||
add $16, %rsp
|
||||
iretq
|
||||
|
||||
isr_nerr 0
|
||||
isr_nerr 1
|
||||
isr_nerr 2
|
||||
isr_nerr 3
|
||||
isr_nerr 4
|
||||
isr_nerr 5
|
||||
isr_nerr 6
|
||||
isr_nerr 7
|
||||
isr_yerr 8
|
||||
isr_nerr 9
|
||||
isr_yerr 10
|
||||
isr_yerr 11
|
||||
isr_yerr 12
|
||||
isr_yerr 13
|
||||
isr_yerr 14
|
||||
isr_nerr 15
|
||||
isr_nerr 16
|
||||
isr_yerr 17
|
||||
isr_nerr 18
|
||||
isr_nerr 19
|
||||
isr_nerr 20
|
||||
isr_nerr 21
|
||||
isr_nerr 22
|
||||
isr_nerr 23
|
||||
isr_nerr 24
|
||||
isr_nerr 25
|
||||
isr_nerr 26
|
||||
isr_nerr 27
|
||||
isr_nerr 28
|
||||
isr_nerr 29
|
||||
isr_yerr 30
|
||||
isr_nerr 31
|
||||
|
||||
.section .rodata
|
||||
.global __x86_64_exception_vectors
|
||||
__x86_64_exception_vectors:
|
||||
.quad exc_isr_0
|
||||
.quad exc_isr_1
|
||||
.quad exc_isr_2
|
||||
.quad exc_isr_3
|
||||
.quad exc_isr_4
|
||||
.quad exc_isr_5
|
||||
.quad exc_isr_6
|
||||
.quad exc_isr_7
|
||||
.quad exc_isr_8
|
||||
.quad exc_isr_9
|
||||
.quad exc_isr_10
|
||||
.quad exc_isr_11
|
||||
.quad exc_isr_12
|
||||
.quad exc_isr_13
|
||||
.quad exc_isr_14
|
||||
.quad exc_isr_15
|
||||
.quad exc_isr_16
|
||||
.quad exc_isr_17
|
||||
.quad exc_isr_18
|
||||
.quad exc_isr_19
|
||||
.quad exc_isr_20
|
||||
.quad exc_isr_21
|
||||
.quad exc_isr_22
|
||||
.quad exc_isr_23
|
||||
.quad exc_isr_24
|
||||
.quad exc_isr_25
|
||||
.quad exc_isr_26
|
||||
.quad exc_isr_27
|
||||
.quad exc_isr_28
|
||||
.quad exc_isr_29
|
||||
.quad exc_isr_30
|
||||
.quad exc_isr_31
|
||||
@@ -0,0 +1,80 @@
|
||||
use core::arch::{asm, global_asm};
|
||||
use core::mem::size_of_val;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(packed)]
|
||||
pub struct Entry {
|
||||
base_lo: u16,
|
||||
selector: u16,
|
||||
__res0: u8,
|
||||
flags: u8,
|
||||
base_hi: u16,
|
||||
base_ex: u32,
|
||||
__res1: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(packed)]
|
||||
struct Pointer {
|
||||
limit: u16,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub const SIZE: usize = 256;
|
||||
|
||||
impl Entry {
|
||||
pub const PRESENT: u8 = 1 << 7;
|
||||
pub const INT32: u8 = 0xE;
|
||||
|
||||
pub const fn new(base: usize, selector: u16, flags: u8) -> Self {
|
||||
Self {
|
||||
base_lo: (base & 0xFFFF) as u16,
|
||||
base_hi: ((base >> 16) & 0xFFFF) as u16,
|
||||
base_ex: (base >> 32) as u32,
|
||||
selector,
|
||||
flags,
|
||||
__res0: 0,
|
||||
__res1: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const fn empty() -> Self {
|
||||
Self {
|
||||
base_lo: 0,
|
||||
base_hi: 0,
|
||||
base_ex: 0,
|
||||
selector: 0,
|
||||
flags: 0,
|
||||
__res0: 0,
|
||||
__res1: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut IDT: [Entry; SIZE] = [Entry::empty(); SIZE];
|
||||
|
||||
#[inline]
|
||||
unsafe fn load_idt(ptr: &Pointer) {
|
||||
asm!("lidt ({})", in(reg) ptr, options(att_syntax));
|
||||
}
|
||||
|
||||
pub unsafe fn init<F: FnOnce(&mut [Entry; SIZE])>(f: F) {
|
||||
extern "C" {
|
||||
static __x86_64_exception_vectors: [usize; 32];
|
||||
}
|
||||
|
||||
for (i, &entry) in __x86_64_exception_vectors.iter().enumerate() {
|
||||
IDT[i] = Entry::new(entry, 0x08, Entry::PRESENT | Entry::INT32);
|
||||
}
|
||||
|
||||
f(&mut IDT);
|
||||
|
||||
let idtr = Pointer {
|
||||
limit: size_of_val(&IDT) as u16 - 1,
|
||||
offset: &IDT as *const _ as usize,
|
||||
};
|
||||
load_idt(&idtr);
|
||||
}
|
||||
|
||||
global_asm!(include_str!("idt.S"), options(att_syntax));
|
||||
@@ -0,0 +1,144 @@
|
||||
use crate::arch::x86_64::{
|
||||
idt::{Entry as IdtEntry, SIZE as IDT_SIZE},
|
||||
PortIo,
|
||||
};
|
||||
use crate::dev::{
|
||||
irq::{IntController, IntSource, IrqContext},
|
||||
Device,
|
||||
};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use core::arch::global_asm;
|
||||
use libsys::error::Errno;
|
||||
|
||||
const ICW1_INIT: u8 = 0x10;
|
||||
const ICW1_ICW4: u8 = 0x01;
|
||||
|
||||
const ICW4_8086: u8 = 0x01;
|
||||
|
||||
pub(super) struct I8259 {
|
||||
cmd_a: PortIo<u8>,
|
||||
cmd_b: PortIo<u8>,
|
||||
data_a: PortIo<u8>,
|
||||
data_b: PortIo<u8>,
|
||||
|
||||
table: IrqSafeSpinLock<[Option<&'static (dyn IntSource + Sync)>; 15]>,
|
||||
}
|
||||
|
||||
/// Interrupt line number wrapper struct
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct IrqNumber(u32);
|
||||
|
||||
impl IrqNumber {
|
||||
/// IRQ line number limit
|
||||
pub const MAX: u32 = 16;
|
||||
|
||||
/// Constructs a wrapped IRQ line number
|
||||
pub const fn new(u: u32) -> Self {
|
||||
if u > Self::MAX {
|
||||
panic!();
|
||||
}
|
||||
Self(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for I8259 {
|
||||
fn name(&self) -> &'static str {
|
||||
"i8259-compatible IRQ controller"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
self.cmd_a.write(ICW1_INIT | ICW1_ICW4);
|
||||
self.cmd_b.write(ICW1_INIT | ICW1_ICW4);
|
||||
self.data_a.write(32);
|
||||
self.data_b.write(32 + 8);
|
||||
self.data_a.write(4);
|
||||
self.data_b.write(2);
|
||||
|
||||
self.data_a.write(ICW4_8086);
|
||||
self.data_b.write(ICW4_8086);
|
||||
|
||||
self.data_a.write(0xFE);
|
||||
self.data_b.write(0xFF);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntController for I8259 {
|
||||
type IrqNumber = IrqNumber;
|
||||
|
||||
fn register_handler(
|
||||
&self,
|
||||
irq: Self::IrqNumber,
|
||||
handler: &'static (dyn IntSource + Sync),
|
||||
) -> Result<(), Errno> {
|
||||
let index = irq.0 as usize;
|
||||
let mut lock = self.table.lock();
|
||||
if lock[index].is_some() {
|
||||
return Err(Errno::AlreadyExists);
|
||||
}
|
||||
|
||||
lock[index] = Some(handler);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enable_irq(&self, irq: Self::IrqNumber) -> Result<(), Errno> {
|
||||
let port = if irq.0 < 8 {
|
||||
&self.data_a
|
||||
} else {
|
||||
&self.data_b
|
||||
};
|
||||
|
||||
let mask = port.read() & !(1 << (irq.0 & 0x7));
|
||||
port.write(mask);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_pending_irqs<'irq_context>(&'irq_context self, ic: &IrqContext<'irq_context>) {
|
||||
let irq_number = ic.token();
|
||||
|
||||
// Clear irq
|
||||
if irq_number > 8 {
|
||||
self.cmd_b.write(0x20);
|
||||
}
|
||||
self.cmd_a.write(0x20);
|
||||
|
||||
{
|
||||
let table = self.table.lock();
|
||||
match table[irq_number] {
|
||||
None => panic!("No handler registered for irq{}", irq_number),
|
||||
Some(handler) => {
|
||||
drop(table);
|
||||
handler.handle_irq().expect("irq handler failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl I8259 {
|
||||
pub const fn new() -> Self {
|
||||
unsafe {
|
||||
Self {
|
||||
cmd_a: PortIo::new(0x20),
|
||||
data_a: PortIo::new(0x21),
|
||||
cmd_b: PortIo::new(0xA0),
|
||||
data_b: PortIo::new(0xA1),
|
||||
table: IrqSafeSpinLock::new([None; 15]),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_isr_entries(entries: &mut [IdtEntry; IDT_SIZE]) {
|
||||
extern "C" {
|
||||
static __x86_64_irq_vectors: [usize; 16];
|
||||
}
|
||||
|
||||
for (i, &entry) in unsafe { __x86_64_irq_vectors.iter().enumerate() } {
|
||||
entries[i + 32] = IdtEntry::new(entry, 0x08, IdtEntry::PRESENT | IdtEntry::INT32);
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(include_str!("irq_vectors.S"), options(att_syntax));
|
||||
@@ -0,0 +1,56 @@
|
||||
//! x86_64-specific assembly functions
|
||||
use core::arch::asm;
|
||||
|
||||
/// Disables delievery of IRQs
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: requires ring 0
|
||||
#[inline(always)]
|
||||
pub unsafe fn irq_disable() {
|
||||
asm!("cli");
|
||||
}
|
||||
|
||||
/// Discards an entry related to `addr` from TLB cache
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: requires ring 0
|
||||
#[inline(always)]
|
||||
pub unsafe fn flush_tlb_virt(addr: usize) {
|
||||
asm!("invlpg ({})", in(reg) addr, options(att_syntax));
|
||||
}
|
||||
|
||||
/// Discards all entries related to `asid` from TLB cache
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to use for known [Process]es and their ASIDs
|
||||
// TODO actually implement this on x86-64
|
||||
#[inline(always)]
|
||||
pub unsafe fn flush_tlb_asid(_asid: usize) {}
|
||||
|
||||
/// Read a value from a model-specific register
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: arbitrary MSR reads, may cause CPU exceptions
|
||||
#[inline(always)]
|
||||
pub unsafe fn rdmsr(a: u32) -> u64 {
|
||||
let mut eax: u32;
|
||||
let mut edx: u32;
|
||||
asm!("rdmsr", in("ecx") a, out("eax") eax, out("edx") edx);
|
||||
(eax as u64) | ((edx as u64) << 32)
|
||||
}
|
||||
|
||||
/// Writes a value to a model-specific register
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: arbitrary MSR writes
|
||||
#[inline(always)]
|
||||
pub unsafe fn wrmsr(a: u32, b: u64) {
|
||||
let eax = b as u32;
|
||||
let edx = (b >> 32) as u32;
|
||||
asm!("wrmsr", in("ecx") a, in("eax") eax, in("edx") edx);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use core::arch::asm;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub struct PortIo<T> {
|
||||
port: u16,
|
||||
_pd: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> PortIo<T> {
|
||||
pub const unsafe fn new(port: u16) -> Self {
|
||||
Self {
|
||||
port,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PortIo<u8> {
|
||||
pub fn read(&self) -> u8 {
|
||||
let mut res: u8;
|
||||
unsafe {
|
||||
asm!("inb %dx, %al", in("dx") self.port, out("al") res, options(att_syntax));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn write(&self, value: u8) {
|
||||
unsafe {
|
||||
asm!("outb %al, %dx", in("dx") self.port, in("al") value, options(att_syntax));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
.macro irq_entry no
|
||||
__x86_64_irq_\no:
|
||||
cli
|
||||
pushq $0
|
||||
pushq $\no
|
||||
|
||||
push %rax
|
||||
push %rcx
|
||||
push %rdx
|
||||
push %rbx
|
||||
push %rbp
|
||||
push %rsi
|
||||
push %rdi
|
||||
|
||||
push %r8
|
||||
push %r9
|
||||
push %r10
|
||||
push %r11
|
||||
push %r12
|
||||
push %r13
|
||||
push %r14
|
||||
push %r15
|
||||
|
||||
mov %rsp, %rdi
|
||||
call __x86_64_irq_handler
|
||||
|
||||
pop %r15
|
||||
pop %r14
|
||||
pop %r13
|
||||
pop %r12
|
||||
pop %r11
|
||||
pop %r10
|
||||
pop %r9
|
||||
pop %r8
|
||||
|
||||
pop %rdi
|
||||
pop %rsi
|
||||
pop %rbp
|
||||
pop %rbx
|
||||
pop %rdx
|
||||
pop %rcx
|
||||
pop %rax
|
||||
|
||||
add $16, %rsp
|
||||
iretq
|
||||
.endm
|
||||
|
||||
.section .text
|
||||
|
||||
// __x86_64_irq_0:
|
||||
// cli
|
||||
//
|
||||
// push %rax
|
||||
// push %rcx
|
||||
// push %rdx
|
||||
// push %rbx
|
||||
// push %rbp
|
||||
// push %rsi
|
||||
// push %rdi
|
||||
//
|
||||
// push %r8
|
||||
// push %r9
|
||||
// push %r10
|
||||
// push %r11
|
||||
// push %r12
|
||||
// push %r13
|
||||
// push %r14
|
||||
// push %r15
|
||||
//
|
||||
// mov $0x20, %al
|
||||
// mov $0x20, %dx
|
||||
// outb %al, %dx
|
||||
//
|
||||
// call sched_yield
|
||||
//
|
||||
// pop %r15
|
||||
// pop %r14
|
||||
// pop %r13
|
||||
// pop %r12
|
||||
// pop %r11
|
||||
// pop %r10
|
||||
// pop %r9
|
||||
// pop %r8
|
||||
//
|
||||
// pop %rdi
|
||||
// pop %rsi
|
||||
// pop %rbp
|
||||
// pop %rbx
|
||||
// pop %rdx
|
||||
// pop %rcx
|
||||
// pop %rax
|
||||
//
|
||||
// iretq
|
||||
|
||||
irq_entry 0
|
||||
irq_entry 1
|
||||
irq_entry 2
|
||||
irq_entry 3
|
||||
irq_entry 4
|
||||
irq_entry 5
|
||||
irq_entry 6
|
||||
irq_entry 7
|
||||
irq_entry 8
|
||||
irq_entry 9
|
||||
irq_entry 10
|
||||
irq_entry 11
|
||||
irq_entry 12
|
||||
irq_entry 13
|
||||
irq_entry 14
|
||||
irq_entry 15
|
||||
|
||||
.section .rodata
|
||||
.global __x86_64_irq_vectors
|
||||
__x86_64_irq_vectors:
|
||||
.quad __x86_64_irq_0
|
||||
.quad __x86_64_irq_1
|
||||
.quad __x86_64_irq_2
|
||||
.quad __x86_64_irq_3
|
||||
.quad __x86_64_irq_4
|
||||
.quad __x86_64_irq_5
|
||||
.quad __x86_64_irq_6
|
||||
.quad __x86_64_irq_7
|
||||
.quad __x86_64_irq_8
|
||||
.quad __x86_64_irq_9
|
||||
.quad __x86_64_irq_10
|
||||
.quad __x86_64_irq_11
|
||||
.quad __x86_64_irq_12
|
||||
.quad __x86_64_irq_13
|
||||
.quad __x86_64_irq_14
|
||||
.quad __x86_64_irq_15
|
||||
@@ -0,0 +1,71 @@
|
||||
//! x86_64 platform implementation details
|
||||
use crate::dev::{display::StaticFramebuffer, irq::IntController, serial::SerialDevice};
|
||||
use core::arch::asm;
|
||||
|
||||
mod uart;
|
||||
use uart::Uart;
|
||||
mod intc;
|
||||
use intc::I8259;
|
||||
pub use intc::IrqNumber;
|
||||
mod timer;
|
||||
use timer::Timer;
|
||||
|
||||
mod io;
|
||||
pub(self) use io::PortIo;
|
||||
|
||||
pub mod boot;
|
||||
pub mod context;
|
||||
pub(self) mod exception;
|
||||
pub(self) mod gdt;
|
||||
pub(self) mod idt;
|
||||
pub mod intrin;
|
||||
pub mod reg;
|
||||
pub(self) mod syscall;
|
||||
pub mod virt;
|
||||
|
||||
pub use syscall::SyscallFrame as ForkFrame;
|
||||
|
||||
/// Masks IRQs and returns previous IRQ mask state
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: disables IRQ handling temporarily
|
||||
#[inline(always)]
|
||||
pub unsafe fn irq_mask_save() -> u64 {
|
||||
let mut res;
|
||||
asm!("pushf; cli; pop {}", out(reg) res, options(att_syntax));
|
||||
res
|
||||
}
|
||||
|
||||
/// Restores IRQ mask state
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: modifies interrupt behavior. Must only be used in
|
||||
/// conjunction with [irq_mask_save]
|
||||
#[inline(always)]
|
||||
pub unsafe fn irq_restore(state: u64) {
|
||||
if state & (1 << 9) != 0 {
|
||||
asm!("sti");
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to interrupt controller device
|
||||
pub fn intc() -> &'static impl IntController {
|
||||
&INTC
|
||||
}
|
||||
|
||||
/// Returns a reference to primary console device
|
||||
pub fn console() -> &'static impl SerialDevice {
|
||||
&COM1
|
||||
}
|
||||
|
||||
/// Returns a reference to CPU-local timer device
|
||||
pub fn local_timer() -> &'static Timer {
|
||||
&TIMER
|
||||
}
|
||||
|
||||
static COM1: Uart = unsafe { Uart::new(0x3F8, IrqNumber::new(4)) };
|
||||
pub(self) static INTC: I8259 = I8259::new();
|
||||
pub(self) static DISPLAY: StaticFramebuffer = StaticFramebuffer::uninit();
|
||||
pub(self) static TIMER: Timer = Timer::new(IrqNumber::new(0));
|
||||
@@ -0,0 +1,152 @@
|
||||
//! x86_64 model-specific and control register interfaces
|
||||
|
||||
macro_rules! wrap_msr {
|
||||
($struct_name:ident, $name:ident, $address:expr, $fields:tt) => {
|
||||
register_bitfields! {
|
||||
u64,
|
||||
#[allow(missing_docs)]
|
||||
pub $name $fields
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct $struct_name;
|
||||
|
||||
impl Readable for $struct_name {
|
||||
type T = u64;
|
||||
type R = $name::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
unsafe {
|
||||
rdmsr($address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for $struct_name {
|
||||
type T = u64;
|
||||
type R = $name::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
wrmsr($address, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub const $name: $struct_name = $struct_name;
|
||||
}
|
||||
}
|
||||
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
use core::arch::asm;
|
||||
use crate::arch::x86_64::intrin::{rdmsr, wrmsr};
|
||||
|
||||
// CRn registers
|
||||
register_bitfields! {
|
||||
u64,
|
||||
/// Control register CR4 fields
|
||||
#[allow(missing_docs)]
|
||||
pub CR4 [
|
||||
/// Indicates OS support for FXSR/FXRSTOR instructions
|
||||
OSFXSR OFFSET(9) NUMBITS(1) [],
|
||||
/// Indicates OS support for unmasked SIMD exceptions
|
||||
OSXMMEXCPT OFFSET(10) NUMBITS(1) []
|
||||
]
|
||||
}
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
/// Control register CR0 fields
|
||||
#[allow(missing_docs)]
|
||||
pub CR0 [
|
||||
/// Indicates requirement for x87 emulation
|
||||
EM OFFSET(2) NUMBITS(1) [],
|
||||
/// Controls x87 exception handling
|
||||
MP OFFSET(1) NUMBITS(1) []
|
||||
]
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct Cr4;
|
||||
#[allow(missing_docs)]
|
||||
pub struct Cr0;
|
||||
|
||||
impl Readable for Cr4 {
|
||||
type T = u64;
|
||||
type R = CR4::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut res: u64;
|
||||
unsafe {
|
||||
asm!("mov %cr4, {}", out(reg) res, options(att_syntax))
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Cr4 {
|
||||
type T = u64;
|
||||
type R = CR4::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!("mov {}, %cr4", in(reg) value, options(att_syntax));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for Cr0 {
|
||||
type T = u64;
|
||||
type R = CR0::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn get(&self) -> Self::T {
|
||||
let mut res: u64;
|
||||
unsafe {
|
||||
asm!("mov %cr0, {}", out(reg) res, options(att_syntax))
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for Cr0 {
|
||||
type T = u64;
|
||||
type R = CR0::Register;
|
||||
|
||||
#[inline(always)]
|
||||
fn set(&self, value: Self::T) {
|
||||
unsafe {
|
||||
asm!("mov {}, %cr0", in(reg) value, options(att_syntax));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Control register CR4
|
||||
pub const CR4: Cr4 = Cr4;
|
||||
/// Control register CR0
|
||||
pub const CR0: Cr0 = Cr0;
|
||||
|
||||
wrap_msr!(MsrIa32Efer, MSR_IA32_EFER, 0xC0000080, [
|
||||
SCE OFFSET(0) NUMBITS(1) [],
|
||||
LME OFFSET(8) NUMBITS(1) [],
|
||||
LMA OFFSET(10) NUMBITS(1) [],
|
||||
NXE OFFSET(11) NUMBITS(1) []
|
||||
]);
|
||||
wrap_msr!(MsrIa32Lstar, MSR_IA32_LSTAR, 0xC0000082, [
|
||||
VALUE OFFSET(0) NUMBITS(64) []
|
||||
]);
|
||||
wrap_msr!(MsrIa32Star, MSR_IA32_STAR, 0xC0000081, [
|
||||
SYSCALL_CS_SS OFFSET(32) NUMBITS(8) [],
|
||||
SYSRET_CS_SS OFFSET(48) NUMBITS(8) []
|
||||
]);
|
||||
wrap_msr!(MsrIa32Sfmask, MSR_IA32_SFMASK, 0xC0000084, [
|
||||
IF OFFSET(9) NUMBITS(1) []
|
||||
]);
|
||||
@@ -0,0 +1,13 @@
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_bitfields,
|
||||
};
|
||||
use crate::arch::x86_64::intrin::{rdmsr, wrmsr};
|
||||
|
||||
register_bitfields! {
|
||||
u64,
|
||||
pub MSR_IA32_EFER [
|
||||
]
|
||||
}
|
||||
|
||||
wrap_msr!(MSR_IA32_EFER, 0xC0000080);
|
||||
@@ -0,0 +1,60 @@
|
||||
.section .text
|
||||
.global __x86_64_syscall_entry
|
||||
__x86_64_syscall_entry:
|
||||
// Syscalls only happen from user space, so
|
||||
// relying on TSS.RSP0 is safe here I guess
|
||||
mov %rsp, scratch(%rip)
|
||||
mov (4 + TSS)(%rip), %rsp
|
||||
|
||||
// Now on kernel stack
|
||||
// Push the whole state
|
||||
push %rcx // saved %rip
|
||||
push %r11 // saved %rflags
|
||||
mov scratch(%rip), %r11
|
||||
push %r11 // saved %rsp
|
||||
|
||||
push %r12
|
||||
push %r13
|
||||
push %r14
|
||||
push %r15
|
||||
push %rbp
|
||||
push %rbx
|
||||
|
||||
push %rax
|
||||
|
||||
push %r9
|
||||
push %r8
|
||||
push %r10
|
||||
push %rdx
|
||||
push %rsi
|
||||
push %rdi
|
||||
|
||||
mov %rsp, %rdi
|
||||
call __x86_64_syscall
|
||||
|
||||
pop %rdi
|
||||
pop %rsi
|
||||
pop %rdx
|
||||
pop %r10
|
||||
pop %r8
|
||||
pop %r9
|
||||
|
||||
pop %rax
|
||||
|
||||
pop %rbx
|
||||
pop %rbp
|
||||
pop %r15
|
||||
pop %r14
|
||||
pop %r13
|
||||
pop %r12
|
||||
|
||||
pop %rdi
|
||||
pop %r11
|
||||
pop %rcx
|
||||
mov %rdi, %rsp
|
||||
|
||||
sysretq
|
||||
|
||||
.section .bss
|
||||
scratch:
|
||||
.skip 8
|
||||
@@ -0,0 +1,59 @@
|
||||
use crate::arch::x86_64::reg::{MSR_IA32_EFER, MSR_IA32_LSTAR, MSR_IA32_SFMASK, MSR_IA32_STAR};
|
||||
use core::arch::global_asm;
|
||||
use tock_registers::interfaces::{ReadWriteable, Writeable};
|
||||
use libsys::abi::SystemCall;
|
||||
use crate::syscall;
|
||||
|
||||
/// Syscall registers
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SyscallFrame {
|
||||
/// General-purpose registers
|
||||
pub x: [usize; 13],
|
||||
|
||||
/// Caller (ring 3) saved stack pointer
|
||||
pub saved_rsp: usize,
|
||||
/// Caller (ring 3) saved processor flags
|
||||
pub saved_rflags: usize,
|
||||
/// Caller (ring 3) return address
|
||||
pub saved_rip: usize,
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
extern "C" {
|
||||
fn __x86_64_syscall_entry();
|
||||
}
|
||||
|
||||
MSR_IA32_SFMASK.write(MSR_IA32_SFMASK::IF::SET);
|
||||
MSR_IA32_LSTAR.set(__x86_64_syscall_entry as usize as u64);
|
||||
MSR_IA32_STAR
|
||||
.write(MSR_IA32_STAR::SYSRET_CS_SS.val(0x1B - 8) + MSR_IA32_STAR::SYSCALL_CS_SS.val(0x08));
|
||||
MSR_IA32_EFER.modify(MSR_IA32_EFER::SCE::SET);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
extern "C" fn __x86_64_syscall(frame: &mut SyscallFrame) {
|
||||
let num = SystemCall::from_repr(frame.x[6]);
|
||||
if num.is_none() {
|
||||
todo!();
|
||||
}
|
||||
let num = num.unwrap();
|
||||
if num == SystemCall::Fork {
|
||||
match unsafe { syscall::sys_fork(frame) } {
|
||||
Ok(pid) => frame.x[6] = u32::from(pid) as usize,
|
||||
Err(err) => {
|
||||
frame.x[6] = err.to_negative_isize() as usize;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
match syscall::syscall(num, &frame.x[..6]) {
|
||||
Ok(val) => frame.x[6] = val,
|
||||
Err(err) => {
|
||||
frame.x[6] = err.to_negative_isize() as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global_asm!(include_str!("syscall.S"), options(att_syntax));
|
||||
@@ -0,0 +1,69 @@
|
||||
//! i.... timer implementation
|
||||
|
||||
use crate::arch::machine::{self, IrqNumber, PortIo};
|
||||
use crate::dev::{
|
||||
irq::{IntController, IntSource},
|
||||
pseudo,
|
||||
timer::TimestampSource,
|
||||
Device,
|
||||
};
|
||||
use crate::proc;
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
use core::time::Duration;
|
||||
use libsys::error::Errno;
|
||||
|
||||
// 1.1931816666 MHz base freq
|
||||
|
||||
/// Generic timer struct
|
||||
pub struct Timer {
|
||||
data0: PortIo<u8>,
|
||||
counter: AtomicU64,
|
||||
irq: IrqNumber,
|
||||
}
|
||||
|
||||
impl Device for Timer {
|
||||
fn name(&self) -> &'static str {
|
||||
"Intel Timer"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
const DIV: u16 = (1193182u32 / 1000) as u16;
|
||||
|
||||
self.data0.write((DIV & 0xFF) as u8);
|
||||
self.data0.write((DIV >> 8) as u8);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TimestampSource for Timer {
|
||||
fn timestamp(&self) -> Result<Duration, Errno> {
|
||||
Ok(Duration::from_millis(self.counter.load(Ordering::Relaxed)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntSource for Timer {
|
||||
fn handle_irq(&self) -> Result<(), Errno> {
|
||||
let value = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
proc::wait::tick();
|
||||
pseudo::RANDOM.set_state(value as u32);
|
||||
proc::switch();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_irqs(&'static self) -> Result<(), Errno> {
|
||||
machine::INTC.register_handler(self.irq, self)?;
|
||||
machine::INTC.enable_irq(self.irq)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
/// Constructs a new instance of ARM Generic Timer
|
||||
pub const fn new(irq: IrqNumber) -> Self {
|
||||
Self {
|
||||
data0: unsafe { PortIo::new(0x40) },
|
||||
counter: AtomicU64::new(0),
|
||||
irq,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::arch::x86_64::{self, IrqNumber, PortIo};
|
||||
use crate::dev::{
|
||||
irq::{IntController, IntSource},
|
||||
serial::SerialDevice,
|
||||
tty::{CharRing, TtyDevice},
|
||||
Device,
|
||||
};
|
||||
use libsys::error::Errno;
|
||||
|
||||
#[derive(TtyCharDevice)]
|
||||
pub(super) struct Uart {
|
||||
dr: PortIo<u8>,
|
||||
ier: PortIo<u8>,
|
||||
isr: PortIo<u8>,
|
||||
ring: CharRing<16>,
|
||||
irq: IrqNumber,
|
||||
}
|
||||
|
||||
impl Device for Uart {
|
||||
fn name(&self) -> &'static str {
|
||||
"x86 COM-port"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TtyDevice<16> for Uart {
|
||||
fn ring(&self) -> &CharRing<16> {
|
||||
&self.ring
|
||||
}
|
||||
}
|
||||
|
||||
impl IntSource for Uart {
|
||||
fn handle_irq(&self) -> Result<(), Errno> {
|
||||
if self.isr.read() != 0 {
|
||||
self.recv_byte(self.dr.read());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_irqs(&'static self) -> Result<(), Errno> {
|
||||
// TODO shared IRQs between COM# ports
|
||||
x86_64::INTC.register_handler(self.irq, self)?;
|
||||
self.ier.write(1 << 0);
|
||||
x86_64::INTC.enable_irq(self.irq)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialDevice for Uart {
|
||||
fn send(&self, byte: u8) -> Result<(), Errno> {
|
||||
self.dr.write(byte);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn recv(&self, _blocking: bool) -> Result<u8, Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Uart {
|
||||
pub const unsafe fn new(base: u16, irq: IrqNumber) -> Self {
|
||||
Self {
|
||||
dr: PortIo::new(base),
|
||||
ier: PortIo::new(base + 1),
|
||||
isr: PortIo::new(base + 2),
|
||||
ring: CharRing::new(),
|
||||
irq,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
use super::table::TableImpl;
|
||||
|
||||
#[repr(C, align(0x1000))]
|
||||
pub struct FixedTableGroup {
|
||||
pub pml4: TableImpl,
|
||||
pub pdpt: TableImpl,
|
||||
pub pd: [TableImpl; 16],
|
||||
}
|
||||
|
||||
// Upper mappings
|
||||
#[no_mangle]
|
||||
pub(super) static mut KERNEL_FIXED: FixedTableGroup = FixedTableGroup {
|
||||
pml4: TableImpl::empty(),
|
||||
pdpt: TableImpl::empty(),
|
||||
pd: [TableImpl::empty(); 16],
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
//! x86_64 virtual memory management implementation
|
||||
use crate::mem::virt::table::{MapAttributes, Entry};
|
||||
use core::arch::asm;
|
||||
use libsys::error::Errno;
|
||||
|
||||
mod table;
|
||||
mod fixed;
|
||||
pub use table::{EntryImpl, SpaceImpl};
|
||||
use fixed::KERNEL_FIXED;
|
||||
|
||||
bitflags! {
|
||||
/// Raw attributes for x86_64 [Entry] implementation
|
||||
pub struct RawAttributesImpl: u64 {
|
||||
/// Entry is valid and mapped
|
||||
const PRESENT = EntryImpl::PRESENT;
|
||||
/// Entry is writable by user processes
|
||||
const WRITE = EntryImpl::WRITE;
|
||||
/// Entry is accessible (readable) by user processes
|
||||
const USER = EntryImpl::USER;
|
||||
/// Entry points to a block instead of a next-level table
|
||||
const BLOCK = EntryImpl::BLOCK;
|
||||
/// Entry is global across virtual address spaces
|
||||
const GLOBAL = 1 << 8;
|
||||
/// Entry is marked as Copy-on-Write
|
||||
const EX_COW = EntryImpl::EX_COW;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MapAttributes> for RawAttributesImpl {
|
||||
fn from(i: MapAttributes) -> Self {
|
||||
let mut res = RawAttributesImpl::empty();
|
||||
|
||||
if i.contains(MapAttributes::USER_READ) {
|
||||
res |= RawAttributesImpl::USER;
|
||||
}
|
||||
if i.contains(MapAttributes::USER_WRITE) {
|
||||
res |= RawAttributesImpl::WRITE | RawAttributesImpl::USER;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs initialization of virtual memory control by kernel
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only safe to be called once during virtual memory init.
|
||||
pub unsafe fn enable() {
|
||||
// Remove the lower mapping
|
||||
KERNEL_FIXED.pml4[0] = EntryImpl::EMPTY;
|
||||
|
||||
// Flush the TLB by reloading cr3
|
||||
asm!("mov %cr3, %rax; mov %rax, %cr3", options(att_syntax));
|
||||
}
|
||||
|
||||
/// Allocates a range of virtual memory of requested size and maps
|
||||
/// it to specified device memory
|
||||
pub fn map_device_memory(_phys: usize, _count: usize) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
use crate::arch::intrin;
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
virt::table::{Entry, MapAttributes, Space},
|
||||
};
|
||||
use core::fmt;
|
||||
use core::ops::{Index, IndexMut};
|
||||
use libsys::error::Errno;
|
||||
|
||||
use super::{RawAttributesImpl, KERNEL_FIXED};
|
||||
|
||||
/// Transparent wrapper structure representing a single
|
||||
/// translation table entry
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct EntryImpl(u64);
|
||||
|
||||
/// Structure describing a single level of translation mappings
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C, align(0x1000))]
|
||||
pub struct TableImpl {
|
||||
entries: [EntryImpl; 512],
|
||||
}
|
||||
|
||||
/// Top-level translation table wrapper
|
||||
#[repr(transparent)]
|
||||
pub struct SpaceImpl(TableImpl);
|
||||
|
||||
impl EntryImpl {
|
||||
pub(super) const PRESENT: u64 = 1 << 0;
|
||||
pub(super) const WRITE: u64 = 1 << 1;
|
||||
pub(super) const USER: u64 = 1 << 2;
|
||||
pub(super) const BLOCK: u64 = 1 << 7;
|
||||
pub(super) const EX_COW: u64 = 1 << 62;
|
||||
|
||||
const PHYS_MASK: u64 = 0x0000FFFFFFFFF000;
|
||||
}
|
||||
|
||||
impl fmt::Debug for EntryImpl {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("EntryImpl")
|
||||
.field("address", &self.address())
|
||||
.field("flags", &unsafe {
|
||||
RawAttributesImpl::from_bits_unchecked(self.0 & 0xFFF)
|
||||
})
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry for EntryImpl {
|
||||
type RawAttributes = RawAttributesImpl;
|
||||
const EMPTY: Self = Self(0);
|
||||
|
||||
#[inline]
|
||||
fn normal(addr: usize, attrs: MapAttributes) -> Self {
|
||||
Self((addr as u64) | RawAttributesImpl::from(attrs).bits() | Self::PRESENT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn block(addr: usize, attrs: MapAttributes) -> Self {
|
||||
Self((addr as u64) | RawAttributesImpl::from(attrs).bits() | Self::BLOCK | Self::PRESENT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn address(self) -> usize {
|
||||
(self.0 & Self::PHYS_MASK) as usize
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_address(&mut self, virt: usize) {
|
||||
self.0 &= !Self::PHYS_MASK;
|
||||
self.0 |= (virt as u64) & Self::PHYS_MASK;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_present(self) -> bool {
|
||||
self.0 & Self::PRESENT != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_normal(self) -> bool {
|
||||
self.0 & Self::BLOCK == 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fork_with_cow(&mut self) -> Self {
|
||||
self.0 &= !Self::WRITE;
|
||||
self.0 |= Self::EX_COW;
|
||||
*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn copy_from_cow(self, new_addr: usize) -> Self {
|
||||
let attrs = self.0 & !(Self::PHYS_MASK | Self::EX_COW);
|
||||
Self(((new_addr as u64) & Self::PHYS_MASK) | (attrs | Self::WRITE))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_cow(self) -> bool {
|
||||
self.0 & Self::EX_COW != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_user_writable(self) -> bool {
|
||||
const BITS: u64 = EntryImpl::USER | EntryImpl::WRITE;
|
||||
self.0 & BITS == BITS
|
||||
}
|
||||
}
|
||||
|
||||
impl Space for SpaceImpl {
|
||||
type Entry = EntryImpl;
|
||||
|
||||
fn alloc_empty() -> Result<&'static mut Self, Errno> {
|
||||
let kernel_pdpt_phys =
|
||||
unsafe { &KERNEL_FIXED.pdpt as *const _ as usize - mem::KERNEL_OFFSET };
|
||||
let page = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(page) as *mut Self) };
|
||||
res.0.entries[..511].fill(EntryImpl::EMPTY);
|
||||
res.0.entries[511] = EntryImpl::normal(
|
||||
kernel_pdpt_phys,
|
||||
MapAttributes::SHARE_OUTER
|
||||
| MapAttributes::KERNEL_EXEC
|
||||
| MapAttributes::KERNEL_WRITE
|
||||
| MapAttributes::USER_READ
|
||||
| MapAttributes::USER_WRITE,
|
||||
);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
unsafe fn release(space: &'static mut Self) {
|
||||
let pdpt0 = space.0.next_level_table_mut(0).unwrap();
|
||||
|
||||
for pdpti in 0..512 {
|
||||
let pdpt_entry = pdpt0[pdpti];
|
||||
if !pdpt_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(pdpt_entry.is_normal());
|
||||
let pd = &mut *(mem::virtualize(pdpt_entry.address()) as *mut TableImpl);
|
||||
|
||||
for pdi in 0..512 {
|
||||
let pd_entry = pd[pdi];
|
||||
if !pd_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(pd_entry.is_normal());
|
||||
let pt = &mut *(mem::virtualize(pd_entry.address()) as *mut TableImpl);
|
||||
|
||||
for pti in 0..512 {
|
||||
let entry = pt[pti];
|
||||
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_normal());
|
||||
phys::free_page(entry.address()).unwrap();
|
||||
}
|
||||
phys::free_page(pd_entry.address()).unwrap();
|
||||
}
|
||||
phys::free_page(pdpt_entry.address()).unwrap();
|
||||
}
|
||||
phys::free_page(space.0[0].address()).unwrap();
|
||||
}
|
||||
|
||||
fn fork(&mut self) -> Result<&'static mut Self, Errno> {
|
||||
let res = Self::alloc_empty()?;
|
||||
let pdpt0 = self.0.next_level_table_mut(0).unwrap();
|
||||
|
||||
for pdpti in 0..512 {
|
||||
if let Some(pd) = pdpt0.next_level_table_mut(pdpti) {
|
||||
for pdi in 0..512 {
|
||||
if let Some(pt) = pd.next_level_table_mut(pdi) {
|
||||
for pti in 0..512 {
|
||||
let entry = &mut pt[pti];
|
||||
let virt_addr = (pdpti << 30) | (pdi << 21) | (pti << 12);
|
||||
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_normal());
|
||||
let src_phys = entry.address();
|
||||
|
||||
// let dst_phys = phys::alloc_page(PageUsage::UserPrivate)?;
|
||||
// unsafe {
|
||||
// use libsys::mem::memcpy;
|
||||
// memcpy(
|
||||
// mem::virtualize(dst_phys) as *mut u8,
|
||||
// mem::virtualize(src_phys) as *const u8,
|
||||
// 4096
|
||||
// );
|
||||
// }
|
||||
|
||||
// debugln!("Clone page {:#x}", virt_addr);
|
||||
// let new_entry = EntryImpl::normal(dst_phys, MapAttributes::USER_WRITE | MapAttributes::USER_READ);
|
||||
|
||||
// TODO check exact page usage
|
||||
let dst_phys = unsafe { phys::fork_page(src_phys)? };
|
||||
|
||||
let new_entry = if dst_phys != src_phys {
|
||||
todo!()
|
||||
} else if entry.is_user_writable() {
|
||||
entry.fork_with_cow()
|
||||
} else {
|
||||
*entry
|
||||
};
|
||||
|
||||
unsafe {
|
||||
res.write_last_level(virt_addr, new_entry, true, false)?;
|
||||
intrin::flush_tlb_virt(virt_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
unsafe fn write_last_level(
|
||||
&mut self,
|
||||
virt: usize,
|
||||
entry: Self::Entry,
|
||||
_create_intermediate: bool, // TODO handle this properly
|
||||
overwrite: bool,
|
||||
) -> Result<(), Errno> {
|
||||
let l0i = virt >> 39;
|
||||
let l1i = (virt >> 30) & 0x1FF;
|
||||
let l2i = (virt >> 21) & 0x1FF;
|
||||
let l3i = (virt >> 12) & 0x1FF;
|
||||
|
||||
let l0_table = self.0.next_level_table_or_alloc(l0i)?;
|
||||
let l1_table = l0_table.next_level_table_or_alloc(l1i)?;
|
||||
let l2_table = l1_table.next_level_table_or_alloc(l2i)?;
|
||||
|
||||
if l2_table[l3i].is_present() && !overwrite {
|
||||
warnln!(
|
||||
"Entry already exists for address: virt={:#x}, prev={:#x}, new={:#x}",
|
||||
virt,
|
||||
l2_table[l3i].address(),
|
||||
entry.address()
|
||||
);
|
||||
Err(Errno::AlreadyExists)
|
||||
} else {
|
||||
l2_table[l3i] = entry;
|
||||
intrin::flush_tlb_virt(virt);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn read_last_level(&self, virt: usize) -> Result<Self::Entry, Errno> {
|
||||
let l0i = virt >> 39;
|
||||
let l1i = (virt >> 30) & 0x1FF;
|
||||
let l2i = (virt >> 21) & 0x1FF;
|
||||
let l3i = (virt >> 12) & 0x1FF;
|
||||
|
||||
let l0_table = self.0.next_level_table(l0i).ok_or(Errno::DoesNotExist)?;
|
||||
let l1_table = l0_table.next_level_table(l1i).ok_or(Errno::DoesNotExist)?;
|
||||
let l2_table = l1_table.next_level_table(l2i).ok_or(Errno::DoesNotExist)?;
|
||||
|
||||
let entry = l2_table[l3i];
|
||||
if entry.is_present() {
|
||||
Ok(entry)
|
||||
} else {
|
||||
Err(Errno::DoesNotExist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableImpl {
|
||||
/// Constructs a table with no valid mappings
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
entries: [EntryImpl::EMPTY; 512],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// If `index` represents a `Block`-type mapping, will return an error.
|
||||
/// If `index` does not map to any translation table, will try to allocate, init and
|
||||
/// map a new one, returning it after doing so.
|
||||
pub fn next_level_table_or_alloc(&mut self, index: usize) -> Result<&'static mut Self, Errno> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
Ok(unsafe { &mut *(mem::virtualize(entry.address()) as *mut _) })
|
||||
} else {
|
||||
let phys = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(phys) as *mut Self) };
|
||||
self[index] = EntryImpl::normal(
|
||||
phys,
|
||||
MapAttributes::USER_WRITE
|
||||
| MapAttributes::USER_READ
|
||||
| MapAttributes::KERNEL_WRITE
|
||||
| MapAttributes::USER_EXEC,
|
||||
);
|
||||
res.entries.fill(EntryImpl::EMPTY);
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// Same as [next_level_table_or_alloc], but returns `None` if no table is mapped.
|
||||
pub fn next_level_table(&self, index: usize) -> Option<&'static Self> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
panic!("Entry is not a table: idx={}", index);
|
||||
}
|
||||
|
||||
Some(unsafe { &*(mem::virtualize(entry.address()) as *const _) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns mutable next-level translation table reference for `index`,
|
||||
/// if one is present. Same as [next_level_table_or_alloc], but returns
|
||||
/// `None` if no table is mapped.
|
||||
pub fn next_level_table_mut(&mut self, index: usize) -> Option<&'static mut Self> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_normal() {
|
||||
panic!("Entry is not a table: idx={}", index);
|
||||
}
|
||||
|
||||
Some(unsafe { &mut *(mem::virtualize(entry.address()) as *mut _) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for TableImpl {
|
||||
type Output = EntryImpl;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.entries[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for TableImpl {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.entries[index]
|
||||
}
|
||||
}
|
||||
+207
-11
@@ -11,20 +11,110 @@
|
||||
//! * [warnln!]
|
||||
//! * [errorln!]
|
||||
|
||||
use crate::dev::serial::SerialDevice;
|
||||
use crate::dev::{
|
||||
display::{Display, FramebufferInfo},
|
||||
serial::SerialDevice,
|
||||
};
|
||||
use crate::font;
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use libsys::{debug::TraceLevel, error::Errno, mem::memcpy};
|
||||
|
||||
/// Currently active print level
|
||||
pub static LEVEL: Level = Level::Debug;
|
||||
static COLOR_MAP: [u32; 16] = [
|
||||
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA, 0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA, 0x555555,
|
||||
0x5555FF, 0x55FF55, 0x55FFFF, 0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF,
|
||||
];
|
||||
static ATTR_MAP: [usize; 10] = [0, 4, 2, 6, 1, 5, 3, 7, 7, 7];
|
||||
static DISPLAY: IrqSafeSpinLock<FramebufferOutput> = IrqSafeSpinLock::new(FramebufferOutput {
|
||||
display: None,
|
||||
col: 0,
|
||||
row: 0,
|
||||
fg: 0xBBBBBB,
|
||||
bg: 0x000000,
|
||||
esc: EscapeState::None,
|
||||
esc_argv: [0; 8],
|
||||
esc_argc: 0,
|
||||
});
|
||||
|
||||
enum EscapeState {
|
||||
None,
|
||||
Esc,
|
||||
Data,
|
||||
}
|
||||
|
||||
struct FramebufferOutput {
|
||||
display: Option<&'static dyn Display>,
|
||||
row: usize,
|
||||
col: usize,
|
||||
fg: u32,
|
||||
bg: u32,
|
||||
esc: EscapeState,
|
||||
esc_argv: [usize; 8],
|
||||
esc_argc: usize,
|
||||
}
|
||||
|
||||
impl fmt::Write for FramebufferOutput {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
if self.display.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let fb = self.display.unwrap().framebuffer().unwrap();
|
||||
|
||||
for ch in s.chars() {
|
||||
self.putc(fb, ch);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets active display for debug console output
|
||||
pub fn set_display(disp: &'static dyn Display) {
|
||||
DISPLAY.lock().display = Some(disp);
|
||||
}
|
||||
|
||||
/// Kernel logging levels
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
#[repr(u32)]
|
||||
pub enum Level {
|
||||
/// Debugging information
|
||||
Debug,
|
||||
Debug = 1,
|
||||
/// General informational messages
|
||||
Info,
|
||||
Info = 2,
|
||||
/// Non-critical warnings
|
||||
Warn,
|
||||
Warn = 3,
|
||||
/// Critical errors
|
||||
Error,
|
||||
Error = 4,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for Level {
|
||||
type Error = Errno;
|
||||
|
||||
#[inline(always)]
|
||||
fn try_from(l: u32) -> Result<Level, Errno> {
|
||||
match l {
|
||||
1 => Ok(Level::Debug),
|
||||
2 => Ok(Level::Info),
|
||||
3 => Ok(Level::Warn),
|
||||
4 => Ok(Level::Error),
|
||||
_ => Err(Errno::InvalidArgument),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TraceLevel> for Level {
|
||||
#[inline(always)]
|
||||
fn from(l: TraceLevel) -> Self {
|
||||
match l {
|
||||
TraceLevel::Debug => Self::Debug,
|
||||
TraceLevel::Info => Self::Info,
|
||||
TraceLevel::Warn => Self::Warn,
|
||||
TraceLevel::Error => Self::Error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SerialOutput<T: 'static + SerialDevice> {
|
||||
@@ -101,13 +191,119 @@ macro_rules! errorln {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn _debug(_level: Level, args: fmt::Arguments) {
|
||||
pub fn _debug(level: Level, args: fmt::Arguments) {
|
||||
use crate::arch::machine;
|
||||
use fmt::Write;
|
||||
|
||||
SerialOutput {
|
||||
inner: machine::console(),
|
||||
if level > Level::Debug {
|
||||
DISPLAY.lock().write_fmt(args).ok();
|
||||
}
|
||||
|
||||
if level >= LEVEL {
|
||||
SerialOutput {
|
||||
inner: machine::console(),
|
||||
}
|
||||
.write_fmt(args)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl FramebufferOutput {
|
||||
const CW: usize = 8;
|
||||
const CH: usize = 12;
|
||||
|
||||
pub fn set_char(&mut self, fb: &FramebufferInfo, x: usize, y: usize, ch: char) {
|
||||
if (x + 1) * Self::CW >= fb.width || (y + 1) * Self::CH >= fb.height {
|
||||
return;
|
||||
}
|
||||
font::get().draw(fb, x * Self::CW, y * Self::CH, ch, self.fg, self.bg);
|
||||
}
|
||||
|
||||
pub fn scroll(&mut self, fb: &FramebufferInfo) {
|
||||
let stride = 4 * Self::CH * fb.width;
|
||||
let h = fb.height / Self::CH - 1;
|
||||
if self.row == h {
|
||||
for y in 0..(h - 1) {
|
||||
unsafe {
|
||||
memcpy(
|
||||
(fb.virt_base + stride * y) as *mut u8,
|
||||
(fb.virt_base + (y + 1) * stride) as *const u8,
|
||||
stride,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.row = h - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn putc(&mut self, fb: &FramebufferInfo, ch: char) {
|
||||
#![allow(clippy::single_match)]
|
||||
|
||||
match self.esc {
|
||||
EscapeState::None => {
|
||||
match ch {
|
||||
'\x1B' => {
|
||||
self.esc = EscapeState::Esc;
|
||||
self.esc_argv.fill(0);
|
||||
self.esc_argc = 0;
|
||||
}
|
||||
' '..='\x7E' => {
|
||||
self.set_char(fb, self.col, self.row, ch);
|
||||
|
||||
// Advance the cursor
|
||||
self.col += 1;
|
||||
if (self.col + 1) * Self::CW >= fb.width {
|
||||
self.col = 0;
|
||||
self.row += 1;
|
||||
}
|
||||
}
|
||||
'\n' => {
|
||||
self.col = 0;
|
||||
self.row += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
EscapeState::Esc => match ch {
|
||||
'[' => {
|
||||
self.esc = EscapeState::Data;
|
||||
}
|
||||
_ => {
|
||||
self.esc = EscapeState::None;
|
||||
}
|
||||
},
|
||||
EscapeState::Data => {
|
||||
match ch {
|
||||
'0'..='9' => {
|
||||
self.esc_argv[self.esc_argc] *= 10;
|
||||
self.esc_argv[self.esc_argc] += (ch as u8 - b'0') as usize;
|
||||
}
|
||||
';' => {
|
||||
self.esc_argc += 1;
|
||||
}
|
||||
_ => {
|
||||
self.esc_argc += 1;
|
||||
self.esc = EscapeState::None;
|
||||
}
|
||||
}
|
||||
|
||||
match ch {
|
||||
'm' => {
|
||||
for i in 0..self.esc_argc {
|
||||
let item = self.esc_argv[i];
|
||||
if item / 10 == 4 {
|
||||
self.bg = COLOR_MAP[ATTR_MAP[(item % 10) as usize]];
|
||||
}
|
||||
if item / 10 == 3 {
|
||||
self.fg = COLOR_MAP[ATTR_MAP[(item % 10) as usize]];
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.scroll(fb);
|
||||
}
|
||||
.write_fmt(args)
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
//! Graphical display interfaces
|
||||
use crate::dev::Device;
|
||||
use libsys::error::Errno;
|
||||
use crate::util::InitOnce;
|
||||
|
||||
/// Description of a framebuffer
|
||||
pub struct FramebufferInfo {
|
||||
/// Width in pixels
|
||||
pub width: usize,
|
||||
/// Height in pixels
|
||||
pub height: usize,
|
||||
/// Physical start address
|
||||
pub phys_base: usize,
|
||||
/// Virtual address where the framebuffer is mapped
|
||||
pub virt_base: usize
|
||||
}
|
||||
|
||||
/// Generic display interface
|
||||
pub trait Display: Device {
|
||||
/// Changes currently active display mode
|
||||
fn set_mode(&self, mode: DisplayMode) -> Result<(), Errno>;
|
||||
/// Returns currently active framebuffer information
|
||||
fn framebuffer(&self) -> Result<&FramebufferInfo, Errno>;
|
||||
}
|
||||
|
||||
/// Display configuration details
|
||||
#[allow(dead_code)]
|
||||
pub struct DisplayMode {
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
/// Generic single-mode framebuffer
|
||||
pub struct StaticFramebuffer {
|
||||
framebuffer: InitOnce<FramebufferInfo>
|
||||
}
|
||||
|
||||
impl Device for StaticFramebuffer {
|
||||
fn name(&self) -> &'static str {
|
||||
"Generic framebuffer device"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StaticFramebuffer {
|
||||
fn set_mode(&self, _mode: DisplayMode) -> Result<(), Errno> {
|
||||
Err(Errno::InvalidOperation)
|
||||
}
|
||||
|
||||
fn framebuffer(&self) -> Result<&FramebufferInfo, Errno> {
|
||||
if let Some(fb) = self.framebuffer.as_ref_option() {
|
||||
Ok(fb)
|
||||
} else {
|
||||
Err(Errno::InvalidOperation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticFramebuffer {
|
||||
/// Constructs an empty [StaticFramebuffer] object
|
||||
pub const fn uninit() -> Self {
|
||||
Self { framebuffer: InitOnce::new() }
|
||||
}
|
||||
|
||||
/// Initializes the device from existing framebuffer object
|
||||
pub fn set_framebuffer(&self, framebuffer: FramebufferInfo) {
|
||||
self.framebuffer.init(framebuffer);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use libsys::error::Errno;
|
||||
|
||||
/// Token to indicate the local core is running in IRQ context
|
||||
pub struct IrqContext<'irq_context> {
|
||||
token: usize,
|
||||
_0: PhantomData<&'irq_context ()>,
|
||||
}
|
||||
|
||||
@@ -45,7 +46,12 @@ impl<'q> IrqContext<'q> {
|
||||
///
|
||||
/// Only allowed to be constructed in top-level IRQ handlers
|
||||
#[inline(always)]
|
||||
pub unsafe fn new() -> Self {
|
||||
Self { _0: PhantomData }
|
||||
pub unsafe fn new(token: usize) -> Self {
|
||||
Self { token, _0: PhantomData }
|
||||
}
|
||||
|
||||
/// Returns the value this object was initialized with
|
||||
pub const fn token(&self) -> usize {
|
||||
self.token
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
use libsys::error::Errno;
|
||||
|
||||
// Device classes
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod fdt;
|
||||
pub mod gpio;
|
||||
pub mod irq;
|
||||
pub mod display;
|
||||
pub mod pci;
|
||||
pub mod rtc;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub mod sd;
|
||||
pub mod serial;
|
||||
pub mod timer;
|
||||
pub mod pseudo;
|
||||
pub mod tty;
|
||||
|
||||
/// Generic device trait
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
//! Virtual (pseudo) device implemetation
|
||||
use crate::dev::Device;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use libsys::{error::Errno, ioctl::IoctlCmd};
|
||||
use vfs::CharDevice;
|
||||
|
||||
/// Pseudorandom number generator device
|
||||
pub struct Random {
|
||||
state: AtomicU32,
|
||||
}
|
||||
/// Zero device
|
||||
pub struct Zero;
|
||||
|
||||
impl Device for Random {
|
||||
fn name(&self) -> &'static str {
|
||||
"Pseudo-random device"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CharDevice for Random {
|
||||
fn read(&self, _blocking: bool, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
for byte in data.iter_mut() {
|
||||
*byte = self.read_single() as u8;
|
||||
}
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn write(&self, _blocking: bool, _data: &[u8]) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn is_ready(&self, _write: bool) -> Result<bool, Errno> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn ioctl(&self, _cmd: IoctlCmd, _ptr: usize, _lim: usize) -> Result<usize, Errno> {
|
||||
Err(Errno::InvalidArgument)
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for Zero {
|
||||
fn name(&self) -> &'static str {
|
||||
"Zero device"
|
||||
}
|
||||
|
||||
unsafe fn enable(&self) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl CharDevice for Zero {
|
||||
fn read(&self, _blocking: bool, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
data.fill(0);
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn write(&self, _blocking: bool, _data: &[u8]) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn is_ready(&self, _write: bool) -> Result<bool, Errno> {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn ioctl(&self, _cmd: IoctlCmd, _ptr: usize, _lim: usize) -> Result<usize, Errno> {
|
||||
Err(Errno::InvalidArgument)
|
||||
}
|
||||
}
|
||||
|
||||
impl Random {
|
||||
/// Initializes PRNG with a seed value
|
||||
pub fn set_state(&self, state: u32) {
|
||||
self.state.store(state, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Returns a single pseudo-random value
|
||||
pub fn read_single(&self) -> u32 {
|
||||
let mut x = self.state.load(Ordering::Acquire);
|
||||
x ^= x << 13;
|
||||
x ^= x >> 7;
|
||||
x ^= x << 17;
|
||||
self.state.store(x, Ordering::Release);
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
/// Pseudorandom number generator device
|
||||
pub static RANDOM: Random = Random {
|
||||
state: AtomicU32::new(0),
|
||||
};
|
||||
/// Zero device
|
||||
pub static ZERO: Zero = Zero;
|
||||
+39
-11
@@ -1,14 +1,19 @@
|
||||
//! Teletype (TTY) device facilities
|
||||
use crate::dev::serial::SerialDevice;
|
||||
use crate::proc::wait::{Wait, WAIT_SELECT};
|
||||
use crate::proc::{
|
||||
wait::{Wait, WAIT_SELECT},
|
||||
Process,
|
||||
};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use crate::syscall::arg;
|
||||
use core::mem::size_of;
|
||||
use libsys::error::Errno;
|
||||
use libsys::{
|
||||
ioctl::IoctlCmd,
|
||||
proc::Pid,
|
||||
signal::Signal,
|
||||
termios::{Termios, TermiosIflag, TermiosLflag, TermiosOflag},
|
||||
ioctl::IoctlCmd
|
||||
};
|
||||
use core::mem::size_of;
|
||||
use crate::syscall::arg::validate_user_ptr_struct;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CharRingInner<const N: usize> {
|
||||
@@ -16,6 +21,7 @@ struct CharRingInner<const N: usize> {
|
||||
wr: usize,
|
||||
data: [u8; N],
|
||||
flags: u8,
|
||||
fg_pgid: Option<Pid>,
|
||||
}
|
||||
|
||||
/// Ring buffer for TTYs
|
||||
@@ -31,6 +37,7 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
|
||||
/// Returns a reference to character device's ring buffer
|
||||
fn ring(&self) -> &CharRing<N>;
|
||||
|
||||
/// Returns `true` if the TTY is ready for an operation
|
||||
fn is_ready(&self, write: bool) -> Result<bool, Errno> {
|
||||
let ring = self.ring();
|
||||
if write {
|
||||
@@ -45,16 +52,21 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
|
||||
match cmd {
|
||||
IoctlCmd::TtyGetAttributes => {
|
||||
// TODO validate size
|
||||
let res = validate_user_ptr_struct::<Termios>(ptr)?;
|
||||
let res = arg::struct_mut::<Termios>(ptr)?;
|
||||
*res = self.ring().config.lock().clone();
|
||||
Ok(size_of::<Termios>())
|
||||
},
|
||||
}
|
||||
IoctlCmd::TtySetAttributes => {
|
||||
let src = validate_user_ptr_struct::<Termios>(ptr)?;
|
||||
let src = arg::struct_ref::<Termios>(ptr)?;
|
||||
*self.ring().config.lock() = src.clone();
|
||||
Ok(size_of::<Termios>())
|
||||
},
|
||||
_ => Err(Errno::InvalidArgument)
|
||||
}
|
||||
IoctlCmd::TtySetPgrp => {
|
||||
let src = arg::struct_ref::<u32>(ptr)?;
|
||||
self.ring().inner.lock().fg_pgid = Some(Pid::try_from(*src)?);
|
||||
Ok(0)
|
||||
}
|
||||
_ => Err(Errno::InvalidArgument),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +122,20 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
|
||||
}
|
||||
}
|
||||
|
||||
if byte == 0x3 && config.lflag.contains(TermiosLflag::ISIG) {
|
||||
drop(config);
|
||||
let pgid = ring.inner.lock().fg_pgid;
|
||||
if let Some(pgid) = pgid {
|
||||
// TODO send to pgid
|
||||
let proc = Process::get(pgid);
|
||||
if let Some(proc) = proc {
|
||||
// TODO
|
||||
proc.set_signal(Signal::Interrupt);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.ring().putc(byte, false).ok();
|
||||
}
|
||||
|
||||
@@ -232,17 +258,19 @@ impl<const N: usize> CharRing<N> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
inner: IrqSafeSpinLock::new(CharRingInner {
|
||||
fg_pgid: None,
|
||||
rd: 0,
|
||||
wr: 0,
|
||||
data: [0; N],
|
||||
flags: 0,
|
||||
}),
|
||||
config: IrqSafeSpinLock::new(Termios::new()),
|
||||
wait_read: Wait::new(),
|
||||
wait_write: Wait::new(),
|
||||
wait_read: Wait::new("tty_read"),
|
||||
wait_write: Wait::new("tty_write"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a character/line is available for reception
|
||||
pub fn is_readable(&self) -> bool {
|
||||
let inner = self.inner.lock();
|
||||
let config = self.config.lock();
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
//! Text drawing routines and font support
|
||||
|
||||
use crate::util::InitOnce;
|
||||
use libsys::mem::read_le32;
|
||||
use crate::dev::display::FramebufferInfo;
|
||||
|
||||
static FONT_DATA: &[u8] = include_bytes!("../../etc/default8x16.psfu");
|
||||
static FONT: InitOnce<Font> = InitOnce::new();
|
||||
|
||||
/// Font data description structure
|
||||
pub struct Font {
|
||||
char_width: usize,
|
||||
char_height: usize,
|
||||
bytes_per_glyph: usize,
|
||||
data: &'static [u8],
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Renders a glyph onto the framebuffer
|
||||
pub fn draw(&self, fb: &FramebufferInfo, bx: usize, by: usize, ch: char, fg: u32, bg: u32) {
|
||||
if (' ' .. '\x7B').contains(&ch) {
|
||||
let char_data = &self.data[ch as usize * self.bytes_per_glyph..];
|
||||
|
||||
for iy in 0..self.char_height {
|
||||
for ix in 0..self.char_width {
|
||||
let cx = self.char_width - ix - 1;
|
||||
let ptr = fb.virt_base + (ix + bx + (iy + by) * fb.width) * 4;
|
||||
let value = if char_data[iy + (cx) / 8] & (1 << (cx & 0x7)) != 0 {
|
||||
fg
|
||||
} else {
|
||||
bg
|
||||
};
|
||||
unsafe { core::ptr::write_volatile(ptr as *mut u32, value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the global [Font] object from PSF
|
||||
pub fn init() {
|
||||
assert_eq!(read_le32(FONT_DATA), 0x864ab572);
|
||||
|
||||
FONT.init(Font {
|
||||
char_width: read_le32(&FONT_DATA[28..]) as usize,
|
||||
char_height: read_le32(&FONT_DATA[24..]) as usize,
|
||||
bytes_per_glyph: read_le32(&FONT_DATA[20..]) as usize,
|
||||
data: &FONT_DATA[32..]
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns a reference to global [Font] object
|
||||
pub fn get() -> &'static Font {
|
||||
FONT.get()
|
||||
}
|
||||
+19
-8
@@ -1,9 +1,10 @@
|
||||
//! Device list pseudo-filesystem
|
||||
use crate::util::InitOnce;
|
||||
use alloc::boxed::Box;
|
||||
use core::cell::RefCell;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use libsys::error::Errno;
|
||||
use vfs::{CharDevice, CharDeviceWrapper, Vnode, VnodeKind, VnodeRef};
|
||||
use libsys::{error::Errno, stat::FileMode};
|
||||
use vfs::CharDevice;
|
||||
use vfs::{Vnode, VnodeData, VnodeRef};
|
||||
|
||||
/// Possible character device kinds
|
||||
#[derive(Debug)]
|
||||
@@ -16,7 +17,14 @@ static DEVFS_ROOT: InitOnce<VnodeRef> = InitOnce::new();
|
||||
|
||||
/// Initializes devfs
|
||||
pub fn init() {
|
||||
DEVFS_ROOT.init(Vnode::new("", VnodeKind::Directory, 0));
|
||||
let node = Vnode::new(
|
||||
"",
|
||||
VnodeData::Directory(RefCell::new(None)),
|
||||
Vnode::CACHE_READDIR | Vnode::CACHE_STAT,
|
||||
);
|
||||
// let node = Vnode::new("", VnodeKind::Directory, Vnode::CACHE_READDIR | Vnode::CACHE_STAT);
|
||||
node.props_mut().mode = FileMode::default_dir();
|
||||
DEVFS_ROOT.init(node);
|
||||
}
|
||||
|
||||
/// Returns devfs root node reference
|
||||
@@ -24,11 +32,14 @@ pub fn root() -> &'static VnodeRef {
|
||||
DEVFS_ROOT.get()
|
||||
}
|
||||
|
||||
fn _add_char_device(dev: &'static dyn CharDevice, name: &str) -> Result<(), Errno> {
|
||||
/// Adds device `dev` to devfs with `name`
|
||||
pub fn add_named_char_device(dev: &'static dyn CharDevice, name: &str) -> Result<(), Errno> {
|
||||
infoln!("Add char device: {}", name);
|
||||
|
||||
let node = Vnode::new(name, VnodeKind::Char, 0);
|
||||
node.set_data(Box::new(CharDeviceWrapper::new(dev)));
|
||||
let node = Vnode::new(name, VnodeData::Char(dev), Vnode::CACHE_STAT);
|
||||
// let node = Vnode::new(name, VnodeKind::Char, Vnode::CACHE_STAT);
|
||||
// node.set_data(Box::new(CharDeviceWrapper::new(dev)));
|
||||
node.props_mut().mode = FileMode::from_bits(0o600).unwrap() | FileMode::S_IFCHR;
|
||||
|
||||
DEVFS_ROOT.get().attach(node);
|
||||
|
||||
@@ -53,5 +64,5 @@ pub fn add_char_device(dev: &'static dyn CharDevice, kind: CharDeviceType) -> Re
|
||||
|
||||
let name = core::str::from_utf8(&buf[..=prefix.len()]).map_err(|_| Errno::InvalidArgument)?;
|
||||
|
||||
_add_char_device(dev, name)
|
||||
add_named_char_device(dev, name)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@ use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
};
|
||||
use libsys::{error::Errno, stat::MountOptions};
|
||||
use memfs::BlockAllocator;
|
||||
use vfs::VnodeRef;
|
||||
|
||||
pub mod devfs;
|
||||
pub mod sysfs;
|
||||
|
||||
/// Allocator implementation for memfs
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -25,3 +28,14 @@ unsafe impl BlockAllocator for MemfsBlockAlloc {
|
||||
phys::free_page(phys).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a filesystem instance based on `options`
|
||||
pub fn create_filesystem(options: &MountOptions) -> Result<VnodeRef, Errno> {
|
||||
let fs_name = options.fs.unwrap();
|
||||
|
||||
match fs_name {
|
||||
"devfs" => Ok(devfs::root().clone()),
|
||||
"sysfs" => Ok(sysfs::root().clone()),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
//! System control/info virtual filesystem
|
||||
use crate::debug::{self, Level};
|
||||
use crate::util::InitOnce;
|
||||
use alloc::boxed::Box;
|
||||
use core::cell::RefCell;
|
||||
use core::fmt::{self, Write};
|
||||
use core::str::FromStr;
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
stat::{FileMode, OpenFlags, Stat},
|
||||
};
|
||||
use vfs::{Vnode, VnodeCommon, VnodeData, VnodeFile, VnodeRef};
|
||||
|
||||
struct NodeData<R: Fn(&mut [u8]) -> Result<usize, Errno>, W: Fn(&[u8]) -> Result<usize, Errno>> {
|
||||
read_func: R,
|
||||
write_func: W,
|
||||
}
|
||||
|
||||
struct BufferWriter<'a> {
|
||||
dst: &'a mut [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Write for BufferWriter<'a> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for byte in s.bytes() {
|
||||
if self.pos == self.dst.len() {
|
||||
todo!();
|
||||
}
|
||||
self.dst[self.pos] = byte;
|
||||
self.pos += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BufferWriter<'a> {
|
||||
pub const fn new(dst: &'a mut [u8]) -> Self {
|
||||
Self { dst, pos: 0 }
|
||||
}
|
||||
|
||||
pub const fn count(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Fn(&mut [u8]) -> Result<usize, Errno>, W: Fn(&[u8]) -> Result<usize, Errno>> VnodeCommon
|
||||
for NodeData<R, W>
|
||||
{
|
||||
fn open(&mut self, _node: VnodeRef, _mode: OpenFlags) -> Result<usize, Errno> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn close(&mut self, _node: VnodeRef) -> Result<(), Errno> {
|
||||
Ok(())
|
||||
}
|
||||
/// Performs filetype-specific request
|
||||
fn ioctl(
|
||||
&mut self,
|
||||
_node: VnodeRef,
|
||||
_cmd: IoctlCmd,
|
||||
_ptr: usize,
|
||||
_len: usize,
|
||||
) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Retrieves file status
|
||||
fn stat(&mut self, _node: VnodeRef) -> Result<Stat, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Reports the size of this filesystem object in bytes
|
||||
fn size(&mut self, _node: VnodeRef) -> Result<usize, Errno> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Returns `true` if node is ready for an operation
|
||||
fn ready(&mut self, _node: VnodeRef, _write: bool) -> Result<bool, Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Fn(&mut [u8]) -> Result<usize, Errno>, W: Fn(&[u8]) -> Result<usize, Errno>> VnodeFile
|
||||
for NodeData<R, W>
|
||||
{
|
||||
fn read(&mut self, _node: VnodeRef, pos: usize, data: &mut [u8]) -> Result<usize, Errno> {
|
||||
if pos != 0 {
|
||||
// TODO handle this
|
||||
Ok(0)
|
||||
} else {
|
||||
(self.read_func)(data)
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _node: VnodeRef, pos: usize, data: &[u8]) -> Result<usize, Errno> {
|
||||
if pos != 0 {
|
||||
todo!();
|
||||
}
|
||||
(self.write_func)(data)
|
||||
}
|
||||
|
||||
fn truncate(&mut self, _node: VnodeRef, _size: usize) -> Result<(), Errno> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
impl<R: Fn(&mut [u8]) -> Result<usize, Errno>, W: Fn(&[u8]) -> Result<usize, Errno>>
|
||||
NodeData<R, W>
|
||||
{
|
||||
pub const fn new(read_func: R, write_func: W) -> Self {
|
||||
Self {
|
||||
read_func,
|
||||
write_func,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SYSFS_ROOT: InitOnce<VnodeRef> = InitOnce::new();
|
||||
|
||||
// TODO subdirs
|
||||
fn add_generic_node<R, W>(parent: Option<VnodeRef>, name: &str, mode: FileMode, read: R, write: W)
|
||||
where
|
||||
R: Fn(&mut [u8]) -> Result<usize, Errno> + 'static,
|
||||
W: Fn(&[u8]) -> Result<usize, Errno> + 'static,
|
||||
{
|
||||
let node = Vnode::new(
|
||||
name,
|
||||
VnodeData::File(RefCell::new(Some(Box::new(NodeData::new(read, write))))),
|
||||
Vnode::CACHE_STAT,
|
||||
);
|
||||
node.props_mut().mode = mode | FileMode::S_IFREG;
|
||||
|
||||
if let Some(parent) = parent {
|
||||
parent.attach(node);
|
||||
} else {
|
||||
SYSFS_ROOT.get().attach(node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a node with and `read` and `write` operations
|
||||
pub fn add_read_write_node<R, W>(parent: Option<VnodeRef>, name: &str, read: R, write: W)
|
||||
where
|
||||
R: Fn(&mut [u8]) -> Result<usize, Errno> + 'static,
|
||||
W: Fn(&[u8]) -> Result<usize, Errno> + 'static,
|
||||
{
|
||||
add_generic_node(
|
||||
parent,
|
||||
name,
|
||||
FileMode::from_bits(0o600).unwrap(),
|
||||
read,
|
||||
write,
|
||||
)
|
||||
}
|
||||
|
||||
/// Adds `read`-only node
|
||||
pub fn add_read_node<R>(parent: Option<VnodeRef>, name: &str, read: R)
|
||||
where
|
||||
R: Fn(&mut [u8]) -> Result<usize, Errno> + 'static,
|
||||
{
|
||||
add_generic_node(
|
||||
parent,
|
||||
name,
|
||||
FileMode::from_bits(0o400).unwrap(),
|
||||
read,
|
||||
|_| Err(Errno::ReadOnly),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a directory in sysfs structure
|
||||
pub fn add_directory(parent: Option<VnodeRef>, name: &str) -> Result<VnodeRef, Errno> {
|
||||
let node = Vnode::new(
|
||||
name,
|
||||
VnodeData::Directory(RefCell::new(None)),
|
||||
Vnode::CACHE_READDIR | Vnode::CACHE_STAT,
|
||||
);
|
||||
node.props_mut().mode = FileMode::from_bits(0o500).unwrap() | FileMode::S_IFDIR;
|
||||
|
||||
if let Some(parent) = parent {
|
||||
parent.attach(node.clone());
|
||||
} else {
|
||||
SYSFS_ROOT.get().attach(node.clone());
|
||||
}
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
/// Returns sysfs root node reference
|
||||
pub fn root() -> &'static VnodeRef {
|
||||
SYSFS_ROOT.get()
|
||||
}
|
||||
|
||||
/// Sets up the sysfs tree
|
||||
pub fn init() {
|
||||
let node = Vnode::new(
|
||||
"",
|
||||
VnodeData::Directory(RefCell::new(None)),
|
||||
Vnode::CACHE_READDIR | Vnode::CACHE_STAT,
|
||||
);
|
||||
node.props_mut().mode = FileMode::default_dir();
|
||||
SYSFS_ROOT.init(node);
|
||||
|
||||
let debug_dir = add_directory(None, "debug").unwrap();
|
||||
|
||||
add_read_write_node(
|
||||
Some(debug_dir),
|
||||
"level",
|
||||
|buf| {
|
||||
let mut writer = BufferWriter::new(buf);
|
||||
writeln!(&mut writer, "{}", debug::LEVEL as u32).map_err(|_| Errno::InvalidArgument)?;
|
||||
Ok(writer.count())
|
||||
},
|
||||
|buf| {
|
||||
let s = core::str::from_utf8(buf).map_err(|_| Errno::InvalidArgument)?;
|
||||
let _value = u32::from_str(s)
|
||||
.map_err(|_| Errno::InvalidArgument)
|
||||
.and_then(Level::try_from)?;
|
||||
todo!()
|
||||
},
|
||||
);
|
||||
|
||||
add_read_node(None, "uptime", |buf| {
|
||||
use crate::arch::machine;
|
||||
use crate::dev::timer::TimestampSource;
|
||||
|
||||
let mut writer = BufferWriter::new(buf);
|
||||
let time = machine::local_timer().timestamp()?;
|
||||
writeln!(&mut writer, "{} {}", time.as_secs(), time.subsec_nanos())
|
||||
.map_err(|_| Errno::InvalidArgument)?;
|
||||
Ok(writer.count())
|
||||
});
|
||||
}
|
||||
+4
-3
@@ -4,7 +4,7 @@ use crate::config::{ConfigKey, CONFIG};
|
||||
use crate::fs::{devfs, MemfsBlockAlloc};
|
||||
use crate::mem;
|
||||
use crate::proc::{elf, Process};
|
||||
use libsys::stat::{FileDescriptor, OpenFlags};
|
||||
use libsys::stat::{FileDescriptor, GroupId, OpenFlags, UserId};
|
||||
use memfs::Ramfs;
|
||||
use vfs::{Filesystem, Ioctx};
|
||||
|
||||
@@ -29,7 +29,7 @@ pub extern "C" fn init_fn(_arg: usize) -> ! {
|
||||
unsafe { Ramfs::open(initrd_start as *mut u8, initrd_size, MemfsBlockAlloc {}).unwrap() };
|
||||
let root = fs.root().unwrap();
|
||||
|
||||
let ioctx = Ioctx::new(root);
|
||||
let ioctx = Ioctx::new(root, UserId::root(), GroupId::root());
|
||||
|
||||
let node = ioctx.find(None, "/init", true).unwrap();
|
||||
let file = node.open(OpenFlags::O_RDONLY | OpenFlags::O_EXEC).unwrap();
|
||||
@@ -54,10 +54,11 @@ pub extern "C" fn init_fn(_arg: usize) -> ! {
|
||||
io.set_file(FileDescriptor::STDIN, stdin).unwrap();
|
||||
io.set_file(FileDescriptor::STDOUT, stdout).unwrap();
|
||||
io.set_file(FileDescriptor::STDERR, stderr).unwrap();
|
||||
io.set_ctty(tty_node);
|
||||
}
|
||||
|
||||
drop(cfg);
|
||||
|
||||
Process::execve(|space| elf::load_elf(space, file), 0).unwrap();
|
||||
Process::execve(|space| elf::load_elf(space, file), &["/init"]).unwrap();
|
||||
panic!("Unreachable");
|
||||
}
|
||||
|
||||
+7
-8
@@ -1,18 +1,16 @@
|
||||
//! osdve5 crate (lol)
|
||||
#![feature(
|
||||
asm,
|
||||
global_asm,
|
||||
const_for,
|
||||
const_mut_refs,
|
||||
const_raw_ptr_deref,
|
||||
const_fn_fn_ptr_basics,
|
||||
const_fn_trait_bound,
|
||||
const_trait_impl,
|
||||
const_panic,
|
||||
const_btree_new,
|
||||
linked_list_cursors,
|
||||
panic_info_message,
|
||||
alloc_error_handler,
|
||||
linked_list_cursors,
|
||||
const_btree_new
|
||||
asm_const,
|
||||
core_intrinsics,
|
||||
)]
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
@@ -28,11 +26,12 @@ extern crate alloc;
|
||||
|
||||
#[macro_use]
|
||||
pub mod debug;
|
||||
|
||||
//
|
||||
pub mod arch;
|
||||
pub mod config;
|
||||
pub mod dev;
|
||||
pub mod fs;
|
||||
pub mod font;
|
||||
pub mod init;
|
||||
pub mod mem;
|
||||
pub mod proc;
|
||||
@@ -43,7 +42,7 @@ pub mod util;
|
||||
#[panic_handler]
|
||||
fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
unsafe {
|
||||
asm!("msr daifset, #2");
|
||||
arch::intrin::irq_disable();
|
||||
}
|
||||
|
||||
errorln!("Panic: {:?}", pi);
|
||||
|
||||
@@ -59,7 +59,7 @@ static HEAP: InitOnce<IrqSafeSpinLock<Heap>> = InitOnce::new();
|
||||
pub unsafe fn init(base: usize, size: usize) {
|
||||
let heap = Heap { base, size, ptr: 0 };
|
||||
|
||||
infoln!("Kernel heap: {:#x}..{:#x}", base, base + size);
|
||||
// infoln!("Kernel heap: {:#x}..{:#x}", base, base + size);
|
||||
|
||||
HEAP.init(IrqSafeSpinLock::new(heap));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,16 @@ pub mod heap;
|
||||
pub mod phys;
|
||||
pub mod virt;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_arch = "x86_64")] {
|
||||
/// 8-byte padding for x86_64 for user stack alignment
|
||||
pub const USTACK_PADDING: usize = 8;
|
||||
} else {
|
||||
/// No stack alignment required
|
||||
pub const USTACK_PADDING: usize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtual offset applied to kernel address space
|
||||
pub const KERNEL_OFFSET: usize = 0xFFFFFF8000000000;
|
||||
|
||||
@@ -26,3 +36,20 @@ pub fn kernel_end_phys() -> usize {
|
||||
}
|
||||
unsafe { &__kernel_end as *const _ as usize - KERNEL_OFFSET }
|
||||
}
|
||||
|
||||
// TODO cross-platform variant
|
||||
/// Returns `true` if `virt` address is accessible for requested operation
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[inline(always)]
|
||||
pub fn is_el0_accessible(virt: usize, write: bool) -> bool {
|
||||
use core::arch::asm;
|
||||
let mut res: usize;
|
||||
unsafe {
|
||||
if write {
|
||||
asm!("at s1e0w, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
|
||||
} else {
|
||||
asm!("at s1e0r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
|
||||
}
|
||||
}
|
||||
res & 1 == 0
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use super::{PageInfo, PageUsage, PageStatistics};
|
||||
use super::{PageInfo, PageStatistics, PageUsage};
|
||||
use crate::mem::{virtualize, PAGE_SIZE};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use core::mem;
|
||||
use libsys::{error::Errno, mem::memcpy};
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe to implement because of direct memory manipulation
|
||||
pub unsafe trait Manager {
|
||||
fn alloc_page(&mut self, pu: PageUsage) -> Result<usize, Errno>;
|
||||
fn alloc_contiguous_pages(&mut self, pu: PageUsage, count: usize) -> Result<usize, Errno>;
|
||||
@@ -17,6 +20,7 @@ pub struct SimpleManager {
|
||||
pages: &'static mut [PageInfo],
|
||||
stats: PageStatistics,
|
||||
base_index: usize,
|
||||
last_index: usize,
|
||||
}
|
||||
impl SimpleManager {
|
||||
pub(super) unsafe fn initialize(base: usize, at: usize, count: usize) -> Self {
|
||||
@@ -34,13 +38,14 @@ impl SimpleManager {
|
||||
}
|
||||
Self {
|
||||
base_index: base / PAGE_SIZE,
|
||||
last_index: 0,
|
||||
stats: PageStatistics {
|
||||
available: 0,
|
||||
kernel: 0,
|
||||
kernel_heap: 0,
|
||||
paging: 0,
|
||||
user_private: 0,
|
||||
filesystem: 0
|
||||
filesystem: 0,
|
||||
},
|
||||
pages,
|
||||
}
|
||||
@@ -57,11 +62,21 @@ impl SimpleManager {
|
||||
}
|
||||
|
||||
fn alloc_single_index(&mut self, pu: PageUsage) -> Result<usize, Errno> {
|
||||
for index in 0..self.pages.len() {
|
||||
for index in self.last_index..self.pages.len() {
|
||||
let page = &mut self.pages[index];
|
||||
if page.usage == PageUsage::Available {
|
||||
page.usage = pu;
|
||||
page.refcount = 1;
|
||||
self.last_index = index;
|
||||
return Ok(index);
|
||||
}
|
||||
}
|
||||
for index in 0..self.last_index {
|
||||
let page = &mut self.pages[index];
|
||||
if page.usage == PageUsage::Available {
|
||||
page.usage = pu;
|
||||
page.refcount = 1;
|
||||
self.last_index = index;
|
||||
return Ok(index);
|
||||
}
|
||||
}
|
||||
@@ -81,22 +96,23 @@ impl SimpleManager {
|
||||
self.stats.available -= count;
|
||||
}
|
||||
|
||||
fn update_stats_free(&mut self, pu: PageUsage, count: usize) {
|
||||
let field = match pu {
|
||||
PageUsage::Kernel => &mut self.stats.kernel,
|
||||
PageUsage::KernelHeap => &mut self.stats.kernel_heap,
|
||||
PageUsage::Paging => &mut self.stats.paging,
|
||||
PageUsage::UserPrivate => &mut self.stats.user_private,
|
||||
PageUsage::Filesystem => &mut self.stats.filesystem,
|
||||
_ => panic!("TODO {:?}", pu),
|
||||
};
|
||||
*field -= count;
|
||||
self.stats.available += count;
|
||||
}
|
||||
// fn update_stats_free(&mut self, pu: PageUsage, count: usize) {
|
||||
// let field = match pu {
|
||||
// PageUsage::Kernel => &mut self.stats.kernel,
|
||||
// PageUsage::KernelHeap => &mut self.stats.kernel_heap,
|
||||
// PageUsage::Paging => &mut self.stats.paging,
|
||||
// PageUsage::UserPrivate => &mut self.stats.user_private,
|
||||
// PageUsage::Filesystem => &mut self.stats.filesystem,
|
||||
// _ => panic!("TODO {:?}", pu),
|
||||
// };
|
||||
// *field -= count;
|
||||
// self.stats.available += count;
|
||||
// }
|
||||
}
|
||||
unsafe impl Manager for SimpleManager {
|
||||
fn alloc_page(&mut self, pu: PageUsage) -> Result<usize, Errno> {
|
||||
let res = self.alloc_single_index(pu)
|
||||
let res = self
|
||||
.alloc_single_index(pu)
|
||||
.map(|r| (self.base_index + r) * PAGE_SIZE);
|
||||
if res.is_ok() {
|
||||
self.update_stats_alloc(pu, 1);
|
||||
@@ -122,23 +138,21 @@ unsafe impl Manager for SimpleManager {
|
||||
Err(Errno::OutOfMemory)
|
||||
}
|
||||
fn free_page(&mut self, addr: usize) -> Result<(), Errno> {
|
||||
let usage = {
|
||||
let index = self.page_index(addr);
|
||||
let page = &mut self.pages[index];
|
||||
let index = self.page_index(addr);
|
||||
let page = &mut self.pages[index];
|
||||
|
||||
let usage = page.usage;
|
||||
assert!(page.usage != PageUsage::Reserved && page.usage != PageUsage::Available);
|
||||
assert!(page.usage != PageUsage::Reserved && page.usage != PageUsage::Available);
|
||||
|
||||
if page.refcount > 1 {
|
||||
page.refcount -= 1;
|
||||
} else {
|
||||
assert_eq!(page.refcount, 1);
|
||||
page.usage = PageUsage::Available;
|
||||
page.refcount = 0;
|
||||
}
|
||||
if page.refcount > 1 {
|
||||
page.refcount -= 1;
|
||||
} else {
|
||||
assert_eq!(page.refcount, 1);
|
||||
page.usage = PageUsage::Available;
|
||||
page.refcount = 0;
|
||||
|
||||
self.last_index = index;
|
||||
}
|
||||
|
||||
usage
|
||||
};
|
||||
// FIXME
|
||||
// self.update_stats_free(usage, 1);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ mod manager;
|
||||
mod reserved;
|
||||
|
||||
use manager::{Manager, SimpleManager, MANAGER};
|
||||
pub use reserved::ReservedRegion;
|
||||
pub use reserved::{ReservedRegion, reserve};
|
||||
|
||||
type ManagerImpl = SimpleManager;
|
||||
|
||||
@@ -213,7 +213,10 @@ pub unsafe fn init_from_iter<T: Iterator<Item = MemoryRegion> + Clone>(iter: T)
|
||||
// Step 1. Count available memory
|
||||
let mut total_pages = 0usize;
|
||||
for reg in iter.clone() {
|
||||
total_pages += (reg.end - reg.start) / PAGE_SIZE;
|
||||
let upper = (reg.end - mem_base) / PAGE_SIZE;
|
||||
if upper > total_pages {
|
||||
total_pages = upper;
|
||||
}
|
||||
}
|
||||
// TODO maybe instead of size_of::<...> use Layout?
|
||||
let need_pages = ((total_pages * size_of::<PageInfo>()) + 0xFFF) / 0x1000;
|
||||
|
||||
+42
-18
@@ -2,18 +2,13 @@
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Deref;
|
||||
use cortex_a::asm::barrier::{self, dsb, isb};
|
||||
use cortex_a::registers::TTBR0_EL1;
|
||||
use libsys::error::Errno;
|
||||
use tock_registers::interfaces::Writeable;
|
||||
use libsys::{mem::memcpy, error::Errno};
|
||||
use crate::mem::{self, phys::{self, PageUsage}};
|
||||
|
||||
pub mod table;
|
||||
pub use table::{Entry, MapAttributes, Space, Table};
|
||||
pub mod fixed;
|
||||
pub use fixed::FixedTableGroup;
|
||||
use crate::arch::platform::virt as virt_impl;
|
||||
|
||||
#[no_mangle]
|
||||
static mut KERNEL_TTBR1: FixedTableGroup = FixedTableGroup::empty();
|
||||
use table::{Space, SpaceImpl, MapAttributes};
|
||||
|
||||
/// Structure representing a region of memory used for MMIO/device access
|
||||
// TODO: this shouldn't be trivially-cloneable and should instead incorporate
|
||||
@@ -45,7 +40,7 @@ impl DeviceMemory {
|
||||
///
|
||||
/// See [FixedTableGroup::map_region]
|
||||
pub fn map(name: &'static str, phys: usize, count: usize) -> Result<Self, Errno> {
|
||||
let base = unsafe { KERNEL_TTBR1.map_region(phys, count) }?;
|
||||
let base = virt_impl::map_device_memory(phys, count)?;
|
||||
debugln!(
|
||||
"Mapping {:#x}..{:#x} -> {:#x} for {:?}",
|
||||
base,
|
||||
@@ -91,15 +86,44 @@ impl<T> Deref for DeviceMemoryIo<T> {
|
||||
/// identity-mapped translation
|
||||
pub fn enable() -> Result<(), Errno> {
|
||||
unsafe {
|
||||
KERNEL_TTBR1.init_device_map();
|
||||
|
||||
dsb(barrier::ISH);
|
||||
isb(barrier::SY);
|
||||
virt_impl::enable();
|
||||
}
|
||||
|
||||
// Disable lower-half translation
|
||||
TTBR0_EL1.set(0);
|
||||
//TCR_EL1.modify(TCR_EL1::EPD0::SET);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a [Copy]able object to `dst` address in `space`. Will allocate any affected pages.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: arbitrary memory write.
|
||||
pub unsafe fn write_paged<T: Clone + Copy>(space: &mut SpaceImpl, dst: usize, src: T) -> Result<(), Errno> {
|
||||
write_paged_bytes(space, dst, core::slice::from_raw_parts(&src as *const _ as *const u8, core::mem::size_of::<T>()))
|
||||
}
|
||||
|
||||
/// Writes a byte slice to `dst` address in `space`. Will allocate any affected pages.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: arbitrary memory write.
|
||||
pub unsafe fn write_paged_bytes(space: &mut SpaceImpl, dst: usize, src: &[u8]) -> Result<(), Errno> {
|
||||
if (src.len() + (dst % 4096)) > 4096 {
|
||||
todo!("Object crossed page boundary");
|
||||
}
|
||||
let page_virt = dst & !4095;
|
||||
let page_phys = if let Ok(phys) = space.translate(dst) {
|
||||
phys
|
||||
} else {
|
||||
let page = phys::alloc_page(PageUsage::UserPrivate)?;
|
||||
let flags = MapAttributes::SHARE_OUTER | MapAttributes::USER_READ;
|
||||
space.map(page_virt, page, flags)?;
|
||||
page
|
||||
};
|
||||
|
||||
memcpy(
|
||||
(mem::virtualize(page_phys) + (dst % 4096)) as *mut u8,
|
||||
src.as_ptr(),
|
||||
src.len(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+137
-280
@@ -1,234 +1,122 @@
|
||||
//! Translation table manipulation facilities
|
||||
|
||||
use crate::arch::platform::virt as virt_impl;
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
};
|
||||
use core::ops::{Index, IndexMut};
|
||||
use libsys::{error::Errno, mem::memset};
|
||||
|
||||
/// Transparent wrapper structure representing a single
|
||||
/// translation table entry
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct Entry(u64);
|
||||
|
||||
/// Structure describing a single level of translation mappings
|
||||
#[repr(C, align(0x1000))]
|
||||
pub struct Table {
|
||||
entries: [Entry; 512],
|
||||
}
|
||||
|
||||
/// Wrapper for top-most level of address translation tables
|
||||
#[repr(transparent)]
|
||||
pub struct Space(Table);
|
||||
use core::ffi::c_void;
|
||||
use libsys::error::Errno;
|
||||
pub use virt_impl::{EntryImpl, SpaceImpl};
|
||||
|
||||
bitflags! {
|
||||
/// Attributes attached to each translation [Entry]
|
||||
/// Virtual space entry attributes
|
||||
pub struct MapAttributes: u64 {
|
||||
// TODO use 2 lower bits to determine mapping size?
|
||||
/// nG bit -- determines whether a TLB entry associated with this mapping
|
||||
/// applies only to current ASID or all ASIDs.
|
||||
const NOT_GLOBAL = 1 << 11;
|
||||
/// AF bit -- must be set by software, otherwise Access Error exception is
|
||||
/// generated when the page is accessed
|
||||
const ACCESS = 1 << 10;
|
||||
/// The memory region is outer-shareable
|
||||
const SH_OUTER = 2 << 8;
|
||||
/// This page is used for device-MMIO mapping and uses MAIR attribute #1
|
||||
const DEVICE = 1 << 2;
|
||||
/// Entry is readable by user threads
|
||||
const USER_READ = 1 << 0;
|
||||
/// Entry is writable by user threads
|
||||
const USER_WRITE = 1 << 1;
|
||||
/// Data from entry can be executed by user threads
|
||||
const USER_EXEC = 1 << 2;
|
||||
|
||||
/// Pages marked with this bit are Copy-on-Write
|
||||
const EX_COW = 1 << 55;
|
||||
/// Entry is writable by kernel
|
||||
const KERNEL_WRITE = 1 << 3;
|
||||
/// Data from entry can be executed by kernel
|
||||
const KERNEL_EXEC = 1 << 4;
|
||||
|
||||
/// UXN bit -- if set, page may not be used for instruction fetching from EL0
|
||||
const UXN = 1 << 54;
|
||||
/// PXN bit -- if set, page may not be used for instruction fetching from EL1
|
||||
const PXN = 1 << 53;
|
||||
/// TODO TBD
|
||||
const SHARE_OUTER = 1 << 5;
|
||||
/// Memory is used for device interaction
|
||||
const DEVICE_MEMORY = 1 << 6;
|
||||
|
||||
// AP field
|
||||
// Default behavior is: read-write for EL1, no access for EL0
|
||||
/// If set, the page referred to by this entry is read-only for both EL0/EL1
|
||||
const AP_BOTH_READONLY = 3 << 6;
|
||||
/// If set, the page referred to by this entry is read-write for both EL0/EL1
|
||||
const AP_BOTH_READWRITE = 1 << 6;
|
||||
/// Entry is marked as Copy-on-Write
|
||||
const COPY_ON_WRITE = 1 << 7;
|
||||
|
||||
/// Access flag for entry
|
||||
const ACCESS = 1 << 8;
|
||||
/// Entry is global across virtual address spaces
|
||||
const GLOBAL = 1 << 9;
|
||||
}
|
||||
}
|
||||
|
||||
impl Table {
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// If `index` represents a `Block`-type mapping, will return an error.
|
||||
/// If `index` does not map to any translation table, will try to allocate, init and
|
||||
/// map a new one, returning it after doing so.
|
||||
pub fn next_level_table_or_alloc(&mut self, index: usize) -> Result<&'static mut Table, Errno> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_table() {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
/// Interface for a single element of paging mapping
|
||||
pub trait Entry: Clone + Copy {
|
||||
/// Platform-specific entry attribute representation
|
||||
type RawAttributes: From<MapAttributes> + Copy + Clone;
|
||||
/// Invalid entry with no association
|
||||
const EMPTY: Self;
|
||||
|
||||
Ok(unsafe { &mut *(mem::virtualize(entry.address_unchecked()) as *mut _) })
|
||||
} else {
|
||||
let phys = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(phys) as *mut Self) };
|
||||
self[index] = Entry::table(phys, MapAttributes::empty());
|
||||
res.entries.fill(Entry::invalid());
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
/// Constructs an entry pointing to next-level table or page
|
||||
fn normal(addr: usize, attrs: MapAttributes) -> Self;
|
||||
/// Constructs an entry pointing to a contiguous block
|
||||
fn block(addr: usize, attrs: MapAttributes) -> Self;
|
||||
|
||||
/// Returns next-level translation table reference for `index`, if one is present.
|
||||
/// Same as [next_level_table_or_alloc], but returns `None` if no table is mapped.
|
||||
pub fn next_level_table(&mut self, index: usize) -> Option<&'static mut Table> {
|
||||
let entry = self[index];
|
||||
if entry.is_present() {
|
||||
if !entry.is_table() {
|
||||
panic!("Entry is not a table: idx={}", index);
|
||||
}
|
||||
/// Returns physical address the entry points to
|
||||
fn address(self) -> usize;
|
||||
/// Changes the entry physical address
|
||||
fn set_address(&mut self, value: usize);
|
||||
|
||||
Some(unsafe { &mut *(mem::virtualize(entry.address_unchecked()) as *mut _) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// Marks page as CoW and removes user write ability
|
||||
fn fork_with_cow(&mut self) -> Self;
|
||||
/// Clones a CoW entry
|
||||
fn copy_from_cow(self, new_addr: usize) -> Self;
|
||||
|
||||
/// Constructs and fills a [Table] with non-present mappings
|
||||
pub const fn empty() -> Table {
|
||||
Table {
|
||||
entries: [Entry::invalid(); 512],
|
||||
}
|
||||
}
|
||||
/// Returns `true` if entry maps a paging element
|
||||
fn is_present(self) -> bool;
|
||||
/// Returns `true` if page is a 4KiB one
|
||||
fn is_normal(self) -> bool;
|
||||
/// Returns `true` if page is marked as Copy-on-Write
|
||||
fn is_cow(self) -> bool;
|
||||
/// Returns `true` if page is write-accessible for user threads
|
||||
fn is_user_writable(self) -> bool;
|
||||
}
|
||||
|
||||
impl Index<usize> for Table {
|
||||
type Output = Entry;
|
||||
/// Interface for virtual address space manipulation
|
||||
pub trait Space {
|
||||
/// Single table entry data type
|
||||
type Entry: Entry;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.entries[index]
|
||||
}
|
||||
}
|
||||
/// Creates an empty address space
|
||||
fn alloc_empty() -> Result<&'static mut Self, Errno>;
|
||||
|
||||
impl IndexMut<usize> for Table {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.entries[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
const PRESENT: u64 = 1 << 0;
|
||||
const TABLE: u64 = 1 << 1;
|
||||
const PHYS_MASK: u64 = 0x0000FFFFFFFFF000;
|
||||
|
||||
/// Constructs a single non-present mapping
|
||||
pub const fn invalid() -> Self {
|
||||
Self(0)
|
||||
}
|
||||
|
||||
/// Constructs a `Block`-type memory mapping
|
||||
pub const fn block(phys: usize, attrs: MapAttributes) -> Self {
|
||||
Self((phys as u64 & Self::PHYS_MASK) | attrs.bits() | Self::PRESENT)
|
||||
}
|
||||
|
||||
/// Constructs a `Table` or `Page`-type mapping depending on translation level
|
||||
/// this entry is used at
|
||||
pub const fn table(phys: usize, attrs: MapAttributes) -> Self {
|
||||
Self((phys as u64 & Self::PHYS_MASK) | attrs.bits() | Self::PRESENT | Self::TABLE)
|
||||
}
|
||||
|
||||
/// Returns `true` if this entry is not invalid
|
||||
pub const fn is_present(self) -> bool {
|
||||
self.0 & Self::PRESENT != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if this entry is a `Table` or `Page`-type mapping
|
||||
pub const fn is_table(self) -> bool {
|
||||
self.0 & Self::TABLE != 0
|
||||
}
|
||||
|
||||
/// Returns the target address of this translation entry.
|
||||
/// Removes all non-kernel entries from the space.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Does not check if the entry is actually valid.
|
||||
pub const unsafe fn address_unchecked(self) -> usize {
|
||||
(self.0 & Self::PHYS_MASK) as usize
|
||||
}
|
||||
/// Only safe to call on spaces not currently in use, otherwise will
|
||||
/// trigger undefined behavior and/or page fault.
|
||||
unsafe fn release(space: &'static mut Self);
|
||||
|
||||
unsafe fn set_address(&mut self, address: usize) {
|
||||
self.0 &= !Self::PHYS_MASK;
|
||||
self.0 |= (address as u64) & Self::PHYS_MASK;
|
||||
}
|
||||
/// Forks a process virtual memory space
|
||||
fn fork(&mut self) -> Result<&'static mut Self, Errno>;
|
||||
|
||||
unsafe fn fork_flags(self) -> MapAttributes {
|
||||
MapAttributes::from_bits_unchecked(self.0 & !Self::PHYS_MASK)
|
||||
}
|
||||
|
||||
fn set_cow(&mut self) {
|
||||
self.0 |= (MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW).bits();
|
||||
}
|
||||
|
||||
fn clear_cow(&mut self) {
|
||||
self.0 &= !(MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW).bits();
|
||||
self.0 |= MapAttributes::AP_BOTH_READWRITE.bits();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_cow(self) -> bool {
|
||||
let attrs = (MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW).bits();
|
||||
self.0 & attrs == attrs
|
||||
}
|
||||
}
|
||||
|
||||
impl Space {
|
||||
/// Creates a new virtual address space and fills it with [Entry::invalid()]
|
||||
/// mappings. Does physical memory page allocation.
|
||||
pub fn alloc_empty() -> Result<&'static mut Self, Errno> {
|
||||
let phys = phys::alloc_page(PageUsage::Paging)?;
|
||||
let res = unsafe { &mut *(mem::virtualize(phys) as *mut Self) };
|
||||
res.0.entries.fill(Entry::invalid());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Inserts a single `virt` -> `phys` translation entry to this address space.
|
||||
/// Writes an entry corresponding to `virt` address
|
||||
/// to last-level table of this address space.
|
||||
///
|
||||
/// TODO: only works with 4K-sized pages at this moment.
|
||||
pub fn map(&mut self, virt: usize, phys: usize, flags: MapAttributes) -> Result<(), Errno> {
|
||||
let l0i = virt >> 30;
|
||||
let l1i = (virt >> 21) & 0x1FF;
|
||||
let l2i = (virt >> 12) & 0x1FF;
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: arbitrary memory space manipulation.
|
||||
unsafe fn write_last_level(
|
||||
&mut self,
|
||||
virt: usize,
|
||||
entry: Self::Entry,
|
||||
create_intermediate: bool,
|
||||
overwrite: bool,
|
||||
) -> Result<(), Errno>;
|
||||
|
||||
let l1_table = self.0.next_level_table_or_alloc(l0i)?;
|
||||
let l2_table = l1_table.next_level_table_or_alloc(l1i)?;
|
||||
/// Reads an entry corresponding to `virt` address
|
||||
fn read_last_level(&self, virt: usize) -> Result<Self::Entry, Errno>;
|
||||
|
||||
if l2_table[l2i].is_present() {
|
||||
Err(Errno::AlreadyExists)
|
||||
} else {
|
||||
l2_table[l2i] = Entry::table(phys, flags | MapAttributes::ACCESS);
|
||||
#[cfg(feature = "verbose")]
|
||||
debugln!("{:#p} Map {:#x} -> {:#x}, {:?}", self, virt, phys, flags);
|
||||
Ok(())
|
||||
}
|
||||
/// Returns physical address of this table
|
||||
fn address_phys(&mut self) -> usize {
|
||||
self as *mut _ as *mut c_void as usize - mem::KERNEL_OFFSET
|
||||
}
|
||||
|
||||
/// Attempts to resolve a page fault at `virt` address by copying the
|
||||
/// underlying Copy-on-Write mapping (if any is present)
|
||||
pub fn try_cow_copy(&mut self, virt: usize) -> Result<(), Errno> {
|
||||
let virt = virt & !0xFFF;
|
||||
let l0i = virt >> 30;
|
||||
let l1i = (virt >> 21) & 0x1FF;
|
||||
let l2i = (virt >> 12) & 0x1FF;
|
||||
/// Performs Copy-on-Write cloning on page fault
|
||||
fn try_cow_copy(&mut self, virt: usize) -> Result<(), Errno> {
|
||||
let entry = self.read_last_level(virt)?;
|
||||
let src_phys = entry.address();
|
||||
|
||||
let l1_table = self.0.next_level_table(l0i).ok_or(Errno::DoesNotExist)?;
|
||||
let l2_table = l1_table.next_level_table(l1i).ok_or(Errno::DoesNotExist)?;
|
||||
|
||||
let entry = l2_table[l2i];
|
||||
|
||||
if !entry.is_present() {
|
||||
warnln!("Entry is not present: {:#x}", virt);
|
||||
return Err(Errno::DoesNotExist);
|
||||
}
|
||||
|
||||
let src_phys = unsafe { entry.address_unchecked() };
|
||||
if !entry.is_cow() {
|
||||
warnln!(
|
||||
"Entry is not marked as CoW: {:#x}, points to {:#x}",
|
||||
@@ -239,97 +127,66 @@ impl Space {
|
||||
}
|
||||
|
||||
let dst_phys = unsafe { phys::copy_cow_page(src_phys)? };
|
||||
unsafe {
|
||||
l2_table[l2i].set_address(dst_phys);
|
||||
}
|
||||
l2_table[l2i].clear_cow();
|
||||
|
||||
unsafe {
|
||||
self.write_last_level(virt, entry.copy_from_cow(dst_phys), false, true)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Performs a copy of the address space, cloning data owned by it
|
||||
pub fn fork(&mut self) -> Result<&'static mut Self, Errno> {
|
||||
let res = Self::alloc_empty()?;
|
||||
for l0i in 0..512 {
|
||||
if let Some(l1_table) = self.0.next_level_table(l0i) {
|
||||
for l1i in 0..512 {
|
||||
if let Some(l2_table) = l1_table.next_level_table(l1i) {
|
||||
for l2i in 0..512 {
|
||||
let entry = l2_table[l2i];
|
||||
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_table());
|
||||
let src_phys = unsafe { entry.address_unchecked() };
|
||||
let virt_addr = (l0i << 30) | (l1i << 21) | (l2i << 12);
|
||||
let dst_phys = unsafe { phys::fork_page(src_phys)? };
|
||||
|
||||
let mut flags = unsafe { entry.fork_flags() };
|
||||
if dst_phys != src_phys {
|
||||
todo!();
|
||||
// res.map(virt_addr, dst_phys, flags)?;
|
||||
} else {
|
||||
// TODO only apply CoW to writable pages
|
||||
flags |= MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW;
|
||||
l2_table[l2i].set_cow();
|
||||
unsafe {
|
||||
asm!("tlbi vaae1, {}", in(reg) virt_addr);
|
||||
}
|
||||
res.map(virt_addr, dst_phys, flags)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Creates a new virtual -> physical memory mapping. Will fail if one is
|
||||
/// already associated with given virtual address.
|
||||
fn map(&mut self, virt: usize, phys: usize, attrs: MapAttributes) -> Result<(), Errno> {
|
||||
#[cfg(feature = "verbose")]
|
||||
debugln!("Map {:#x} -> {:#x}, {:?}", virt, phys, attrs);
|
||||
unsafe {
|
||||
self.write_last_level(
|
||||
virt,
|
||||
Entry::normal(phys, attrs | MapAttributes::ACCESS),
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Releases all the mappings from the address space. Frees all
|
||||
/// memory pages referenced by this space as well as those used for
|
||||
/// its paging tables.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: may invalidate currently active address space
|
||||
pub unsafe fn release(space: &mut Self) {
|
||||
for l0i in 0..512 {
|
||||
let l0_entry = space.0[l0i];
|
||||
if !l0_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(l0_entry.is_table());
|
||||
let l1_table = &mut *(mem::virtualize(l0_entry.address_unchecked()) as *mut Table);
|
||||
|
||||
for l1i in 0..512 {
|
||||
let l1_entry = l1_table[l1i];
|
||||
if !l1_entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
assert!(l1_entry.is_table());
|
||||
let l2_table = &mut *(mem::virtualize(l1_entry.address_unchecked()) as *mut Table);
|
||||
|
||||
for l2i in 0..512 {
|
||||
let entry = l2_table[l2i];
|
||||
if !entry.is_present() {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert!(entry.is_table());
|
||||
phys::free_page(entry.address_unchecked()).unwrap();
|
||||
}
|
||||
phys::free_page(l1_entry.address_unchecked()).unwrap();
|
||||
}
|
||||
phys::free_page(l0_entry.address_unchecked()).unwrap();
|
||||
}
|
||||
memset(space as *mut Space as *mut u8, 0, 4096);
|
||||
/// Returns a virtual address physical mapping destination
|
||||
fn translate(&mut self, virt: usize) -> Result<usize, Errno> {
|
||||
self.read_last_level(virt).map(Entry::address)
|
||||
}
|
||||
|
||||
/// Returns the physical address of this structure
|
||||
pub fn address_phys(&mut self) -> usize {
|
||||
(self as *mut _ as usize) - mem::KERNEL_OFFSET
|
||||
/// Releases memory from virtual address range `start`..`start + len * 0x1000`
|
||||
fn free(&mut self, start: usize, len: usize) -> Result<(), Errno> {
|
||||
for i in 0..len {
|
||||
unsafe {
|
||||
self.write_last_level(start + i * 0x1000, Self::Entry::EMPTY, false, true)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Allocates a contiguous region from the address space and maps
|
||||
/// physical pages to it
|
||||
fn allocate(
|
||||
&mut self,
|
||||
start: usize,
|
||||
end: usize,
|
||||
len: usize,
|
||||
flags: MapAttributes,
|
||||
usage: PageUsage,
|
||||
) -> Result<usize, Errno> {
|
||||
'l0: for page in (start..end).step_by(0x1000) {
|
||||
for i in 0..len {
|
||||
if self.translate(page + i * 0x1000).is_ok() {
|
||||
continue 'l0;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..len {
|
||||
let phys = phys::alloc_page(usage).unwrap();
|
||||
self.map(page + i * 0x1000, phys, flags).unwrap();
|
||||
}
|
||||
return Ok(page);
|
||||
}
|
||||
Err(Errno::OutOfMemory)
|
||||
}
|
||||
}
|
||||
|
||||
+21
-17
@@ -2,12 +2,12 @@
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
virt::{MapAttributes, Space},
|
||||
virt::table::{MapAttributes, Space, SpaceImpl},
|
||||
};
|
||||
use core::mem::{size_of, MaybeUninit};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
traits::{Read, Seek, SeekDir}
|
||||
traits::{Read, Seek, SeekDir},
|
||||
};
|
||||
use vfs::FileRef;
|
||||
|
||||
@@ -66,10 +66,10 @@ struct Phdr<E: Elf> {
|
||||
}
|
||||
|
||||
fn map_flags(elf_flags: usize) -> MapAttributes {
|
||||
let mut dst_flags = MapAttributes::NOT_GLOBAL | MapAttributes::SH_OUTER;
|
||||
let mut dst_flags = MapAttributes::SHARE_OUTER;
|
||||
|
||||
if elf_flags & (1 << 0) /* PF_X */ == 0 {
|
||||
dst_flags |= MapAttributes::UXN | MapAttributes::PXN;
|
||||
if elf_flags & (1 << 0) /* PF_X */ != 0 {
|
||||
dst_flags |= MapAttributes::USER_EXEC;
|
||||
}
|
||||
|
||||
match (elf_flags & (3 << 1)) >> 1 {
|
||||
@@ -78,9 +78,9 @@ fn map_flags(elf_flags: usize) -> MapAttributes {
|
||||
// Write-only: not sure if such mapping should exist at all
|
||||
1 => todo!(),
|
||||
// Read-only
|
||||
2 => dst_flags |= MapAttributes::AP_BOTH_READONLY,
|
||||
2 => dst_flags |= MapAttributes::USER_READ,
|
||||
// Read+Write
|
||||
3 => dst_flags |= MapAttributes::AP_BOTH_READWRITE,
|
||||
3 => dst_flags |= MapAttributes::USER_WRITE | MapAttributes::USER_READ,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
@@ -88,7 +88,7 @@ fn map_flags(elf_flags: usize) -> MapAttributes {
|
||||
}
|
||||
|
||||
unsafe fn load_bytes<F>(
|
||||
space: &mut Space,
|
||||
space: &mut SpaceImpl,
|
||||
dst_virt: usize,
|
||||
mut read: F,
|
||||
size: usize,
|
||||
@@ -107,15 +107,17 @@ where
|
||||
let page_off = (dst_page_off + off) % mem::PAGE_SIZE;
|
||||
let count = core::cmp::min(rem, mem::PAGE_SIZE - page_off);
|
||||
|
||||
let page = phys::alloc_page(PageUsage::UserPrivate)?;
|
||||
let page = if let Ok(page) = space.translate(dst_page + page_idx * mem::PAGE_SIZE) {
|
||||
page
|
||||
} else {
|
||||
let page = phys::alloc_page(PageUsage::UserPrivate)?;
|
||||
|
||||
// TODO fetch existing mapping and test flag equality instead
|
||||
// if flags differ, bail out
|
||||
if let Err(e) = space.map(dst_page + page_idx * mem::PAGE_SIZE, page, map_flags(flags)) {
|
||||
if e != Errno::AlreadyExists {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
// TODO fetch existing mapping and test flag equality instead
|
||||
// if flags differ, bail out
|
||||
space.map(dst_page + page_idx * mem::PAGE_SIZE, page, map_flags(flags))?;
|
||||
|
||||
page
|
||||
};
|
||||
|
||||
let dst_page_virt = mem::virtualize(page + page_off);
|
||||
let dst = core::slice::from_raw_parts_mut(dst_page_virt as *mut u8, count);
|
||||
@@ -146,7 +148,7 @@ unsafe fn read_struct<T>(src: &FileRef, pos: usize) -> Result<T, Errno> {
|
||||
}
|
||||
|
||||
/// Loads an ELF program from `source` into target `space`
|
||||
pub fn load_elf(space: &mut Space, source: FileRef) -> Result<usize, Errno> {
|
||||
pub fn load_elf(space: &mut SpaceImpl, source: FileRef) -> Result<usize, Errno> {
|
||||
let ehdr: Ehdr<Elf64> = unsafe { read_struct(&source, 0).unwrap() };
|
||||
|
||||
if &ehdr.ident[0..4] != b"\x7FELF" {
|
||||
@@ -169,6 +171,7 @@ pub fn load_elf(space: &mut Space, source: FileRef) -> Result<usize, Errno> {
|
||||
);
|
||||
|
||||
if phdr.filesz > 0 {
|
||||
debugln!("Load bytes {:#x}..{:#x}", phdr.vaddr, phdr.vaddr + phdr.filesz);
|
||||
unsafe {
|
||||
load_bytes(
|
||||
space,
|
||||
@@ -190,6 +193,7 @@ pub fn load_elf(space: &mut Space, source: FileRef) -> Result<usize, Errno> {
|
||||
|
||||
if phdr.memsz > phdr.filesz {
|
||||
let len = (phdr.memsz - phdr.filesz) as usize;
|
||||
debugln!("Zero bytes {:#x}..{:#x}", phdr.vaddr + phdr.filesz, phdr.vaddr + phdr.memsz);
|
||||
unsafe {
|
||||
load_bytes(
|
||||
space,
|
||||
|
||||
+82
-3
@@ -1,12 +1,16 @@
|
||||
//! Process file descriptors and I/O context
|
||||
use alloc::collections::BTreeMap;
|
||||
use libsys::{error::Errno, stat::FileDescriptor};
|
||||
use vfs::{FileRef, Ioctx};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
stat::{FileDescriptor, GroupId, UserId},
|
||||
};
|
||||
use vfs::{FileRef, Ioctx, VnodeRef};
|
||||
|
||||
/// Process I/O context. Contains file tables, root/cwd info etc.
|
||||
pub struct ProcessIo {
|
||||
ioctx: Option<Ioctx>,
|
||||
files: BTreeMap<u32, FileRef>,
|
||||
ctty: Option<VnodeRef>,
|
||||
}
|
||||
|
||||
impl ProcessIo {
|
||||
@@ -21,9 +25,83 @@ impl ProcessIo {
|
||||
Ok(dst)
|
||||
}
|
||||
|
||||
/// Sets controlling terminal for the process
|
||||
pub fn set_ctty(&mut self, node: VnodeRef) {
|
||||
// assert_eq!(node.kind(), VnodeKind::Char);
|
||||
self.ctty = Some(node);
|
||||
}
|
||||
|
||||
/// Returns current controlling terminal of the process
|
||||
pub fn ctty(&mut self) -> Option<VnodeRef> {
|
||||
self.ctty.clone()
|
||||
}
|
||||
|
||||
/// Returns user ID of the process
|
||||
#[inline(always)]
|
||||
pub fn uid(&self) -> UserId {
|
||||
self.ioctx.as_ref().unwrap().uid
|
||||
}
|
||||
|
||||
/// Returns group ID of the process
|
||||
#[inline(always)]
|
||||
pub fn gid(&self) -> GroupId {
|
||||
self.ioctx.as_ref().unwrap().gid
|
||||
}
|
||||
|
||||
/// Changes (if permitted) user ID of the process
|
||||
#[inline(always)]
|
||||
pub fn set_uid(&mut self, uid: UserId) -> Result<(), Errno> {
|
||||
let old_uid = self.uid();
|
||||
if old_uid == uid {
|
||||
Ok(())
|
||||
} else if !old_uid.is_root() {
|
||||
Err(Errno::PermissionDenied)
|
||||
} else {
|
||||
self.ioctx.as_mut().unwrap().uid = uid;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes (if permitted) group ID of the process
|
||||
#[inline(always)]
|
||||
pub fn set_gid(&mut self, gid: GroupId) -> Result<(), Errno> {
|
||||
let old_gid = self.gid();
|
||||
if old_gid == gid {
|
||||
Ok(())
|
||||
} else if !old_gid.is_root() {
|
||||
Err(Errno::PermissionDenied)
|
||||
} else {
|
||||
self.ioctx.as_mut().unwrap().gid = gid;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Clones a file descriptor into an available slot or, if specified, requested one
|
||||
pub fn duplicate_file(
|
||||
&mut self,
|
||||
src: FileDescriptor,
|
||||
dst: Option<FileDescriptor>,
|
||||
) -> Result<FileDescriptor, Errno> {
|
||||
let file_ref = self.file(src)?;
|
||||
if let Some(dst) = dst {
|
||||
let idx = u32::from(dst);
|
||||
if self.files.get(&idx).is_some() {
|
||||
return Err(Errno::AlreadyExists);
|
||||
}
|
||||
|
||||
self.files.insert(idx, file_ref);
|
||||
Ok(dst)
|
||||
} else {
|
||||
self.place_file(file_ref)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns [File] struct referred to by file descriptor `idx`
|
||||
pub fn file(&mut self, fd: FileDescriptor) -> Result<FileRef, Errno> {
|
||||
self.files.get(&u32::from(fd)).cloned().ok_or(Errno::InvalidFile)
|
||||
self.files
|
||||
.get(&u32::from(fd))
|
||||
.cloned()
|
||||
.ok_or(Errno::InvalidFile)
|
||||
}
|
||||
|
||||
/// Returns [Ioctx] structure reference of this I/O context
|
||||
@@ -54,6 +132,7 @@ impl ProcessIo {
|
||||
Self {
|
||||
files: BTreeMap::new(),
|
||||
ioctx: None,
|
||||
ctty: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-26
@@ -3,11 +3,14 @@
|
||||
use crate::init;
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use alloc::collections::BTreeMap;
|
||||
use libsys::proc::Pid;
|
||||
use libsys::proc::{Pid, Tid};
|
||||
|
||||
pub mod elf;
|
||||
pub mod thread;
|
||||
pub(self) use thread::Context;
|
||||
pub use thread::{State as ThreadState, Thread, ThreadRef};
|
||||
pub mod process;
|
||||
pub use process::{Process, ProcessRef, State as ProcessState};
|
||||
pub use process::{Process, ProcessRef, ProcessState};
|
||||
pub mod io;
|
||||
pub use io::ProcessIo;
|
||||
|
||||
@@ -17,25 +20,6 @@ pub mod sched;
|
||||
pub use sched::Scheduler;
|
||||
pub(self) use sched::SCHED;
|
||||
|
||||
// macro_rules! spawn {
|
||||
// (fn ($dst_arg:ident : usize) $body:block, $src_arg:expr) => {{
|
||||
// #[inline(never)]
|
||||
// extern "C" fn __inner_func($dst_arg : usize) -> ! {
|
||||
// let __res = $body;
|
||||
// {
|
||||
// #![allow(unreachable_code)]
|
||||
// SCHED.current_process().exit(__res);
|
||||
// panic!();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let __proc = $crate::proc::Process::new_kernel(__inner_func, $src_arg).unwrap();
|
||||
// $crate::proc::SCHED.enqueue(__proc.id());
|
||||
// }};
|
||||
//
|
||||
// (fn () $body:block) => (spawn!(fn (_arg: usize) $body, 0usize))
|
||||
// }
|
||||
|
||||
/// Performs a task switch.
|
||||
///
|
||||
/// See [Scheduler::switch]
|
||||
@@ -43,15 +27,17 @@ pub fn switch() {
|
||||
SCHED.switch(false);
|
||||
}
|
||||
|
||||
///
|
||||
pub fn process(id: Pid) -> ProcessRef {
|
||||
PROCESSES.lock().get(&id).unwrap().clone()
|
||||
#[no_mangle]
|
||||
extern "C" fn sched_yield() {
|
||||
SCHED.switch(false);
|
||||
}
|
||||
|
||||
/// Global list of all processes in the system
|
||||
pub(self) static PROCESSES: IrqSafeSpinLock<BTreeMap<Pid, ProcessRef>> =
|
||||
IrqSafeSpinLock::new(BTreeMap::new());
|
||||
|
||||
pub(self) static THREADS: IrqSafeSpinLock<BTreeMap<Tid, ThreadRef>> =
|
||||
IrqSafeSpinLock::new(BTreeMap::new());
|
||||
|
||||
/// Sets up initial process and enters it.
|
||||
///
|
||||
/// See [Scheduler::enter]
|
||||
@@ -61,6 +47,6 @@ pub(self) static PROCESSES: IrqSafeSpinLock<BTreeMap<Pid, ProcessRef>> =
|
||||
/// Unsafe: May only be called once.
|
||||
pub unsafe fn enter() -> ! {
|
||||
SCHED.init();
|
||||
SCHED.enqueue(Process::new_kernel(init::init_fn, 0).unwrap().id());
|
||||
Process::new_kernel(init::init_fn, 0).unwrap().enqueue();
|
||||
SCHED.enter();
|
||||
}
|
||||
|
||||
+350
-294
@@ -1,65 +1,212 @@
|
||||
//! Process data and control
|
||||
use crate::arch::aarch64::exception::ExceptionFrame;
|
||||
use crate::mem::{
|
||||
self,
|
||||
phys::{self, PageUsage},
|
||||
virt::{MapAttributes, Space},
|
||||
virt::{write_paged, write_paged_bytes, table::{MapAttributes, Space, SpaceImpl}},
|
||||
};
|
||||
use crate::proc::{wait::Wait, ProcessIo, PROCESSES, SCHED};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::UnsafeCell;
|
||||
use crate::proc::{
|
||||
wait::Wait, Context, ProcessIo, Thread, ThreadRef, ThreadState, Tid, PROCESSES, SCHED,
|
||||
};
|
||||
use crate::arch::{intrin, platform::ForkFrame};
|
||||
use crate::sync::{IrqSafeSpinLock, IrqSafeSpinLockGuard};
|
||||
use alloc::{rc::Rc, vec::Vec};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use libsys::{error::Errno, signal::Signal, proc::{ExitCode, Pid}};
|
||||
|
||||
pub use crate::arch::platform::context::{self, Context};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
proc::{ExitCode, Pid},
|
||||
signal::Signal,
|
||||
ProgramArgs,
|
||||
};
|
||||
|
||||
/// Wrapper type for a process struct reference
|
||||
pub type ProcessRef = Rc<Process>;
|
||||
|
||||
/// List of possible process states
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum State {
|
||||
/// Process is ready to be executed and/or is scheduled for it
|
||||
Ready,
|
||||
/// Process is currently running or is in system call/interrupt handler
|
||||
Running,
|
||||
pub enum ProcessState {
|
||||
/// Process is alive
|
||||
Active,
|
||||
/// Process has finished execution and is waiting to be reaped
|
||||
Finished,
|
||||
/// Process is waiting for some external event
|
||||
Waiting,
|
||||
}
|
||||
|
||||
struct ProcessInner {
|
||||
space: Option<&'static mut Space>,
|
||||
state: State,
|
||||
space: Option<&'static mut SpaceImpl>,
|
||||
state: ProcessState,
|
||||
id: Pid,
|
||||
wait_flag: bool,
|
||||
pgid: Pid,
|
||||
ppid: Option<Pid>,
|
||||
sid: Pid,
|
||||
exit: Option<ExitCode>,
|
||||
signal_entry: usize,
|
||||
signal_stack: usize,
|
||||
threads: Vec<Tid>,
|
||||
}
|
||||
|
||||
/// Structure describing an operating system process
|
||||
#[allow(dead_code)]
|
||||
pub struct Process {
|
||||
ctx: UnsafeCell<Context>,
|
||||
signal_ctx: UnsafeCell<Context>,
|
||||
inner: IrqSafeSpinLock<ProcessInner>,
|
||||
exit_wait: Wait,
|
||||
signal_state: AtomicU32,
|
||||
signal_pending: AtomicU32,
|
||||
/// Process I/O context
|
||||
pub io: IrqSafeSpinLock<ProcessIo>,
|
||||
}
|
||||
|
||||
impl Process {
|
||||
const USTACK_VIRT_TOP: usize = 0x100000000;
|
||||
const USTACK_PAGES: usize = 4;
|
||||
const USTACK_PAGES: usize = 8;
|
||||
|
||||
/// Returns currently executing process
|
||||
/// Returns the process ID
|
||||
#[inline]
|
||||
pub fn id(&self) -> Pid {
|
||||
self.inner.lock().id
|
||||
}
|
||||
|
||||
/// Returns the process session ID
|
||||
#[inline]
|
||||
pub fn sid(&self) -> Pid {
|
||||
self.inner.lock().sid
|
||||
}
|
||||
|
||||
/// Returns parent's [Pid]
|
||||
#[inline]
|
||||
pub fn pgid(&self) -> Pid {
|
||||
self.inner.lock().pgid
|
||||
}
|
||||
|
||||
/// Returns parent's [Pid]
|
||||
#[inline]
|
||||
pub fn ppid(&self) -> Option<Pid> {
|
||||
self.inner.lock().ppid
|
||||
}
|
||||
|
||||
/// Sets a new group id for the process
|
||||
pub fn set_pgid(&self, pgid: Pid) {
|
||||
self.inner.lock().pgid = pgid;
|
||||
}
|
||||
|
||||
/// Sets a new session id for the process
|
||||
pub fn set_sid(&self, sid: Pid) {
|
||||
self.inner.lock().sid = sid;
|
||||
}
|
||||
|
||||
/// Returns [Rc]-reference to current process
|
||||
#[inline]
|
||||
pub fn current() -> ProcessRef {
|
||||
SCHED.current_process()
|
||||
Thread::current().owner().unwrap()
|
||||
}
|
||||
|
||||
/// Executes a closure performing manipulations on the process address space
|
||||
#[inline]
|
||||
pub fn manipulate_space<R, F>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut SpaceImpl) -> R,
|
||||
{
|
||||
f(self.inner.lock().space.as_mut().unwrap())
|
||||
}
|
||||
|
||||
/// Handles all pending signals (when returning from aborted syscall)
|
||||
pub fn handle_pending_signals(&self) {
|
||||
let mut lock = self.inner.lock();
|
||||
let table = Self::space_phys(&mut lock);
|
||||
let main_thread = Thread::get(lock.threads[0]).unwrap();
|
||||
drop(lock);
|
||||
|
||||
loop {
|
||||
let state = self.signal_state.load(Ordering::Acquire);
|
||||
if let Some(signal) = Self::find1(state).map(|e| Signal::try_from(e as u32).unwrap()) {
|
||||
self.signal_state.fetch_and(!(1 << (signal as u32)), Ordering::Release);
|
||||
main_thread.clone().enter_signal(signal, table);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a pending signal for process
|
||||
pub fn set_signal(&self, signal: Signal) {
|
||||
let mut lock = self.inner.lock();
|
||||
let table = Self::space_phys(&mut lock);
|
||||
let main_thread = Thread::get(lock.threads[0]).unwrap();
|
||||
drop(lock);
|
||||
|
||||
// TODO check that `signal` is not a fault signal
|
||||
// it is illegal to call this function with
|
||||
// fault signals
|
||||
|
||||
match main_thread.state() {
|
||||
ThreadState::Running => {
|
||||
main_thread.enter_signal(signal, table);
|
||||
}
|
||||
ThreadState::Waiting => {
|
||||
self.signal_state.fetch_or(1 << (signal as u32), Ordering::Release);
|
||||
main_thread.interrupt_wait(true);
|
||||
}
|
||||
ThreadState::Ready => {
|
||||
main_thread.clone().setup_signal(signal, table);
|
||||
main_thread.interrupt_wait(false);
|
||||
}
|
||||
ThreadState::Finished => {
|
||||
// TODO report error back
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Immediately delivers a signal to requested thread
|
||||
pub fn enter_fault_signal(&self, thread: ThreadRef, signal: Signal) {
|
||||
let mut lock = self.inner.lock();
|
||||
let table = Self::space_phys(&mut lock);
|
||||
drop(lock);
|
||||
thread.enter_signal(signal, table);
|
||||
}
|
||||
|
||||
/// Crates a new thread in the process
|
||||
pub fn new_user_thread(&self, entry: usize, stack: usize, arg: usize) -> Result<Tid, Errno> {
|
||||
let mut lock = self.inner.lock();
|
||||
|
||||
let table = Self::space_phys(&mut lock);
|
||||
let thread = Thread::new_user(lock.id, entry, stack, arg, table)?;
|
||||
let tid = thread.id();
|
||||
lock.threads.push(tid);
|
||||
SCHED.enqueue(tid);
|
||||
|
||||
Ok(tid)
|
||||
}
|
||||
|
||||
/// Creates a new kernel process
|
||||
pub fn new_kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result<ProcessRef, Errno> {
|
||||
let id = new_kernel_pid();
|
||||
let thread = Thread::new_kernel(Some(id), entry, arg)?;
|
||||
let mut inner = ProcessInner {
|
||||
threads: Vec::new(),
|
||||
id,
|
||||
pgid: id,
|
||||
ppid: None,
|
||||
sid: id,
|
||||
exit: None,
|
||||
space: None,
|
||||
state: ProcessState::Active,
|
||||
};
|
||||
inner.threads.push(thread.id());
|
||||
|
||||
let res = Rc::new(Self {
|
||||
exit_wait: Wait::new("process_exit"),
|
||||
io: IrqSafeSpinLock::new(ProcessIo::new()),
|
||||
signal_state: AtomicU32::new(0),
|
||||
inner: IrqSafeSpinLock::new(inner),
|
||||
});
|
||||
debugln!("New kernel process: {:?}", id);
|
||||
let prev = PROCESSES.lock().insert(id, res.clone());
|
||||
assert!(prev.is_none());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Adds all of the process threads to scheduler queue
|
||||
pub fn enqueue(&self) {
|
||||
let inner = self.inner.lock();
|
||||
for &tid in inner.threads.iter() {
|
||||
SCHED.enqueue(tid);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns process (if any) to which `pid` refers
|
||||
@@ -67,273 +214,139 @@ impl Process {
|
||||
PROCESSES.lock().get(&pid).cloned()
|
||||
}
|
||||
|
||||
/// Sets a pending signal for a process
|
||||
pub fn set_signal(&self, signal: Signal) {
|
||||
let lock = self.inner.lock();
|
||||
|
||||
match lock.state {
|
||||
State::Running => {
|
||||
drop(lock);
|
||||
self.enter_signal(signal);
|
||||
}
|
||||
State::Waiting => {
|
||||
// TODO abort whatever the process is waiting for
|
||||
todo!()
|
||||
}
|
||||
State::Ready => {
|
||||
todo!()
|
||||
}
|
||||
State::Finished => {
|
||||
// TODO report error back
|
||||
todo!()
|
||||
fn find1(a: u32) -> Option<usize> {
|
||||
for i in 0..32 {
|
||||
if a & (1 << i) != 0 {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Switches current thread back from signal handler
|
||||
pub fn return_from_signal(&self) {
|
||||
if self.signal_pending.load(Ordering::Acquire) == 0 {
|
||||
panic!("TODO handle cases when returning from no signal");
|
||||
}
|
||||
self.signal_pending.store(0, Ordering::Release);
|
||||
|
||||
let src_ctx = self.signal_ctx.get();
|
||||
let dst_ctx = self.ctx.get();
|
||||
|
||||
assert_eq!(self.inner.lock().state, State::Running);
|
||||
|
||||
unsafe {
|
||||
(&mut *src_ctx).switch(&mut *dst_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches current thread to a signal handler
|
||||
pub fn enter_signal(&self, signal: Signal) {
|
||||
if self
|
||||
.signal_pending
|
||||
.compare_exchange_weak(0, signal as u32, Ordering::SeqCst, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
panic!("Already handling a signal (maybe handle this case)");
|
||||
}
|
||||
|
||||
let mut lock = self.inner.lock();
|
||||
let signal_ctx = unsafe { &mut *self.signal_ctx.get() };
|
||||
|
||||
let dst_id = lock.id;
|
||||
let dst_space_phys = lock.space.as_mut().unwrap().address_phys();
|
||||
let dst_ttbr0 = dst_space_phys | ((dst_id.asid() as usize) << 48);
|
||||
|
||||
debugln!(
|
||||
"Signal entry: pc={:#x}, sp={:#x}, ttbr0={:#x}",
|
||||
lock.signal_entry,
|
||||
lock.signal_stack,
|
||||
dst_ttbr0
|
||||
);
|
||||
assert_eq!(lock.state, State::Running);
|
||||
|
||||
unsafe {
|
||||
signal_ctx.setup_signal_entry(
|
||||
lock.signal_entry,
|
||||
signal as usize,
|
||||
dst_ttbr0,
|
||||
lock.signal_stack,
|
||||
);
|
||||
}
|
||||
let src_ctx = self.ctx.get();
|
||||
drop(lock);
|
||||
|
||||
unsafe {
|
||||
(&mut *src_ctx).switch(signal_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up values needed for signal entry
|
||||
pub fn setup_signal_context(&self, entry: usize, stack: usize) {
|
||||
let mut lock = self.inner.lock();
|
||||
lock.signal_entry = entry;
|
||||
lock.signal_stack = stack;
|
||||
}
|
||||
|
||||
/// Schedules an initial thread for execution
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: only allowed to be called once, repeated calls
|
||||
/// will generate undefined behavior
|
||||
pub unsafe fn enter(proc: ProcessRef) -> ! {
|
||||
// FIXME use some global lock to guarantee atomicity of thread entry?
|
||||
proc.inner.lock().state = State::Running;
|
||||
proc.current_context().enter()
|
||||
}
|
||||
|
||||
/// Executes a function allowing mutation of the process address space
|
||||
#[inline]
|
||||
pub fn manipulate_space<F: FnOnce(&mut Space) -> Result<(), Errno>>(
|
||||
&self,
|
||||
f: F,
|
||||
) -> Result<(), Errno> {
|
||||
f(self.inner.lock().space.as_mut().unwrap())
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn current_context(&self) -> &mut Context {
|
||||
if self.signal_pending.load(Ordering::Acquire) != 0 {
|
||||
unsafe { &mut *self.signal_ctx.get() }
|
||||
} else {
|
||||
unsafe { &mut *self.ctx.get() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedules a next thread for execution
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe:
|
||||
///
|
||||
/// * Does not ensure src and dst threads are not the same thread
|
||||
/// * Does not ensure src is actually current context
|
||||
pub unsafe fn switch(src: ProcessRef, dst: ProcessRef, discard: bool) {
|
||||
{
|
||||
let mut src_lock = src.inner.lock();
|
||||
let mut dst_lock = dst.inner.lock();
|
||||
|
||||
if !discard {
|
||||
assert_eq!(src_lock.state, State::Running);
|
||||
src_lock.state = State::Ready;
|
||||
}
|
||||
assert!(dst_lock.state == State::Ready || dst_lock.state == State::Waiting);
|
||||
dst_lock.state = State::Running;
|
||||
}
|
||||
|
||||
let src_ctx = src.current_context();
|
||||
let dst_ctx = dst.current_context();
|
||||
|
||||
(&mut *src_ctx).switch(&mut *dst_ctx);
|
||||
}
|
||||
|
||||
/// Suspends current process with a "waiting" status
|
||||
pub fn enter_wait(&self) {
|
||||
let drop = {
|
||||
let mut lock = self.inner.lock();
|
||||
let drop = lock.state == State::Running;
|
||||
lock.state = State::Waiting;
|
||||
SCHED.dequeue(lock.id);
|
||||
drop
|
||||
};
|
||||
if drop {
|
||||
SCHED.switch(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes process wait condition status
|
||||
pub fn set_wait_flag(&self, v: bool) {
|
||||
self.inner.lock().wait_flag = v;
|
||||
}
|
||||
|
||||
/// Returns `true` if process wait condition has not been reached
|
||||
pub fn wait_flag(&self) -> bool {
|
||||
self.inner.lock().wait_flag
|
||||
}
|
||||
|
||||
/// Returns the process ID
|
||||
pub fn id(&self) -> Pid {
|
||||
self.inner.lock().id
|
||||
}
|
||||
|
||||
/// Creates a new kernel process
|
||||
pub fn new_kernel(entry: extern "C" fn(usize) -> !, arg: usize) -> Result<ProcessRef, Errno> {
|
||||
let id = new_kernel_pid();
|
||||
let res = Rc::new(Self {
|
||||
ctx: UnsafeCell::new(Context::kernel(entry as usize, arg)),
|
||||
signal_ctx: UnsafeCell::new(Context::empty()),
|
||||
io: IrqSafeSpinLock::new(ProcessIo::new()),
|
||||
exit_wait: Wait::new(),
|
||||
signal_state: AtomicU32::new(0),
|
||||
signal_pending: AtomicU32::new(0),
|
||||
inner: IrqSafeSpinLock::new(ProcessInner {
|
||||
signal_entry: 0,
|
||||
signal_stack: 0,
|
||||
id,
|
||||
exit: None,
|
||||
space: None,
|
||||
wait_flag: false,
|
||||
state: State::Ready,
|
||||
}),
|
||||
});
|
||||
debugln!("New kernel process: {:?}", id);
|
||||
assert!(PROCESSES.lock().insert(id, res.clone()).is_none());
|
||||
Ok(res)
|
||||
fn space_phys(lock: &mut IrqSafeSpinLockGuard<ProcessInner>) -> usize {
|
||||
lock.space.as_mut().unwrap().address_phys() | ((lock.id.asid() as usize) << 48)
|
||||
}
|
||||
|
||||
/// Creates a "fork" of the process, cloning its address space and
|
||||
/// resources
|
||||
pub fn fork(&self, frame: &mut ExceptionFrame) -> Result<Pid, Errno> {
|
||||
pub fn fork(&self, frame: &mut ForkFrame) -> Result<Pid, Errno> {
|
||||
let src_io = self.io.lock();
|
||||
let mut src_inner = self.inner.lock();
|
||||
|
||||
let dst_id = new_user_pid();
|
||||
let dst_space = src_inner.space.as_mut().unwrap().fork()?;
|
||||
|
||||
let dst_space_phys = (dst_space as *mut _ as usize) - mem::KERNEL_OFFSET;
|
||||
let dst_ttbr0 = dst_space_phys | ((dst_id.asid() as usize) << 48);
|
||||
|
||||
let mut threads = Vec::new();
|
||||
let tid = Thread::fork(Some(dst_id), frame, dst_ttbr0)?.id();
|
||||
threads.push(tid);
|
||||
|
||||
let dst = Rc::new(Self {
|
||||
ctx: UnsafeCell::new(Context::fork(frame, dst_ttbr0)),
|
||||
signal_ctx: UnsafeCell::new(Context::empty()),
|
||||
exit_wait: Wait::new("process_exit"),
|
||||
io: IrqSafeSpinLock::new(src_io.fork()?),
|
||||
exit_wait: Wait::new(),
|
||||
signal_state: AtomicU32::new(0),
|
||||
signal_pending: AtomicU32::new(0),
|
||||
inner: IrqSafeSpinLock::new(ProcessInner {
|
||||
signal_entry: 0,
|
||||
signal_stack: 0,
|
||||
id: dst_id,
|
||||
threads,
|
||||
exit: None,
|
||||
space: Some(dst_space),
|
||||
state: State::Ready,
|
||||
wait_flag: false,
|
||||
state: ProcessState::Active,
|
||||
id: dst_id,
|
||||
pgid: src_inner.pgid,
|
||||
ppid: Some(src_inner.id),
|
||||
sid: src_inner.sid,
|
||||
}),
|
||||
});
|
||||
|
||||
debugln!("Process {:?} forked into {:?}", src_inner.id, dst_id);
|
||||
assert!(PROCESSES.lock().insert(dst_id, dst).is_none());
|
||||
SCHED.enqueue(dst_id);
|
||||
|
||||
SCHED.enqueue(tid);
|
||||
|
||||
Ok(dst_id)
|
||||
}
|
||||
|
||||
/// Terminates a process.
|
||||
pub fn exit<I: Into<ExitCode>>(&self, status: I) {
|
||||
let status = status.into();
|
||||
let drop = {
|
||||
let mut lock = self.inner.lock();
|
||||
let drop = lock.state == State::Running;
|
||||
infoln!("Process {:?} is exiting: {:?}", lock.id, status);
|
||||
assert!(lock.exit.is_none());
|
||||
lock.exit = Some(status);
|
||||
lock.state = State::Finished;
|
||||
pub fn exit(self: ProcessRef, status: ExitCode) {
|
||||
let thread = Thread::current();
|
||||
let mut lock = self.inner.lock();
|
||||
let is_running = thread.owner_id().map(|e| e == lock.id).unwrap_or(false);
|
||||
|
||||
if let Some(space) = lock.space.take() {
|
||||
unsafe {
|
||||
Space::release(space);
|
||||
asm!("tlbi aside1, {}", in(reg) ((lock.id.asid() as usize) << 48));
|
||||
}
|
||||
infoln!("Process {:?} is exiting: {:?}", lock.id, status);
|
||||
assert!(lock.exit.is_none());
|
||||
lock.exit = Some(status);
|
||||
lock.state = ProcessState::Finished;
|
||||
|
||||
for &tid in lock.threads.iter() {
|
||||
let thread = Thread::get(tid).unwrap();
|
||||
if thread.state() == ThreadState::Waiting {
|
||||
todo!()
|
||||
}
|
||||
thread.terminate(status);
|
||||
SCHED.dequeue(tid);
|
||||
}
|
||||
|
||||
self.io.lock().handle_exit();
|
||||
if let Some(space) = lock.space.take() {
|
||||
unsafe {
|
||||
SpaceImpl::release(space);
|
||||
intrin::flush_tlb_asid((lock.id.asid() as usize) << 48);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO when exiting from signal handler interrupting an IO operation
|
||||
// deadlock is achieved
|
||||
self.io.lock().handle_exit();
|
||||
|
||||
drop(lock);
|
||||
|
||||
SCHED.dequeue(lock.id);
|
||||
drop
|
||||
};
|
||||
self.exit_wait.wakeup_all();
|
||||
if drop {
|
||||
|
||||
if is_running {
|
||||
SCHED.switch(true);
|
||||
panic!("This code should never run");
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminates a thread of the process. If the thread is the only
|
||||
/// one remaining, process itself is exited (see [Process::exit])
|
||||
pub fn exit_thread(thread: ThreadRef, status: ExitCode) {
|
||||
let switch = {
|
||||
let switch = thread.state() == ThreadState::Running;
|
||||
let process = thread.owner().unwrap();
|
||||
let mut lock = process.inner.lock();
|
||||
let tid = thread.id();
|
||||
|
||||
if lock.threads.len() == 1 {
|
||||
// TODO call Process::exit instead?
|
||||
drop(lock);
|
||||
process.exit(status);
|
||||
return;
|
||||
}
|
||||
|
||||
lock.threads.retain(|&e| e != tid);
|
||||
|
||||
thread.terminate(status);
|
||||
SCHED.dequeue(tid);
|
||||
debugln!("Thread {:?} terminated", tid);
|
||||
|
||||
switch
|
||||
};
|
||||
|
||||
if switch {
|
||||
// TODO retain thread ID in process "finished" list and
|
||||
// drop it when process finishes
|
||||
SCHED.switch(true);
|
||||
panic!("This code should not run");
|
||||
} else {
|
||||
// Can drop this thread: it's not running
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(&self) -> Option<ExitCode> {
|
||||
let lock = self.inner.lock();
|
||||
if lock.state == State::Finished {
|
||||
if lock.state == ProcessState::Finished {
|
||||
lock.exit
|
||||
} else {
|
||||
None
|
||||
@@ -352,7 +365,6 @@ impl Process {
|
||||
if let Some(r) = proc.collect() {
|
||||
// TODO drop the process struct itself
|
||||
PROCESSES.lock().remove(&proc.id());
|
||||
debugln!("pid {:?} has {} refs", proc.id(), Rc::strong_count(&proc));
|
||||
return Ok(r);
|
||||
}
|
||||
|
||||
@@ -360,81 +372,125 @@ impl Process {
|
||||
}
|
||||
}
|
||||
|
||||
fn store_arguments(space: &mut SpaceImpl, argv: &[&str]) -> Result<usize, Errno> {
|
||||
let mut offset = 0usize;
|
||||
// TODO vmalloc?
|
||||
let base = 0x60000000;
|
||||
|
||||
// 1. Store program argument string bytes
|
||||
for arg in argv.iter() {
|
||||
unsafe {
|
||||
write_paged_bytes(space, base + offset, arg.as_bytes())?;
|
||||
}
|
||||
offset += arg.len();
|
||||
}
|
||||
// Align
|
||||
offset = (offset + 15) & !15;
|
||||
let argv_offset = offset;
|
||||
|
||||
// 2. Store arg pointers
|
||||
let mut data_offset = 0usize;
|
||||
for arg in argv.iter() {
|
||||
// XXX this is really unsafe and I am not really sure ABI will stay like this XXX
|
||||
unsafe {
|
||||
write_paged(space, base + offset, base + data_offset)?;
|
||||
write_paged(space, base + offset + 8, arg.len())?;
|
||||
}
|
||||
offset += 16;
|
||||
data_offset += arg.len();
|
||||
}
|
||||
|
||||
// 3. Store ProgramArgs
|
||||
let data = ProgramArgs {
|
||||
argc: argv.len(),
|
||||
argv: base + argv_offset,
|
||||
storage: base,
|
||||
size: offset + core::mem::size_of::<ProgramArgs>(),
|
||||
};
|
||||
unsafe {
|
||||
write_paged(space, base + offset, data)?;
|
||||
}
|
||||
|
||||
Ok(base + offset)
|
||||
}
|
||||
|
||||
/// Returns the process's address space ID
|
||||
pub fn asid(&self) -> usize {
|
||||
(self.id().asid() as usize) << 48
|
||||
}
|
||||
|
||||
/// Loads a new program into current process address space
|
||||
pub fn execve<F: FnOnce(&mut Space) -> Result<usize, Errno>>(
|
||||
pub fn execve<F: FnOnce(&mut SpaceImpl) -> Result<usize, Errno>>(
|
||||
loader: F,
|
||||
arg: usize,
|
||||
argv: &[&str],
|
||||
) -> Result<(), Errno> {
|
||||
unsafe {
|
||||
// Run with interrupts disabled
|
||||
asm!("msr daifset, #2");
|
||||
intrin::irq_disable();
|
||||
}
|
||||
|
||||
let proc = SCHED.current_process();
|
||||
let mut lock = proc.inner.lock();
|
||||
if lock.id.is_kernel() {
|
||||
let mut proc_lock = PROCESSES.lock();
|
||||
let old_pid = lock.id;
|
||||
assert!(
|
||||
proc_lock.remove(&old_pid).is_some(),
|
||||
"Failed to downgrade kernel process (remove kernel pid)"
|
||||
);
|
||||
lock.id = new_user_pid();
|
||||
debugln!(
|
||||
"Process downgrades from kernel to user: {:?} -> {:?}",
|
||||
old_pid,
|
||||
lock.id
|
||||
);
|
||||
assert!(proc_lock.insert(lock.id, proc.clone()).is_none());
|
||||
unsafe {
|
||||
SCHED.hack_current_pid(lock.id);
|
||||
}
|
||||
} else {
|
||||
// Invalidate user ASID
|
||||
let input = (lock.id.asid() as usize) << 48;
|
||||
unsafe {
|
||||
asm!("tlbi aside1, {}", in(reg) input);
|
||||
}
|
||||
let proc = Process::current();
|
||||
let mut process_lock = proc.inner.lock();
|
||||
|
||||
if process_lock.threads.len() != 1 {
|
||||
todo!();
|
||||
}
|
||||
|
||||
let thread = Thread::get(process_lock.threads[0]).unwrap();
|
||||
|
||||
if process_lock.id.is_kernel() {
|
||||
let mut processes = PROCESSES.lock();
|
||||
let old_pid = process_lock.id;
|
||||
let new_pid = new_user_pid();
|
||||
debugln!("Downgrading process {:?} -> {:?}", old_pid, new_pid);
|
||||
|
||||
let r = processes.remove(&old_pid);
|
||||
assert!(r.is_some());
|
||||
process_lock.id = new_pid;
|
||||
process_lock.pgid = new_pid;
|
||||
process_lock.sid = new_pid;
|
||||
let r = processes.insert(new_pid, proc.clone());
|
||||
assert!(r.is_none());
|
||||
}
|
||||
|
||||
thread.set_owner(process_lock.id);
|
||||
|
||||
proc.io.lock().handle_cloexec();
|
||||
|
||||
let new_space = Space::alloc_empty()?;
|
||||
let new_space = SpaceImpl::alloc_empty()?;
|
||||
let new_space_phys = (new_space as *mut _ as usize) - mem::KERNEL_OFFSET;
|
||||
|
||||
let ustack_virt_bottom = Self::USTACK_VIRT_TOP - Self::USTACK_PAGES * mem::PAGE_SIZE;
|
||||
for i in 0..Self::USTACK_PAGES {
|
||||
let page = phys::alloc_page(PageUsage::UserPrivate).unwrap();
|
||||
let flags = MapAttributes::SH_OUTER
|
||||
| MapAttributes::NOT_GLOBAL
|
||||
| MapAttributes::UXN
|
||||
| MapAttributes::PXN
|
||||
| MapAttributes::AP_BOTH_READWRITE;
|
||||
let flags = MapAttributes::SHARE_OUTER
|
||||
| MapAttributes::USER_READ
|
||||
| MapAttributes::USER_WRITE;
|
||||
new_space
|
||||
.map(ustack_virt_bottom + i * mem::PAGE_SIZE, page, flags)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let entry = loader(new_space)?;
|
||||
let arg = Self::store_arguments(new_space, argv)?;
|
||||
|
||||
debugln!("Will now enter at {:#x}", entry);
|
||||
// TODO drop old address space
|
||||
lock.space = Some(new_space);
|
||||
process_lock.space = Some(new_space);
|
||||
|
||||
unsafe {
|
||||
// TODO drop old context
|
||||
let ctx = proc.ctx.get();
|
||||
let ctx = thread.ctx.get();
|
||||
let asid = (process_lock.id.asid() as usize) << 48;
|
||||
intrin::flush_tlb_asid(asid);
|
||||
|
||||
ctx.write(Context::user(
|
||||
entry,
|
||||
arg,
|
||||
new_space_phys | ((lock.id.asid() as usize) << 48),
|
||||
Self::USTACK_VIRT_TOP,
|
||||
new_space_phys | asid,
|
||||
Self::USTACK_VIRT_TOP - mem::USTACK_PADDING,
|
||||
));
|
||||
|
||||
assert_eq!(lock.state, State::Running);
|
||||
|
||||
drop(lock);
|
||||
drop(process_lock);
|
||||
|
||||
(*ctx).enter();
|
||||
}
|
||||
|
||||
+41
-24
@@ -1,13 +1,15 @@
|
||||
//!
|
||||
use crate::proc::{Pid, Process, ProcessRef, PROCESSES};
|
||||
use crate::arch::intrin;
|
||||
use crate::proc::{Thread, ThreadRef, THREADS};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use crate::util::InitOnce;
|
||||
use alloc::{collections::VecDeque, rc::Rc};
|
||||
use libsys::proc::Tid;
|
||||
|
||||
struct SchedulerInner {
|
||||
queue: VecDeque<Pid>,
|
||||
idle: Option<Pid>,
|
||||
current: Option<Pid>,
|
||||
queue: VecDeque<Tid>,
|
||||
idle: Option<Tid>,
|
||||
current: Option<Tid>,
|
||||
}
|
||||
|
||||
/// Process scheduler state and queues
|
||||
@@ -23,7 +25,9 @@ impl SchedulerInner {
|
||||
current: None,
|
||||
};
|
||||
|
||||
this.idle = Some(Process::new_kernel(idle_fn, 0).unwrap().id());
|
||||
let idle = Thread::new_kernel(None, idle_fn, 0).unwrap().id();
|
||||
assert_eq!(idle, Tid::IDLE);
|
||||
this.idle = Some(idle);
|
||||
|
||||
this
|
||||
}
|
||||
@@ -39,13 +43,13 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
/// Schedules a thread for execution
|
||||
pub fn enqueue(&self, pid: Pid) {
|
||||
self.inner.get().lock().queue.push_back(pid);
|
||||
pub fn enqueue(&self, tid: Tid) {
|
||||
self.inner.get().lock().queue.push_back(tid);
|
||||
}
|
||||
|
||||
/// Removes given `pid` from execution queue
|
||||
pub fn dequeue(&self, pid: Pid) {
|
||||
self.inner.get().lock().queue.retain(|&p| p != pid)
|
||||
/// Removes given `tid` from execution queue
|
||||
pub fn dequeue(&self, tid: Tid) {
|
||||
self.inner.get().lock().queue.retain(|&p| p != tid)
|
||||
}
|
||||
|
||||
/// Performs initial process entry.
|
||||
@@ -63,11 +67,11 @@ impl Scheduler {
|
||||
};
|
||||
|
||||
inner.current = Some(id);
|
||||
PROCESSES.lock().get(&id).unwrap().clone()
|
||||
THREADS.lock().get(&id).unwrap().clone()
|
||||
};
|
||||
|
||||
asm!("msr daifset, #2");
|
||||
Process::enter(thread)
|
||||
intrin::irq_disable();
|
||||
Thread::enter(thread)
|
||||
}
|
||||
|
||||
/// This hack is required to be called from execve() when downgrading current
|
||||
@@ -76,8 +80,14 @@ impl Scheduler {
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: only allowed to be called from Process::execve()
|
||||
pub unsafe fn hack_current_pid(&self, new: Pid) {
|
||||
self.inner.get().lock().current = Some(new);
|
||||
pub unsafe fn hack_current_tid(&self, old: Tid, new: Tid) {
|
||||
let mut lock = self.inner.get().lock();
|
||||
match lock.current {
|
||||
Some(t) if t == old => {
|
||||
lock.current = Some(new);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches to the next task scheduled for execution. If there're
|
||||
@@ -87,7 +97,7 @@ impl Scheduler {
|
||||
let mut inner = self.inner.get().lock();
|
||||
let current = inner.current.unwrap();
|
||||
|
||||
if !discard && current != Pid::IDLE {
|
||||
if !discard && current != Tid::IDLE {
|
||||
// Put the process into the back of the queue
|
||||
inner.queue.push_back(current);
|
||||
}
|
||||
@@ -100,7 +110,7 @@ impl Scheduler {
|
||||
|
||||
inner.current = Some(next);
|
||||
let (from, to) = {
|
||||
let lock = PROCESSES.lock();
|
||||
let lock = THREADS.lock();
|
||||
(
|
||||
lock.get(¤t).unwrap().clone(),
|
||||
lock.get(&next).unwrap().clone(),
|
||||
@@ -112,18 +122,25 @@ impl Scheduler {
|
||||
|
||||
if !Rc::ptr_eq(&from, &to) {
|
||||
unsafe {
|
||||
asm!("msr daifset, #2");
|
||||
Process::switch(from, to, discard);
|
||||
intrin::irq_disable();
|
||||
Thread::switch(from, to, discard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Rc-reference to currently running process
|
||||
pub fn current_process(&self) -> ProcessRef {
|
||||
/// Returns a [Rc]-reference to currently running Thread
|
||||
pub fn current_thread(&self) -> ThreadRef {
|
||||
let inner = self.inner.get().lock();
|
||||
let current = inner.current.unwrap();
|
||||
PROCESSES.lock().get(¤t).unwrap().clone()
|
||||
let id = inner.current.unwrap();
|
||||
THREADS.lock().get(&id).unwrap().clone()
|
||||
}
|
||||
|
||||
// /// Returns a Rc-reference to currently running process
|
||||
// pub fn current_process(&self) -> ProcessRef {
|
||||
// let inner = self.inner.get().lock();
|
||||
// let current = inner.current.unwrap();
|
||||
// PROCESSES.lock().get(¤t).unwrap().clone()
|
||||
// }
|
||||
}
|
||||
|
||||
/// Returns `true` if the scheduler has been initialized
|
||||
@@ -134,7 +151,7 @@ pub fn is_ready() -> bool {
|
||||
#[inline(never)]
|
||||
extern "C" fn idle_fn(_a: usize) -> ! {
|
||||
loop {
|
||||
cortex_a::asm::wfi();
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
//! Facilities for controlling threads - smallest units of
|
||||
//! execution in the operating system
|
||||
// use crate::arch::aarch64::exception::ExceptionFrame;
|
||||
use crate::arch::platform::ForkFrame;
|
||||
use crate::proc::{
|
||||
wait::{Wait, WaitStatus},
|
||||
Process, ProcessRef, SCHED, THREADS,
|
||||
};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use crate::util::InitOnce;
|
||||
use alloc::rc::Rc;
|
||||
use core::cell::UnsafeCell;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
use libsys::{
|
||||
error::Errno,
|
||||
proc::{ExitCode, Pid, Tid},
|
||||
signal::Signal,
|
||||
};
|
||||
|
||||
pub use crate::arch::platform::context::{self, Context};
|
||||
|
||||
/// Convenience wrapper for [Thread] references
|
||||
pub type ThreadRef = Rc<Thread>;
|
||||
|
||||
/// List of possible process states
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum State {
|
||||
/// Process is ready to be executed and/or is scheduled for it
|
||||
Ready,
|
||||
/// Process is currently running or is in system call/interrupt handler
|
||||
Running,
|
||||
/// Process has finished execution and is waiting to be reaped
|
||||
Finished,
|
||||
/// Process is waiting for some external event
|
||||
Waiting,
|
||||
}
|
||||
|
||||
struct ThreadInner {
|
||||
id: Tid,
|
||||
state: State,
|
||||
owner: Option<Pid>,
|
||||
pending_wait: Option<&'static Wait>,
|
||||
wait_status: WaitStatus,
|
||||
signal_entry: usize,
|
||||
signal_stack: usize,
|
||||
}
|
||||
|
||||
/// Thread control data
|
||||
pub struct Thread {
|
||||
inner: IrqSafeSpinLock<ThreadInner>,
|
||||
exit_wait: Wait,
|
||||
exit_status: InitOnce<ExitCode>,
|
||||
pub(super) ctx: UnsafeCell<Context>,
|
||||
signal_ctx: UnsafeCell<Context>,
|
||||
signal_pending: AtomicU32,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
/// Returns currently active thread [Rc]-reference
|
||||
#[inline]
|
||||
pub fn current() -> ThreadRef {
|
||||
SCHED.current_thread()
|
||||
}
|
||||
|
||||
/// Returns `true` if the thread is currently executing a signal handler context
|
||||
pub fn is_handling_signal(&self) -> bool {
|
||||
self.signal_pending.load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
/// Returns a reference to thread `tid`, if it exists
|
||||
#[inline]
|
||||
pub fn get(tid: Tid) -> Option<ThreadRef> {
|
||||
THREADS.lock().get(&tid).cloned()
|
||||
}
|
||||
|
||||
/// Returns the owner process
|
||||
#[inline]
|
||||
pub fn owner(&self) -> Option<ProcessRef> {
|
||||
self.inner.lock().owner.and_then(Process::get)
|
||||
}
|
||||
|
||||
/// Returns [Pid] of the owner process
|
||||
pub fn owner_id(&self) -> Option<Pid> {
|
||||
self.inner.lock().owner
|
||||
}
|
||||
|
||||
/// Creates a new kernel process
|
||||
pub fn new_kernel(
|
||||
owner: Option<Pid>,
|
||||
entry: extern "C" fn(usize) -> !,
|
||||
arg: usize,
|
||||
) -> Result<ThreadRef, Errno> {
|
||||
let id = new_tid();
|
||||
|
||||
let res = Rc::new(Self {
|
||||
ctx: UnsafeCell::new(Context::kernel(entry as usize, arg)),
|
||||
signal_ctx: UnsafeCell::new(Context::empty()),
|
||||
signal_pending: AtomicU32::new(0),
|
||||
exit_wait: Wait::new("thread_exit"),
|
||||
exit_status: InitOnce::new(),
|
||||
inner: IrqSafeSpinLock::new(ThreadInner {
|
||||
signal_entry: 0,
|
||||
signal_stack: 0,
|
||||
id,
|
||||
owner,
|
||||
pending_wait: None,
|
||||
wait_status: WaitStatus::Done,
|
||||
state: State::Ready,
|
||||
}),
|
||||
});
|
||||
debugln!("New kernel thread: {:?}", id);
|
||||
assert!(THREADS.lock().insert(id, res.clone()).is_none());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Creates a new userspace process
|
||||
pub fn new_user(
|
||||
owner: Pid,
|
||||
entry: usize,
|
||||
stack: usize,
|
||||
arg: usize,
|
||||
ttbr0: usize,
|
||||
) -> Result<ThreadRef, Errno> {
|
||||
let id = new_tid();
|
||||
|
||||
let res = Rc::new(Self {
|
||||
ctx: UnsafeCell::new(Context::user(entry, arg, ttbr0, stack)),
|
||||
signal_ctx: UnsafeCell::new(Context::empty()),
|
||||
signal_pending: AtomicU32::new(0),
|
||||
exit_wait: Wait::new("thread_exit"),
|
||||
exit_status: InitOnce::new(),
|
||||
inner: IrqSafeSpinLock::new(ThreadInner {
|
||||
signal_entry: 0,
|
||||
signal_stack: 0,
|
||||
id,
|
||||
owner: Some(owner),
|
||||
pending_wait: None,
|
||||
wait_status: WaitStatus::Done,
|
||||
state: State::Ready,
|
||||
}),
|
||||
});
|
||||
debugln!("New userspace thread: {:?}", id);
|
||||
assert!(THREADS.lock().insert(id, res.clone()).is_none());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Creates a fork thread cloning `frame` context
|
||||
pub fn fork(
|
||||
owner: Option<Pid>,
|
||||
frame: &ForkFrame,
|
||||
space_phys: usize,
|
||||
) -> Result<ThreadRef, Errno> {
|
||||
let id = new_tid();
|
||||
|
||||
let res = Rc::new(Self {
|
||||
ctx: UnsafeCell::new(Context::fork(frame, space_phys)),
|
||||
signal_ctx: UnsafeCell::new(Context::empty()),
|
||||
signal_pending: AtomicU32::new(0),
|
||||
exit_wait: Wait::new("thread_exit"),
|
||||
exit_status: InitOnce::new(),
|
||||
inner: IrqSafeSpinLock::new(ThreadInner {
|
||||
signal_entry: 0,
|
||||
signal_stack: 0,
|
||||
id,
|
||||
owner,
|
||||
pending_wait: None,
|
||||
wait_status: WaitStatus::Done,
|
||||
state: State::Ready,
|
||||
}),
|
||||
});
|
||||
debugln!("Forked new user thread: {:?}", id);
|
||||
assert!(THREADS.lock().insert(id, res.clone()).is_none());
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Returns the thread ID
|
||||
#[inline]
|
||||
pub fn id(&self) -> Tid {
|
||||
self.inner.lock().id
|
||||
}
|
||||
|
||||
/// Schedules an initial thread for execution
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe: only allowed to be called once, repeated calls
|
||||
/// will generate undefined behavior
|
||||
pub unsafe fn enter(thread: ThreadRef) -> ! {
|
||||
// FIXME use some global lock to guarantee atomicity of thread entry?
|
||||
thread.inner.lock().state = State::Running;
|
||||
thread.current_context().enter()
|
||||
}
|
||||
|
||||
/// Schedules a next thread for execution
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Unsafe:
|
||||
///
|
||||
/// * Does not ensure src and dst threads are not the same thread
|
||||
/// * Does not ensure src is actually current context
|
||||
pub unsafe fn switch(src: ThreadRef, dst: ThreadRef, discard: bool) {
|
||||
{
|
||||
let mut src_lock = src.inner.lock();
|
||||
let mut dst_lock = dst.inner.lock();
|
||||
|
||||
if !discard {
|
||||
assert_eq!(src_lock.state, State::Running);
|
||||
src_lock.state = State::Ready;
|
||||
}
|
||||
// assert!(dst_lock.state == State::Ready || dst_lock.state == State::Waiting);
|
||||
dst_lock.state = State::Running;
|
||||
}
|
||||
|
||||
let src_ctx = src.current_context();
|
||||
let dst_ctx = dst.current_context();
|
||||
|
||||
(&mut *src_ctx).switch(&mut *dst_ctx);
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
fn current_context(&self) -> &mut Context {
|
||||
if self.signal_pending.load(Ordering::Acquire) != 0 {
|
||||
unsafe { &mut *self.signal_ctx.get() }
|
||||
} else {
|
||||
unsafe { &mut *self.ctx.get() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends current process with a "waiting" status
|
||||
pub fn enter_wait(&self) {
|
||||
let drop = {
|
||||
let mut lock = self.inner.lock();
|
||||
let drop = lock.state == State::Running;
|
||||
lock.state = State::Waiting;
|
||||
SCHED.dequeue(lock.id);
|
||||
drop
|
||||
};
|
||||
if drop {
|
||||
SCHED.switch(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Changes process wait condition status
|
||||
pub fn setup_wait(&self, wait: *const Wait) {
|
||||
#![allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||
let mut lock = self.inner.lock();
|
||||
// FIXME this is not cool
|
||||
lock.pending_wait = Some(unsafe { &*wait });
|
||||
lock.wait_status = WaitStatus::Pending;
|
||||
}
|
||||
|
||||
/// Suspends current thread until thread `tid` terminates
|
||||
pub fn waittid(tid: Tid) -> Result<(), Errno> {
|
||||
loop {
|
||||
let thread = THREADS
|
||||
.lock()
|
||||
.get(&tid)
|
||||
.cloned()
|
||||
.ok_or(Errno::DoesNotExist)?;
|
||||
|
||||
if thread.state() == State::Finished {
|
||||
// TODO remove thread from its parent?
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
thread.exit_wait.wait(None)?;
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates pending wait status
|
||||
pub fn set_wait_status(&self, status: WaitStatus) {
|
||||
let mut lock = self.inner.lock();
|
||||
lock.wait_status = status;
|
||||
}
|
||||
|
||||
/// Resets wait channel back to initial state
|
||||
pub fn reset_wait(&self) {
|
||||
let mut lock = self.inner.lock();
|
||||
lock.pending_wait = None;
|
||||
lock.wait_status = WaitStatus::Done;
|
||||
}
|
||||
|
||||
/// Returns status of the thread's pending wait
|
||||
pub fn wait_status(&self) -> WaitStatus {
|
||||
self.inner.lock().wait_status
|
||||
}
|
||||
|
||||
/// Switches current thread back from signal handler
|
||||
pub fn return_from_signal(&self) {
|
||||
if self.signal_pending.load(Ordering::Acquire) == 0 {
|
||||
panic!("TODO handle cases when returning from no signal");
|
||||
}
|
||||
self.signal_pending.store(0, Ordering::Release);
|
||||
|
||||
let src_ctx = self.signal_ctx.get();
|
||||
let dst_ctx = self.ctx.get();
|
||||
|
||||
assert_eq!(self.inner.lock().state, State::Running);
|
||||
|
||||
unsafe {
|
||||
(&mut *src_ctx).switch(&mut *dst_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the thread state
|
||||
#[inline]
|
||||
pub fn state(&self) -> State {
|
||||
self.inner.lock().state
|
||||
}
|
||||
|
||||
/// Sets the thread's owner process ID
|
||||
pub fn set_owner(&self, pid: Pid) {
|
||||
self.inner.lock().owner = Some(pid);
|
||||
}
|
||||
|
||||
/// Sets up values needed for signal entry
|
||||
pub fn set_signal_entry(&self, entry: usize, stack: usize) {
|
||||
let mut lock = self.inner.lock();
|
||||
lock.signal_entry = entry;
|
||||
lock.signal_stack = stack;
|
||||
}
|
||||
|
||||
/// Sets up a context for signal handler
|
||||
pub fn setup_signal(self: ThreadRef, signal: Signal, ttbr0: usize) {
|
||||
if self
|
||||
.signal_pending
|
||||
.compare_exchange_weak(0, signal as u32, Ordering::SeqCst, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
panic!("Already handling a signal (maybe handle this case)");
|
||||
}
|
||||
|
||||
let lock = self.inner.lock();
|
||||
if lock.signal_entry == 0 || lock.signal_stack == 0 {
|
||||
drop(lock);
|
||||
Process::exit_thread(self, ExitCode::from(-1));
|
||||
return;
|
||||
}
|
||||
|
||||
let signal_ctx = unsafe { &mut *self.signal_ctx.get() };
|
||||
|
||||
debugln!(
|
||||
"Signal entry: tid={:?}, pc={:#x}, sp={:#x}, ttbr0={:#x}",
|
||||
lock.id,
|
||||
lock.signal_entry,
|
||||
lock.signal_stack,
|
||||
ttbr0
|
||||
);
|
||||
|
||||
unsafe {
|
||||
signal_ctx.setup_signal_entry(
|
||||
lock.signal_entry,
|
||||
signal as usize,
|
||||
ttbr0,
|
||||
lock.signal_stack,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Switches process main thread to a signal handler
|
||||
pub fn enter_signal(self: ThreadRef, signal: Signal, ttbr0: usize) {
|
||||
let src_ctx = self.ctx.get();
|
||||
let signal_ctx = unsafe { &mut *self.signal_ctx.get() };
|
||||
|
||||
assert_eq!(self.state(), State::Running);
|
||||
self.setup_signal(signal, ttbr0);
|
||||
|
||||
unsafe {
|
||||
(&mut *src_ctx).switch(signal_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupts pending wait (from signal routines)
|
||||
pub fn interrupt_wait(&self, enqueue: bool) {
|
||||
let mut lock = self.inner.lock();
|
||||
let tid = lock.id;
|
||||
let wait = lock.pending_wait.take();
|
||||
drop(lock);
|
||||
if let Some(wait) = wait {
|
||||
wait.abort(tid, enqueue);
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans up any resources of the thread and aborts
|
||||
/// pending wait, if any
|
||||
pub fn terminate(&self, status: ExitCode) {
|
||||
let mut lock = self.inner.lock();
|
||||
lock.state = State::Finished;
|
||||
let tid = lock.id;
|
||||
let wait = lock.pending_wait.take();
|
||||
drop(lock);
|
||||
if let Some(wait) = wait {
|
||||
wait.abort(tid, false);
|
||||
}
|
||||
self.exit_status.init(status);
|
||||
self.exit_wait.wakeup_all();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Thread {
|
||||
fn drop(&mut self) {
|
||||
debugln!("Dropping process {:?}", self.id());
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a new thread ID
|
||||
pub fn new_tid() -> Tid {
|
||||
static LAST: AtomicU32 = AtomicU32::new(0);
|
||||
let id = LAST.fetch_add(1, Ordering::Relaxed);
|
||||
Tid::from(id)
|
||||
}
|
||||
+83
-66
@@ -2,25 +2,40 @@
|
||||
|
||||
use crate::arch::machine;
|
||||
use crate::dev::timer::TimestampSource;
|
||||
use crate::proc::{self, sched::SCHED, Process, ProcessRef};
|
||||
use crate::proc::{sched::SCHED, Thread, ThreadRef};
|
||||
use crate::sync::IrqSafeSpinLock;
|
||||
use alloc::collections::LinkedList;
|
||||
use core::time::Duration;
|
||||
use libsys::{error::Errno, stat::FdSet, proc::Pid};
|
||||
use libsys::{error::Errno, proc::Tid, stat::FdSet};
|
||||
|
||||
/// Wait channel structure. Contains a queue of processes
|
||||
/// waiting for some event to happen.
|
||||
pub struct Wait {
|
||||
queue: IrqSafeSpinLock<LinkedList<Pid>>,
|
||||
queue: IrqSafeSpinLock<LinkedList<Tid>>,
|
||||
#[allow(dead_code)]
|
||||
name: &'static str,
|
||||
}
|
||||
|
||||
/// Status of a (possibly) pending wait
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum WaitStatus {
|
||||
/// In progress
|
||||
Pending,
|
||||
/// Wait was interrupted by a signal
|
||||
Interrupted,
|
||||
/// Channel reported data available
|
||||
Done,
|
||||
}
|
||||
|
||||
struct Timeout {
|
||||
pid: Pid,
|
||||
tid: Tid,
|
||||
deadline: Duration,
|
||||
}
|
||||
|
||||
static TICK_LIST: IrqSafeSpinLock<LinkedList<Timeout>> = IrqSafeSpinLock::new(LinkedList::new());
|
||||
pub static WAIT_SELECT: Wait = Wait::new();
|
||||
/// Global wait channel for blocking on select. Gets notified
|
||||
/// of ANY I/O operations available, so not very efficient.
|
||||
pub static WAIT_SELECT: Wait = Wait::new("select");
|
||||
|
||||
/// Checks for any timed out wait channels and interrupts them
|
||||
pub fn tick() {
|
||||
@@ -30,9 +45,9 @@ pub fn tick() {
|
||||
|
||||
while let Some(item) = cursor.current() {
|
||||
if time > item.deadline {
|
||||
let pid = item.pid;
|
||||
let tid = item.tid;
|
||||
cursor.remove_current();
|
||||
SCHED.enqueue(pid);
|
||||
SCHED.enqueue(tid);
|
||||
} else {
|
||||
cursor.move_next();
|
||||
}
|
||||
@@ -42,7 +57,7 @@ pub fn tick() {
|
||||
/// Suspends current process for given duration
|
||||
pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Errno> {
|
||||
// Dummy wait descriptor which will never receive notifications
|
||||
static SLEEP_NOTIFY: Wait = Wait::new();
|
||||
static SLEEP_NOTIFY: Wait = Wait::new("sleep");
|
||||
let deadline = machine::local_timer().timestamp()? + timeout;
|
||||
match SLEEP_NOTIFY.wait(Some(deadline)) {
|
||||
Err(Errno::Interrupt) => {
|
||||
@@ -55,58 +70,53 @@ pub fn sleep(timeout: Duration, remaining: &mut Duration) -> Result<(), Errno> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspends current process until some file descriptor
|
||||
/// signals data available
|
||||
pub fn select(
|
||||
proc: ProcessRef,
|
||||
mut rfds: Option<&mut FdSet>,
|
||||
mut wfds: Option<&mut FdSet>,
|
||||
timeout: Option<Duration>,
|
||||
_thread: ThreadRef,
|
||||
_rfds: Option<&mut FdSet>,
|
||||
_wfds: Option<&mut FdSet>,
|
||||
_timeout: Option<Duration>,
|
||||
) -> Result<usize, Errno> {
|
||||
// TODO support wfds
|
||||
if wfds.is_some() || rfds.is_none() {
|
||||
todo!();
|
||||
}
|
||||
let read = rfds.as_deref().map(FdSet::clone);
|
||||
let write = wfds.as_deref().map(FdSet::clone);
|
||||
rfds.as_deref_mut().map(FdSet::reset);
|
||||
wfds.as_deref_mut().map(FdSet::reset);
|
||||
|
||||
let deadline = timeout.map(|v| v + machine::local_timer().timestamp().unwrap());
|
||||
let mut io = proc.io.lock();
|
||||
|
||||
loop {
|
||||
if let Some(read) = &read {
|
||||
for fd in read.iter() {
|
||||
let file = io.file(fd)?;
|
||||
if file.borrow().is_ready(false)? {
|
||||
rfds.as_mut().unwrap().set(fd);
|
||||
return Ok(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(write) = &write {
|
||||
for fd in write.iter() {
|
||||
let file = io.file(fd)?;
|
||||
if file.borrow().is_ready(true)? {
|
||||
wfds.as_mut().unwrap().set(fd);
|
||||
return Ok(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend
|
||||
match WAIT_SELECT.wait(deadline) {
|
||||
Err(Errno::TimedOut) => return Ok(0),
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => {}
|
||||
}
|
||||
}
|
||||
todo!();
|
||||
}
|
||||
|
||||
impl Wait {
|
||||
/// Constructs a new wait channel
|
||||
pub const fn new() -> Self {
|
||||
pub const fn new(name: &'static str) -> Self {
|
||||
Self {
|
||||
queue: IrqSafeSpinLock::new(LinkedList::new()),
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
/// Interrupt wait pending on the channel
|
||||
pub fn abort(&self, tid: Tid, enqueue: bool) {
|
||||
let mut queue = self.queue.lock();
|
||||
let mut tick_lock = TICK_LIST.lock();
|
||||
let mut cursor = tick_lock.cursor_front_mut();
|
||||
while let Some(item) = cursor.current() {
|
||||
if tid == item.tid {
|
||||
cursor.remove_current();
|
||||
break;
|
||||
} else {
|
||||
cursor.move_next();
|
||||
}
|
||||
}
|
||||
|
||||
let mut cursor = queue.cursor_front_mut();
|
||||
while let Some(item) = cursor.current() {
|
||||
if tid == *item {
|
||||
cursor.remove_current();
|
||||
let thread = Thread::get(tid).unwrap();
|
||||
thread.set_wait_status(WaitStatus::Interrupted);
|
||||
if enqueue {
|
||||
SCHED.enqueue(tid);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
cursor.move_next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,12 +125,12 @@ impl Wait {
|
||||
let mut queue = self.queue.lock();
|
||||
let mut count = 0;
|
||||
while limit != 0 && !queue.is_empty() {
|
||||
let pid = queue.pop_front();
|
||||
if let Some(pid) = pid {
|
||||
let tid = queue.pop_front();
|
||||
if let Some(tid) = tid {
|
||||
let mut tick_lock = TICK_LIST.lock();
|
||||
let mut cursor = tick_lock.cursor_front_mut();
|
||||
while let Some(item) = cursor.current() {
|
||||
if pid == item.pid {
|
||||
if tid == item.tid {
|
||||
cursor.remove_current();
|
||||
break;
|
||||
} else {
|
||||
@@ -129,8 +139,8 @@ impl Wait {
|
||||
}
|
||||
drop(tick_lock);
|
||||
|
||||
proc::process(pid).set_wait_flag(false);
|
||||
SCHED.enqueue(pid);
|
||||
Thread::get(tid).unwrap().set_wait_status(WaitStatus::Done);
|
||||
SCHED.enqueue(tid);
|
||||
}
|
||||
|
||||
limit -= 1;
|
||||
@@ -152,26 +162,33 @@ impl Wait {
|
||||
/// Suspends current process until event is signalled or
|
||||
/// (optional) deadline is reached
|
||||
pub fn wait(&self, deadline: Option<Duration>) -> Result<(), Errno> {
|
||||
let proc = Process::current();
|
||||
let thread = Thread::current();
|
||||
//let deadline = timeout.map(|t| machine::local_timer().timestamp().unwrap() + t);
|
||||
let mut queue_lock = self.queue.lock();
|
||||
|
||||
queue_lock.push_back(proc.id());
|
||||
proc.set_wait_flag(true);
|
||||
queue_lock.push_back(thread.id());
|
||||
thread.setup_wait(self);
|
||||
|
||||
if let Some(deadline) = deadline {
|
||||
TICK_LIST.lock().push_back(Timeout {
|
||||
pid: proc.id(),
|
||||
tid: thread.id(),
|
||||
deadline,
|
||||
});
|
||||
}
|
||||
|
||||
loop {
|
||||
if !proc.wait_flag() {
|
||||
return Ok(());
|
||||
}
|
||||
match thread.wait_status() {
|
||||
WaitStatus::Pending => {}
|
||||
WaitStatus::Done => {
|
||||
return Ok(());
|
||||
}
|
||||
WaitStatus::Interrupted => {
|
||||
return Err(Errno::Interrupt);
|
||||
}
|
||||
};
|
||||
|
||||
drop(queue_lock);
|
||||
proc.enter_wait();
|
||||
thread.enter_wait();
|
||||
queue_lock = self.queue.lock();
|
||||
|
||||
if let Some(deadline) = deadline {
|
||||
@@ -179,7 +196,7 @@ impl Wait {
|
||||
let mut cursor = queue_lock.cursor_front_mut();
|
||||
|
||||
while let Some(&mut item) = cursor.current() {
|
||||
if proc.id() == item {
|
||||
if thread.id() == item {
|
||||
cursor.remove_current();
|
||||
break;
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ impl<T> IrqSafeSpinLock<T> {
|
||||
#[inline(always)]
|
||||
unsafe fn force_release(&self) {
|
||||
self.state.store(false, Ordering::Release);
|
||||
cortex_a::asm::sev();
|
||||
// cortex_a::asm::sev();
|
||||
}
|
||||
|
||||
/// Returns [IrqSafeSpinLockGuard] for this lock
|
||||
@@ -47,7 +47,7 @@ impl<T> IrqSafeSpinLock<T> {
|
||||
let irq_state = unsafe { irq_mask_save() };
|
||||
|
||||
while self.try_lock().is_err() {
|
||||
cortex_a::asm::wfe();
|
||||
// cortex_a::asm::wfe();
|
||||
}
|
||||
|
||||
IrqSafeSpinLockGuard {
|
||||
|
||||
+183
-75
@@ -1,73 +1,227 @@
|
||||
//! System call argument ABI helpers
|
||||
|
||||
use crate::mem;
|
||||
use core::mem::size_of;
|
||||
use crate::arch::intrin;
|
||||
use crate::mem::{self, virt::table::Space};
|
||||
use crate::proc::Process;
|
||||
use core::alloc::Layout;
|
||||
use libsys::error::Errno;
|
||||
|
||||
fn translate(virt: usize) -> Option<usize> {
|
||||
let mut res: usize;
|
||||
unsafe {
|
||||
asm!("at s1e1r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
|
||||
// TODO _mut() versions checking whether pages are actually writable
|
||||
|
||||
macro_rules! invalid_memory {
|
||||
($($args:tt)+) => {
|
||||
warnln!($($args)+);
|
||||
#[cfg(feature = "aggressive_syscall")]
|
||||
{
|
||||
use libsys::signal::Signal;
|
||||
use crate::proc::Thread;
|
||||
|
||||
let thread = Thread::current();
|
||||
let proc = thread.owner().unwrap();
|
||||
proc.enter_fault_signal(thread, Signal::SegmentationFault);
|
||||
}
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
if res & 1 == 0 {
|
||||
Some(res & !(0xFFF | (0xFF << 56)))
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(target_arch = "aarch64")] {
|
||||
#[inline(always)]
|
||||
fn is_el0_accessible(virt: usize, write: bool) -> bool {
|
||||
use core::arch::asm;
|
||||
let mut res: usize;
|
||||
unsafe {
|
||||
if write {
|
||||
asm!("at s1e0w, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
|
||||
} else {
|
||||
asm!("at s1e0r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
|
||||
}
|
||||
}
|
||||
res & 1 == 0
|
||||
}
|
||||
} else {
|
||||
None
|
||||
#[inline(always)]
|
||||
fn is_el0_accessible(virt: usize, write: bool) -> bool {
|
||||
use crate::mem::virt::table::Entry;
|
||||
let proc = Process::current();
|
||||
proc.manipulate_space(|space| {
|
||||
let entry = space.read_last_level(virt & !0xFFF);
|
||||
if let Ok(entry) = entry {
|
||||
// TODO is_user_readable()?
|
||||
entry.is_user_writable() || !write
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps a slim structure pointer
|
||||
pub fn validate_user_ptr_struct<'a, T>(base: usize) -> Result<&'a mut T, Errno> {
|
||||
validate_user_ptr_struct_option(base).and_then(|e| e.ok_or(Errno::InvalidArgument))
|
||||
/// Checks given argument and interprets it as a `T` reference
|
||||
pub fn struct_ref<'a, T>(base: usize) -> Result<&'a T, Errno> {
|
||||
let layout = Layout::new::<T>();
|
||||
if base % layout.align() != 0 {
|
||||
invalid_memory!(
|
||||
"Structure pointer is misaligned: base={:#x}, expected {:?}",
|
||||
base,
|
||||
layout
|
||||
);
|
||||
}
|
||||
let bytes = buf_ref(base, layout.size())?;
|
||||
Ok(unsafe { &*(bytes.as_ptr() as *const T) })
|
||||
}
|
||||
|
||||
pub fn validate_user_ptr_struct_option<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
|
||||
/// Checks given argument and interprets it as a `T` mutable reference
|
||||
pub fn struct_mut<'a, T>(base: usize) -> Result<&'a mut T, Errno> {
|
||||
let layout = Layout::new::<T>();
|
||||
if base % layout.align() != 0 {
|
||||
invalid_memory!(
|
||||
"Structure pointer is misaligned: base={:#x}, expected {:?}",
|
||||
base,
|
||||
layout
|
||||
);
|
||||
}
|
||||
let bytes = buf_mut(base, layout.size())?;
|
||||
Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) })
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a `T` array buffer of size `count`
|
||||
pub fn struct_buf_ref<'a, T>(base: usize, count: usize) -> Result<&'a [T], Errno> {
|
||||
let layout = Layout::array::<T>(count).unwrap();
|
||||
if base % layout.align() != 0 {
|
||||
invalid_memory!(
|
||||
"Structure pointer is misaligned: base={:#x}, expected {:?}",
|
||||
base,
|
||||
layout
|
||||
);
|
||||
}
|
||||
let bytes = buf_ref(base, layout.size())?;
|
||||
Ok(unsafe { core::slice::from_raw_parts(bytes.as_ptr() as *const T, count) })
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a `T` array buffer of size `count`
|
||||
pub fn struct_buf_mut<'a, T>(base: usize, count: usize) -> Result<&'a mut [T], Errno> {
|
||||
let layout = Layout::array::<T>(count).unwrap();
|
||||
if base % layout.align() != 0 {
|
||||
invalid_memory!(
|
||||
"Structure pointer is misaligned: base={:#x}, expected {:?}",
|
||||
base,
|
||||
layout
|
||||
);
|
||||
}
|
||||
let bytes = buf_mut(base, layout.size())?;
|
||||
Ok(unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut T, count) })
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a `Option<&'a T>`
|
||||
pub fn option_struct_ref<'a, T>(base: usize) -> Result<Option<&'a T>, Errno> {
|
||||
if base == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
let bytes = validate_user_ptr(base, size_of::<T>())?;
|
||||
Ok(Some(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }))
|
||||
struct_ref(base).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps an user buffer reference
|
||||
pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> {
|
||||
/// Checks given argument and interprets it as a `Option<&'a mut T>`
|
||||
pub fn option_struct_mut<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
|
||||
if base == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
struct_mut(base).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a `Option<&'a mut [T]>`
|
||||
pub fn option_struct_buf_mut<'a, T>(
|
||||
base: usize,
|
||||
count: usize,
|
||||
) -> Result<Option<&'a mut [T]>, Errno> {
|
||||
if base == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
struct_buf_mut(base, count).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates that the argument pointer is accessible for requested operation
|
||||
/// for current process
|
||||
pub fn validate_ptr(base: usize, len: usize, write: bool) -> Result<(), Errno> {
|
||||
if base > mem::KERNEL_OFFSET || base + len > mem::KERNEL_OFFSET {
|
||||
warnln!(
|
||||
invalid_memory!(
|
||||
"User region refers to kernel memory: base={:#x}, len={:#x}",
|
||||
base,
|
||||
len
|
||||
);
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
let process = Process::current();
|
||||
|
||||
for i in (base / mem::PAGE_SIZE)..((base + len + mem::PAGE_SIZE - 1) / mem::PAGE_SIZE) {
|
||||
if translate(i * mem::PAGE_SIZE).is_none() {
|
||||
warnln!(
|
||||
"User region refers to unmapped memory: base={:#x}, len={:#x} (page {:#x})",
|
||||
if !is_el0_accessible(i * mem::PAGE_SIZE, write) {
|
||||
// It's possible a CoW page hasn't yet been cloned when trying
|
||||
// a write access
|
||||
let res = if write {
|
||||
process.manipulate_space(|space| {
|
||||
space.try_cow_copy(i * mem::PAGE_SIZE)?;
|
||||
unsafe {
|
||||
intrin::flush_tlb_virt(i * mem::PAGE_SIZE);
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
Err(Errno::DoesNotExist)
|
||||
};
|
||||
|
||||
if res.is_ok() {
|
||||
continue;
|
||||
}
|
||||
|
||||
invalid_memory!(
|
||||
"User region refers to inaccessible/unmapped memory: base={:#x}, len={:#x} (page {:#x}, write={})",
|
||||
base,
|
||||
len,
|
||||
i * mem::PAGE_SIZE
|
||||
i * mem::PAGE_SIZE,
|
||||
write
|
||||
);
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a byte buffer
|
||||
pub fn buf_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Errno> {
|
||||
validate_ptr(base, len, false)?;
|
||||
Ok(unsafe { core::slice::from_raw_parts(base as *const u8, len) })
|
||||
}
|
||||
|
||||
/// Checks given argument and interprets it as a mutable byte buffer
|
||||
pub fn buf_mut<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> {
|
||||
validate_ptr(base, len, true)?;
|
||||
Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) })
|
||||
}
|
||||
|
||||
/// Unwraps a nullable user buffer reference
|
||||
pub fn validate_user_ptr_null<'a>(base: usize, len: usize) -> Result<Option<&'a mut [u8]>, Errno> {
|
||||
/// Checks possibly NULL given argument and interprets it as a byte buffer
|
||||
pub fn option_buf_ref<'a>(base: usize, len: usize) -> Result<Option<&'a [u8]>, Errno> {
|
||||
if base == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
validate_user_ptr(base, len).map(Some)
|
||||
buf_ref(base, len).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks possibly NULL given argument and interprets it as a mutable byte buffer
|
||||
pub fn option_buf_mut<'a>(base: usize, len: usize) -> Result<Option<&'a mut [u8]>, Errno> {
|
||||
if base == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
buf_mut(base, len).map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwraps user string argument
|
||||
pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno> {
|
||||
let bytes = validate_user_ptr(base, len)?;
|
||||
pub fn str_ref<'a>(base: usize, len: usize) -> Result<&'a str, Errno> {
|
||||
let bytes = buf_ref(base, len)?;
|
||||
core::str::from_utf8(bytes).map_err(|_| {
|
||||
warnln!(
|
||||
"User string contains invalid UTF-8 characters: base={:#x}, len={:#x}",
|
||||
@@ -77,49 +231,3 @@ pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno>
|
||||
Errno::InvalidArgument
|
||||
})
|
||||
}
|
||||
// if base > mem::KERNEL_OFFSET {
|
||||
// warnln!("User string refers to kernel memory: base={:#x}", base);
|
||||
// return Err(Errno::InvalidArgument);
|
||||
// }
|
||||
//
|
||||
// let base_ptr = base as *const u8;
|
||||
// let mut len = 0;
|
||||
// let mut page_valid = false;
|
||||
// loop {
|
||||
// if len == limit {
|
||||
// warnln!("User string exceeded limit: base={:#x}", base);
|
||||
// return Err(Errno::InvalidArgument);
|
||||
// }
|
||||
//
|
||||
// if (base + len) % mem::PAGE_SIZE == 0 {
|
||||
// page_valid = false;
|
||||
// }
|
||||
//
|
||||
// if !page_valid && translate((base + len) & !0xFFF).is_none() {
|
||||
// warnln!(
|
||||
// "User string refers to unmapped memory: base={:#x}, off={:#x}",
|
||||
// base,
|
||||
// len
|
||||
// );
|
||||
// return Err(Errno::InvalidArgument);
|
||||
// }
|
||||
//
|
||||
// page_valid = true;
|
||||
//
|
||||
// let byte = unsafe { *base_ptr.add(len) };
|
||||
// if byte == 0 {
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// len += 1;
|
||||
// }
|
||||
//
|
||||
// let slice = unsafe { core::slice::from_raw_parts(base_ptr, len) };
|
||||
// core::str::from_utf8(slice).map_err(|_| {
|
||||
// warnln!(
|
||||
// "User string contains invalid UTF-8 characters: base={:#x}",
|
||||
// base
|
||||
// );
|
||||
// Errno::InvalidArgument
|
||||
// })
|
||||
// }
|
||||
|
||||
+350
-105
@@ -1,24 +1,33 @@
|
||||
//! System call implementation
|
||||
|
||||
use crate::arch::platform::exception::ExceptionFrame;
|
||||
use crate::arch::{machine, platform::ForkFrame};
|
||||
use crate::debug::Level;
|
||||
use crate::proc::{elf, wait, Process, ProcessIo};
|
||||
use crate::dev::timer::TimestampSource;
|
||||
use crate::fs::create_filesystem;
|
||||
use crate::mem::{
|
||||
phys::PageUsage,
|
||||
virt::table::{MapAttributes, Space},
|
||||
};
|
||||
use crate::proc::{self, elf, wait, Process, ProcessIo, Thread};
|
||||
use core::mem::size_of;
|
||||
use core::ops::DerefMut;
|
||||
use core::time::Duration;
|
||||
use libsys::{
|
||||
abi,
|
||||
abi::SystemCall,
|
||||
debug::TraceLevel,
|
||||
error::Errno,
|
||||
ioctl::IoctlCmd,
|
||||
proc::Pid,
|
||||
proc::{ExitCode, MemoryAccess, Pid, Tid},
|
||||
signal::{Signal, SignalDestination},
|
||||
stat::{FdSet, FileDescriptor, FileMode, OpenFlags, Stat, AT_EMPTY_PATH},
|
||||
stat::{
|
||||
AccessMode, DirectoryEntry, FdSet, FileDescriptor, FileMode, GroupId, MountOptions,
|
||||
OpenFlags, Stat, UserId, AT_EMPTY_PATH,
|
||||
},
|
||||
traits::{Read, Write},
|
||||
};
|
||||
use vfs::VnodeRef;
|
||||
|
||||
pub mod arg;
|
||||
pub use arg::*;
|
||||
|
||||
/// Creates a "fork" process from current one using its register frame.
|
||||
/// See [Process::fork()].
|
||||
@@ -27,7 +36,7 @@ pub use arg::*;
|
||||
///
|
||||
/// Unsafe: accepts and clones process states. Only legal to call
|
||||
/// from exception handlers.
|
||||
pub unsafe fn sys_fork(regs: &mut ExceptionFrame) -> Result<Pid, Errno> {
|
||||
pub unsafe fn sys_fork(regs: &mut ForkFrame) -> Result<Pid, Errno> {
|
||||
Process::current().fork(regs)
|
||||
}
|
||||
|
||||
@@ -50,19 +59,28 @@ fn find_at_node<T: DerefMut<Target = ProcessIo>>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Main system call dispatcher function
|
||||
pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
|
||||
fn _syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
|
||||
match num {
|
||||
// Process management system calls
|
||||
abi::SYS_EXIT => {
|
||||
Process::current().exit(args[0] as i32);
|
||||
unreachable!();
|
||||
}
|
||||
// I/O
|
||||
SystemCall::Read => {
|
||||
let proc = Process::current();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let mut io = proc.io.lock();
|
||||
let buf = arg::buf_mut(args[1], args[2])?;
|
||||
|
||||
// I/O system calls
|
||||
abi::SYS_OPENAT => {
|
||||
io.file(fd)?.borrow_mut().read(buf)
|
||||
}
|
||||
SystemCall::Write => {
|
||||
let proc = Process::current();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let mut io = proc.io.lock();
|
||||
let buf = arg::buf_ref(args[1], args[2])?;
|
||||
|
||||
io.file(fd)?.borrow_mut().write(buf)
|
||||
}
|
||||
SystemCall::Open => {
|
||||
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
|
||||
let path = validate_user_str(args[1], args[2])?;
|
||||
let path = arg::str_ref(args[1], args[2])?;
|
||||
let mode = FileMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?;
|
||||
let opts = OpenFlags::from_bits(args[4] as u32).ok_or(Errno::InvalidArgument)?;
|
||||
|
||||
@@ -78,34 +96,7 @@ pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
|
||||
let file = io.ioctx().open(at, path, mode, opts)?;
|
||||
Ok(u32::from(io.place_file(file)?) as usize)
|
||||
}
|
||||
abi::SYS_READ => {
|
||||
let proc = Process::current();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let mut io = proc.io.lock();
|
||||
let buf = validate_user_ptr(args[1], args[2])?;
|
||||
|
||||
io.file(fd)?.borrow_mut().read(buf)
|
||||
}
|
||||
abi::SYS_WRITE => {
|
||||
let proc = Process::current();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let mut io = proc.io.lock();
|
||||
let buf = validate_user_ptr(args[1], args[2])?;
|
||||
|
||||
io.file(fd)?.borrow_mut().write(buf)
|
||||
}
|
||||
abi::SYS_FSTATAT => {
|
||||
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
|
||||
let filename = validate_user_str(args[1], args[2])?;
|
||||
let buf = validate_user_ptr_struct::<Stat>(args[3])?;
|
||||
let flags = args[4] as u32;
|
||||
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
find_at_node(&mut io, at_fd, filename, flags & AT_EMPTY_PATH != 0)?.stat(buf)?;
|
||||
Ok(0)
|
||||
}
|
||||
abi::SYS_CLOSE => {
|
||||
SystemCall::Close => {
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
@@ -113,34 +104,20 @@ pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
|
||||
io.close_file(fd)?;
|
||||
Ok(0)
|
||||
}
|
||||
abi::SYS_EXECVE => {
|
||||
let node = {
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
let filename = validate_user_str(args[0], args[1])?;
|
||||
// TODO argv, envp array passing ABI?
|
||||
let node = io.ioctx().find(None, filename, true)?;
|
||||
drop(io);
|
||||
node
|
||||
};
|
||||
let file = node.open(OpenFlags::O_RDONLY)?;
|
||||
Process::execve(|space| elf::load_elf(space, file), 0).unwrap();
|
||||
panic!();
|
||||
}
|
||||
abi::SYS_WAITPID => {
|
||||
// TODO special "pid" values
|
||||
let pid = unsafe { Pid::from_raw(args[0] as u32) };
|
||||
let status = validate_user_ptr_struct::<i32>(args[1])?;
|
||||
SystemCall::FileStatus => {
|
||||
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
|
||||
let filename = arg::str_ref(args[1], args[2])?;
|
||||
let buf = arg::struct_mut::<Stat>(args[3])?;
|
||||
let flags = args[4] as u32;
|
||||
|
||||
match Process::waitpid(pid) {
|
||||
Ok(exit) => {
|
||||
*status = i32::from(exit);
|
||||
Ok(0)
|
||||
}
|
||||
_ => todo!(),
|
||||
}
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
let stat =
|
||||
find_at_node(&mut io, at_fd, filename, flags & AT_EMPTY_PATH != 0)?.stat()?;
|
||||
*buf = stat;
|
||||
Ok(0)
|
||||
}
|
||||
abi::SYS_IOCTL => {
|
||||
SystemCall::Ioctl => {
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let cmd = IoctlCmd::try_from(args[1] as u32)?;
|
||||
|
||||
@@ -150,40 +127,215 @@ pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
|
||||
let node = io.file(fd)?.borrow().node().ok_or(Errno::InvalidFile)?;
|
||||
node.ioctl(cmd, args[2], args[3])
|
||||
}
|
||||
SystemCall::Select => {
|
||||
let rfds = arg::option_struct_mut::<FdSet>(args[0])?;
|
||||
let wfds = arg::option_struct_mut::<FdSet>(args[1])?;
|
||||
let timeout = if args[2] == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Duration::from_nanos(args[2] as u64))
|
||||
};
|
||||
|
||||
// Extra system calls
|
||||
abi::SYS_EX_DEBUG_TRACE => {
|
||||
let buf = validate_user_ptr(args[0], args[1])?;
|
||||
print!(Level::Debug, "[trace] ");
|
||||
for &byte in buf.iter() {
|
||||
print!(Level::Debug, "{}", byte as char);
|
||||
}
|
||||
println!(Level::Debug, "");
|
||||
Ok(args[1])
|
||||
wait::select(Thread::current(), rfds, wfds, timeout)
|
||||
}
|
||||
abi::SYS_EX_NANOSLEEP => {
|
||||
let rem_buf = validate_user_ptr_null(args[1], size_of::<u64>() * 2)?;
|
||||
SystemCall::Access => {
|
||||
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
|
||||
let path = arg::str_ref(args[1], args[2])?;
|
||||
let mode = AccessMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?;
|
||||
let flags = args[4] as u32;
|
||||
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
|
||||
find_at_node(&mut io, at_fd, path, flags & AT_EMPTY_PATH != 0)?
|
||||
.check_access(io.ioctx(), mode)?;
|
||||
Ok(0)
|
||||
}
|
||||
SystemCall::ReadDirectory => {
|
||||
let proc = Process::current();
|
||||
let fd = FileDescriptor::from(args[0] as u32);
|
||||
let mut io = proc.io.lock();
|
||||
let buf = arg::struct_buf_mut::<DirectoryEntry>(args[1], args[2])?;
|
||||
|
||||
io.file(fd)?.borrow_mut().readdir(buf)
|
||||
}
|
||||
SystemCall::GetUserId => {
|
||||
let proc = Process::current();
|
||||
let uid = proc.io.lock().uid();
|
||||
Ok(u32::from(uid) as usize)
|
||||
}
|
||||
SystemCall::GetGroupId => {
|
||||
let proc = Process::current();
|
||||
let gid = proc.io.lock().gid();
|
||||
Ok(u32::from(gid) as usize)
|
||||
}
|
||||
SystemCall::DuplicateFd => {
|
||||
let src = FileDescriptor::from(args[0] as u32);
|
||||
let dst = FileDescriptor::from_i32(args[1] as i32)?;
|
||||
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
|
||||
let res = io.duplicate_file(src, dst)?;
|
||||
|
||||
Ok(u32::from(res) as usize)
|
||||
}
|
||||
SystemCall::SetUserId => {
|
||||
let uid = UserId::from(args[0] as u32);
|
||||
let proc = Process::current();
|
||||
proc.io.lock().set_uid(uid)?;
|
||||
Ok(0)
|
||||
}
|
||||
SystemCall::SetGroupId => {
|
||||
let gid = GroupId::from(args[0] as u32);
|
||||
let proc = Process::current();
|
||||
proc.io.lock().set_gid(gid)?;
|
||||
Ok(0)
|
||||
}
|
||||
SystemCall::SetCurrentDirectory => {
|
||||
let path = arg::str_ref(args[0], args[1])?;
|
||||
let proc = Process::current();
|
||||
proc.io.lock().ioctx().chdir(path)?;
|
||||
Ok(0)
|
||||
}
|
||||
SystemCall::GetCurrentDirectory => {
|
||||
todo!()
|
||||
}
|
||||
SystemCall::Seek => {
|
||||
todo!()
|
||||
}
|
||||
SystemCall::MapMemory => {
|
||||
let len = args[1];
|
||||
if len == 0 || (len & 0xFFF) != 0 {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
let acc = MemoryAccess::from_bits(args[2] as u32).ok_or(Errno::InvalidArgument)?;
|
||||
let _flags = MemoryAccess::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?;
|
||||
|
||||
let mut attrs = MapAttributes::SHARE_OUTER | MapAttributes::USER_READ;
|
||||
if !acc.contains(MemoryAccess::READ) {
|
||||
return Err(Errno::NotImplemented);
|
||||
}
|
||||
if acc.contains(MemoryAccess::WRITE) {
|
||||
if acc.contains(MemoryAccess::EXEC) {
|
||||
return Err(Errno::PermissionDenied);
|
||||
}
|
||||
attrs |= MapAttributes::USER_WRITE;
|
||||
} else {
|
||||
attrs |= MapAttributes::USER_READ;
|
||||
}
|
||||
if !acc.contains(MemoryAccess::EXEC) {
|
||||
attrs |= MapAttributes::USER_EXEC;
|
||||
}
|
||||
|
||||
// TODO don't ignore flags
|
||||
let usage = PageUsage::UserPrivate;
|
||||
|
||||
let proc = Process::current();
|
||||
|
||||
proc.manipulate_space(move |space| {
|
||||
space.allocate(0x100000000, 0xF00000000, len / 4096, attrs, usage)
|
||||
})
|
||||
}
|
||||
SystemCall::UnmapMemory => {
|
||||
let addr = args[0];
|
||||
let len = args[1];
|
||||
|
||||
if addr == 0 || len == 0 || addr & 0xFFF != 0 || len & 0xFFF != 0 {
|
||||
return Err(Errno::InvalidArgument);
|
||||
}
|
||||
|
||||
let proc = Process::current();
|
||||
proc.manipulate_space(move |space| space.free(addr, len / 4096))?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
// Process
|
||||
SystemCall::Clone => {
|
||||
let entry = args[0];
|
||||
let stack = args[1];
|
||||
let arg = args[2];
|
||||
|
||||
Process::current()
|
||||
.new_user_thread(entry, stack, arg)
|
||||
.map(|e| u32::from(e) as usize)
|
||||
}
|
||||
SystemCall::Exec => {
|
||||
let filename = arg::str_ref(args[0], args[1])?;
|
||||
let argv = arg::struct_buf_ref::<&str>(args[2], args[3])?;
|
||||
// Validate each argument as well
|
||||
for item in argv.iter() {
|
||||
arg::validate_ptr(item.as_ptr() as usize, item.len(), false)?;
|
||||
}
|
||||
let node = {
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
// TODO argv, envp array passing ABI?
|
||||
let node = io.ioctx().find(None, filename, true)?;
|
||||
drop(io);
|
||||
node
|
||||
};
|
||||
let file = node.open(OpenFlags::O_RDONLY)?;
|
||||
Process::execve(move |space| elf::load_elf(space, file), argv).unwrap();
|
||||
panic!();
|
||||
}
|
||||
SystemCall::Exit => {
|
||||
let status = ExitCode::from(args[0] as i32);
|
||||
let flags = args[1];
|
||||
|
||||
if flags & (1 << 0) != 0 {
|
||||
Process::exit_thread(Thread::current(), status);
|
||||
} else {
|
||||
Process::current().exit(status);
|
||||
}
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
SystemCall::WaitPid => {
|
||||
// TODO special "pid" values
|
||||
let pid = Pid::try_from(args[0] as u32)?;
|
||||
let status = arg::struct_mut::<i32>(args[1])?;
|
||||
|
||||
match Process::waitpid(pid) {
|
||||
Ok(exit) => {
|
||||
*status = i32::from(exit);
|
||||
Ok(0)
|
||||
}
|
||||
e => e.map(|e| i32::from(e) as usize),
|
||||
}
|
||||
}
|
||||
SystemCall::WaitTid => {
|
||||
let tid = Tid::from(args[0] as u32);
|
||||
|
||||
match Thread::waittid(tid) {
|
||||
Ok(_) => Ok(0),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
SystemCall::GetPid => Ok(u32::from(Process::current().id()) as usize),
|
||||
SystemCall::GetTid => Ok(u32::from(Thread::current().id()) as usize),
|
||||
SystemCall::Sleep => {
|
||||
let rem_buf = arg::option_struct_buf_mut::<u64>(args[1], size_of::<u64>() * 2)?;
|
||||
let mut rem = Duration::new(0, 0);
|
||||
let res = wait::sleep(Duration::from_nanos(args[0] as u64), &mut rem);
|
||||
if res == Err(Errno::Interrupt) {
|
||||
warnln!("Sleep interrupted, {:?} remaining", rem);
|
||||
if rem_buf.is_some() {
|
||||
todo!()
|
||||
if let Some(rem_buf) = rem_buf {
|
||||
rem_buf[0] = rem.as_secs();
|
||||
rem_buf[1] = rem.subsec_nanos() as u64;
|
||||
}
|
||||
}
|
||||
res.map(|_| 0)
|
||||
}
|
||||
abi::SYS_EX_SIGNAL => {
|
||||
let proc = Process::current();
|
||||
proc.setup_signal_context(args[0], args[1]);
|
||||
SystemCall::SetSignalEntry => {
|
||||
Thread::current().set_signal_entry(args[0], args[1]);
|
||||
Ok(0)
|
||||
}
|
||||
abi::SYS_EX_SIGRETURN => {
|
||||
let proc = Process::current();
|
||||
proc.return_from_signal();
|
||||
panic!("This code won't run");
|
||||
SystemCall::SignalReturn => {
|
||||
Thread::current().return_from_signal();
|
||||
unreachable!();
|
||||
}
|
||||
abi::SYS_EX_KILL => {
|
||||
SystemCall::SendSignal => {
|
||||
let target = SignalDestination::from(args[0] as isize);
|
||||
let signal = Signal::try_from(args[1] as u32)?;
|
||||
|
||||
@@ -196,25 +348,118 @@ pub fn syscall(num: usize, args: &[usize]) -> Result<usize, Errno> {
|
||||
};
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
abi::SYS_SELECT => {
|
||||
let rfds = validate_user_ptr_struct_option::<FdSet>(args[0])?;
|
||||
let wfds = validate_user_ptr_struct_option::<FdSet>(args[1])?;
|
||||
let timeout = if args[2] == 0 {
|
||||
None
|
||||
SystemCall::Yield => {
|
||||
proc::switch();
|
||||
Ok(0)
|
||||
}
|
||||
SystemCall::GetSid => {
|
||||
// TODO handle kernel processes here?
|
||||
let pid = Pid::to_option(args[0] as u32);
|
||||
let current = Process::current();
|
||||
let proc = if let Some(pid) = pid {
|
||||
let proc = Process::get(pid).ok_or(Errno::DoesNotExist)?;
|
||||
if proc.sid() != current.sid() {
|
||||
return Err(Errno::PermissionDenied);
|
||||
}
|
||||
proc
|
||||
} else {
|
||||
Some(Duration::from_nanos(args[2] as u64))
|
||||
current
|
||||
};
|
||||
|
||||
Ok(u32::from(proc.sid()) as usize)
|
||||
}
|
||||
SystemCall::GetPgid => {
|
||||
// TODO handle kernel processes here?
|
||||
let pid = Pid::to_option(args[0] as u32);
|
||||
let current = Process::current();
|
||||
let proc = if let Some(pid) = pid {
|
||||
Process::get(pid).ok_or(Errno::DoesNotExist)?
|
||||
} else {
|
||||
current
|
||||
};
|
||||
|
||||
Ok(u32::from(proc.pgid()) as usize)
|
||||
}
|
||||
SystemCall::GetPpid => Ok(u32::from(Process::current().ppid().unwrap()) as usize),
|
||||
SystemCall::SetSid => {
|
||||
let proc = Process::current();
|
||||
wait::select(proc, rfds, wfds, timeout)
|
||||
let mut io = proc.io.lock();
|
||||
|
||||
if let Some(_ctty) = io.ctty() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
let id = proc.id();
|
||||
proc.set_sid(id);
|
||||
Ok(u32::from(id) as usize)
|
||||
}
|
||||
SystemCall::SetPgid => {
|
||||
let pid = Pid::to_option(args[0] as u32);
|
||||
let pgid = Pid::to_option(args[1] as u32);
|
||||
|
||||
let current = Process::current();
|
||||
let proc = if let Some(_pid) = pid {
|
||||
todo!()
|
||||
} else {
|
||||
current
|
||||
};
|
||||
|
||||
if let Some(_pgid) = pgid {
|
||||
todo!();
|
||||
} else {
|
||||
proc.set_pgid(proc.id());
|
||||
}
|
||||
|
||||
Ok(u32::from(proc.pgid()) as usize)
|
||||
}
|
||||
|
||||
_ => {
|
||||
let proc = Process::current();
|
||||
errorln!("Undefined system call: {}", num);
|
||||
proc.enter_signal(Signal::InvalidSystemCall);
|
||||
todo!()
|
||||
// System
|
||||
SystemCall::GetCpuTime => {
|
||||
let time = machine::local_timer().timestamp()?;
|
||||
Ok(time.as_nanos() as usize)
|
||||
}
|
||||
SystemCall::Mount => {
|
||||
let target = arg::str_ref(args[0], args[1])?;
|
||||
let options = arg::struct_ref::<MountOptions>(args[2])?;
|
||||
|
||||
let proc = Process::current();
|
||||
let mut io = proc.io.lock();
|
||||
|
||||
debugln!("mount(target={:?}, options={:#x?})", target, options);
|
||||
|
||||
let target_node = io.ioctx().find(None, target, true)?;
|
||||
let root = create_filesystem(options)?;
|
||||
|
||||
target_node.mount(root)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
// Debugging
|
||||
SystemCall::DebugTrace => {
|
||||
let level = TraceLevel::from_repr(args[0])
|
||||
.map(Level::from)
|
||||
.ok_or(Errno::InvalidArgument)?;
|
||||
let buf = arg::str_ref(args[1], args[2])?;
|
||||
let thread = Thread::current();
|
||||
let proc = thread.owner().unwrap();
|
||||
println!(level, "[trace {:?}:{:?}] {}", proc.id(), thread.id(), buf);
|
||||
Ok(args[1])
|
||||
}
|
||||
|
||||
// Handled elsewhere
|
||||
SystemCall::Fork => unreachable!(),
|
||||
_ => panic!("Unimplemented: {:?}", num),
|
||||
}
|
||||
}
|
||||
|
||||
/// Main system call dispatcher function
|
||||
pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
|
||||
let thread = Thread::current();
|
||||
let process = thread.owner().unwrap();
|
||||
let result = _syscall(num, args);
|
||||
if !thread.is_handling_signal() {
|
||||
process.handle_pending_signals();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
+17
-2
@@ -20,6 +20,16 @@ impl<T> InitOnce<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the initialized value, if one is present.
|
||||
/// Returns [None] otherwise.
|
||||
pub fn as_ref_option(&self) -> Option<&T> {
|
||||
if self.is_initialized() {
|
||||
Some(self.get())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this [InitOnce<T>] can be used
|
||||
#[inline(always)]
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
@@ -29,8 +39,13 @@ impl<T> InitOnce<T> {
|
||||
/// Returns the initialized value. Will panic if the value has not
|
||||
/// yet been initialized.
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
#[track_caller]
|
||||
pub fn get(&self) -> &mut T {
|
||||
assert!(self.is_initialized(), "Access to uninitialized InitOnce<T>");
|
||||
assert!(
|
||||
self.is_initialized(),
|
||||
"Access to uninitialized InitOnce<T>: {:?}",
|
||||
core::panic::Location::caller()
|
||||
);
|
||||
unsafe { (*self.inner.get()).assume_init_mut() }
|
||||
}
|
||||
|
||||
@@ -70,6 +85,6 @@ macro_rules! block {
|
||||
}};
|
||||
|
||||
($cond:expr, $timeout:expr) => {
|
||||
crate::block!($cond, $timeout, return Err(error::Errno::TimedOut))
|
||||
crate::block!($cond, $timeout, return Err(libsys::error::Errno::TimedOut))
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user