initial kernel code

This commit is contained in:
husky 2025-09-08 20:52:06 -07:00
commit 4c299a4491
93 changed files with 7405 additions and 0 deletions

12
.cargo/config Normal file
View file

@ -0,0 +1,12 @@
[target.powerpc-unknown-linux-gnu]
linker = "powerpc-elf-gcc"
ar = "powerpc-elf-ar"
rustflags = [
"-C", "link-args=-nostdlib -ffreestanding -mbig-endian -fPIC",
"-C", "target-feature=+crt-static"
]
[tarcet.riscv32i-unknown-none-elf]
rustflags = [
"-C", "link-args=-nostdlib -ffreestanding -fPIC",
]

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/target
/.idea
/test.img
/test_ppc.img
/mount

48
Cargo.lock generated Normal file
View file

@ -0,0 +1,48 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "cc"
version = "1.2.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "compiler_builtins"
version = "0.1.160"
source = "git+https://github.com/rust-lang/compiler-builtins#a6145905160801fb2a6ac45aab3f1ceeeabc15a8"
[[package]]
name = "ddi"
version = "0.1.0"
[[package]]
name = "find-msvc-tools"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
[[package]]
name = "lbos"
version = "0.1.0"
dependencies = [
"cc",
"compiler_builtins",
"ddi",
"liblbos",
]
[[package]]
name = "liblbos"
version = "0.1.0"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"

41
Cargo.toml Normal file
View file

@ -0,0 +1,41 @@
[workspace]
members = ["liblbos"]
exclude = ["turntable", "makeddi", "ddi", "example"]
[package]
name = "lbos"
version = "0.1.0"
edition = "2024"
[dependencies]
compiler_builtins = { git = "https://github.com/rust-lang/compiler-builtins", features = ["mem"], optional = true }
liblbos = { path = "liblbos" }
ddi = { path = "ddi", default-features = false }
[profile.dev]
panic = "abort"
opt-level = "s"
debug-assertions = false
overflow-checks = false
[profile.release]
panic = "abort"
opt-level = "z"
debug-assertions = false
overflow-checks = false
strip = false
lto = true
codegen-units = 1
[build-dependencies]
cc = "1.0"
[features]
default = []
debug_messages = []
arch_virt = ["dev_virtio", "fs_fat32", "liblbos/arch_riscv32", "ddi/arch_riscv32"]
arch_ofw = []
arch_ppc32 = ["compiler_builtins", "arch_ofw", "fs_fat32"]
dev_virtio = []
fs_fat32 = []

13
LICENSE Normal file
View file

@ -0,0 +1,13 @@
Copyright 2025 Real Microsoft, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this repository except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

89
README.md Normal file
View file

@ -0,0 +1,89 @@
# lifeblood os
### extreme warning: this project is in an INCREDIBLY early stage, see **warnings** section
lifeblood os is the operating system for the lifeblood project
it currently supports riscv32imac, and has some basic support for ppc32 (albeit it probably won't compile, and
has no drivers)
## warnings
- as of right now, this project has just gotten to the point where it can run the shell and run programs from it.
please keep this in mind.
- the design of the kernel is *very esoteric*, this is mainly due to it being targeted for systems with very limited
memory, as the lifeblood computer was originally designed around 32K of ram (although we're currently redesigning
to include more, and the qemu emulator version requires 5MiB to run)
- tons of stuff is missing, notably if you develop a program, you may run into a relocation issue due to the limitations
of the lbos relocator.
- speaking of which, if you do not have experience with low level programming, you may currently find it difficult to
write programs, however still feel free to explore the code and see what you can do!
## building
install the rust toolchain for riscv32imac-unknown-none-elf, then run the following commands for each thing you want
to build:
### kernel
```shell
LBOS_ARCH=virt cargo build --release --target riscv32imac-unknown-none-elf --features arch_virt
```
### TURNTBL.DDI
```shell
cd turntable
LBOS_ARCH=riscv32 cargo build --release --target riscv32imac-unknown-none-elf --features arch_riscv32
cd ..
# the following is required to convert the ELF output to the DDI format that lbos uses
cd makeddi
run -- ../turntable/target/riscv32imac-unknown-none-elf/release/turntable ../turntable/target/riscv32imac-unknown-none-elf/release/TURNTBL.DDI
cd ..
```
### EXAMPLE.DDI
```shell
cd example
LBOS_ARCH=riscv32 cargo build --release --target riscv32imac-unknown-none-elf --features arch_riscv32
cd ..
# the following is required to convert the ELF output to the DDI format that lbos uses
cd makeddi
run -- ../example/target/riscv32imac-unknown-none-elf/release/example ../example/target/riscv32imac-unknown-none-elf/release/EXAMPLE.DDI
cd ..
```
## getting it running
you'll need to install qemu with support for riscv32, and make a fat32 filesystem image with the shell "TURNTBL.DDI"
and optionally the "EXAMPLE.DDI" program.
**NOTE FOR FAT32 FILESYSTEM:** it is currently required that the sector size is 512 bytes, this is hardcoded; cluster
size is not hardcoded.
the best way to do this is to run something like
```shell
dd if=/dev/zero of=test.img bs=1M count=256 status=progress
mkdosfs -F 32 test.img
sudo losetup -fP test.img
```
and then run the `./copy_to_fs.sh` script to copy the built files to the filesystem like so
```shell
./copy_to_fs.sh /dev/loop0 # or whatever the loopback device you created is called, see losetup -a
```
note that the script will likely need to be run as root because of linux's requirements for fat32 filesystems; and more
importantly that the script expects the binary files to be in the directories above in the build section.
after you do this, the filesystem should be ready for the kernel and you should be able to run a variant of the
following to run the kernel
```shell
qemu-system-riscv32 -machine virt -bios none -drive if=none,format=raw,file=/dev/loop0,id=disk1 -device virtio-blk-device,drive=disk1 -serial mon:stdio -m 5M -kernel target/riscv32imac-unknown-none-elf/release/lbos
```
(note that this will take over your terminal and steal CTRL+C, so you'll need to kill it by closing the terminal or
manually killing the process)
## developing programs
currently, the best way to program is to use rust and link to the liblbos library included in this repo,
you should be able to using something like
```toml
[dependencies]
liblbos = { git = "https://forge.voremicrocomputers.com/Vore_Microcomputers/lifeblood_os.git" }
```
but i have not tested this yet so please tell me if it doesn't work
if you want to develop a program in another language, you'll need to investigate everything in this repo to figure out
how lbos expects programs to look. (this will be documented eventually)
good places to start with either developing in rust, or porting another language are:
- turntable, the lbos shell which showcases some basic fs operations and task loading
- example, a simple hello world program that should give you the bare minimum to build and link correctly
- liblbos, a helper library that defines structs (all should be C repr) as well as helper functions for syscalls and
kernel IPC
feel free to shoot me an email if you have any questions! (nikocs at voremicrocomputers dot com)

33
build.rs Normal file
View file

@ -0,0 +1,33 @@
fn main() {
let arch = std::env::var("LBOS_ARCH").unwrap_or("virt".to_string());
println!("cargo:rerun-if-env-changed=LBOS_ARCH");
println!("cargo:rustc-cfg=feature=\"arch_{}\"", arch);
println!("cargo:rerun-if-changed=src/arch/{}/asm", arch);
if arch == "ppc32" {
let mut cc_build = cc::Build::new();
let cc_build = cc_build
.file(format!("src/arch/{}/asm/realmode.S", arch))
.file(format!("src/arch/{}/asm/trap.S", arch));
match &arch[..] {
"ppc32" => {
cc_build
.flag("-mcpu=powerpc")
.flag("-m32")
.flag("-nostdlib")
.flag("-msoft-float");
},
_ => panic!("unknown architecture: {}", arch),
}
cc_build.compile("vap_asm");
// link to the assembly file
println!("cargo:rustc-link-lib=static=vap_asm");
}
// specify the linker.ld script
println!("cargo:rustc-link-arg=-Tsrc/arch/{arch}/asm/linker.ld");
}

16
copy_to_fs.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
storage_device="${1}"
umount "${storage_device}"
echo "MOUNTING..."
mkdir -p mount
mount "${storage_device}" mount
echo "COPYING..."
cp -v turntable/target/riscv32imac-unknown-none-elf/release/TURNTBL.DDI mount
cp -v example/target/riscv32imac-unknown-none-elf/release/EXAMPLE.DDI mount
echo "UNMOUNTING..."
umount "${storage_device}"
echo "COMPLETE"

2
ddi/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

7
ddi/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ddi"
version = "0.1.0"

10
ddi/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "ddi"
version = "0.1.0"
edition = "2024"
[dependencies]
[features]
default = ["arch_riscv32"]
arch_riscv32 = []

2
ddi/src/arch/mod.rs Normal file
View file

@ -0,0 +1,2 @@
#[cfg(feature = "arch_riscv32")]
pub mod riscv32;

109
ddi/src/arch/riscv32/mod.rs Normal file
View file

@ -0,0 +1,109 @@
use crate::DDIRelocationHeader;
#[repr(u16)]
pub enum RiscVRelocationType {
None = 0,
// word32, sym + addend
R32 = 1,
// word64, sym + addend
R64 = 2,
// usize, base + addend
Relative = 3,
// direct copy
Copy = 4,
// not yet handled
JumpSlot = 5,
TlsDtpMod32 = 6,
TlsDtpMod64 = 7,
TlsDtpRel32 = 8,
TlsDtpRel64 = 9,
TlsTpRel32 = 10,
TlsTpRel64 = 11,
// 12 bit PC relative branch
// btype, sym + addend - position
Branch = 16,
// 20 bit PC relative jump
// jtype, sym + addend - position
Jal = 17,
// 32 bit PC relative call
// u+i type pair, sym + addend - position
Call = 18,
// should be same as call
CallPLT = 19,
// high 20 bits of 32 bit PC relative GOT access
// u type, symbol got offset + got address + addend - position
GOTHi20 = 20,
// should be same as GOT
TlsGOTHi20 = 21,
// should be same as GOT
TlsGDHi20 = 22,
// high 20 bits of 32 bit pc relative reference
// u type, sym + addend - position
PCRelHi20 = 23,
// low 12 bits of 32 bit pc relative reference, addend always 0
// i type, sym - position
PCRelLo12I = 24,
// low 12 bits of 32 bit pc relative reference, addend always 0
// s type sym - position
PCRelLo12S = 25,
// high 20 bits of 32 bit absolute address
// u type, sym + addend
Hi20 = 26,
// low 12 bits of 32 bit absolute address
// i type, sym + addend
Lo12I = 27,
// low 12 bits of 32 bit absolute address
// s type, sym + addend
Lo12S = 28,
}
fn patch_hi20_uins(mut instruction: u32, mut pointer: u32) -> u32 {
// check if LO is going to result in a negative number
let lo = pointer & 0xFFF;
if lo & 0x800 != 0 {
// lo is negative
// add 0x1000 to hi
pointer += 0x1000;
}
let hi = (pointer & 0xFFFFF000) & 0xFFFFF000;
instruction = (instruction & !0xFFFFF000) | hi;
instruction
}
fn patch_lo12_iins(mut instruction: u32, pointer: u32) -> u32 {
let lo = ((pointer & 0xFFF) << 20) & 0xFFF00000;
instruction = (instruction & !0xFFF00000) | lo;
instruction
}
fn sap_addr(target_pointer: u32, target_segment_base: u32, relocation_pointer: u32, current_segment_base: u32) -> u32 {
(target_pointer).wrapping_sub(relocation_pointer)
}
pub fn apply_relocation(segment_buffer: &mut [u8], current_segment_base: u32, target_segment_base: u32, relocation_header: &DDIRelocationHeader, unhandled_callback: fn(u16)) {
match relocation_header.relocation_type {
x if x == RiscVRelocationType::None as u16 => {
// do nothing
}
x if x == RiscVRelocationType::CallPLT as u16 || x == RiscVRelocationType::Call as u16 => {
// these are relative, do nothing for now
}
x if x == RiscVRelocationType::Hi20 as u16 => {
let u_ins_ptr = relocation_header.relocation_pointer as usize;
let mut u_ins = u32::from_le_bytes(segment_buffer[u_ins_ptr..u_ins_ptr+4].try_into().unwrap());
let addr = relocation_header.target_pointer as u32 + target_segment_base;
u_ins = patch_hi20_uins(u_ins, addr);
segment_buffer[u_ins_ptr..u_ins_ptr+4].copy_from_slice(&u_ins.to_le_bytes());
}
x if x == RiscVRelocationType::Lo12I as u16 => {
let i_ins_ptr = relocation_header.relocation_pointer as usize;
let mut i_ins = u32::from_le_bytes(segment_buffer[i_ins_ptr..i_ins_ptr+4].try_into().unwrap());
let addr = relocation_header.target_pointer as u32 + target_segment_base;
i_ins = patch_lo12_iins(i_ins, addr);
segment_buffer[i_ins_ptr..i_ins_ptr+4].copy_from_slice(&i_ins.to_le_bytes());
}
x => {
unhandled_callback(x);
}
}
}

126
ddi/src/lib.rs Normal file
View file

@ -0,0 +1,126 @@
#![no_std]
//! どこでも一緒、いつまでも!〜
pub mod arch;
#[repr(C)]
pub struct DDIHeader {
pub magic: [u8; 4], // "ddI\0"
pub version: u8,
pub ddi_type: u8,
}
#[repr(u8)]
pub enum DDIType {
LBOSRiscV = b'R',
}
#[repr(C)]
pub struct DDILBOSHeader {
pub entrypoint: u32,
pub segment_header_pointer: u32,
pub segment_header_count: u32,
pub relocation_header_pointer: u32,
pub relocation_header_count: u32,
pub entrypoint_segment: u32,
}
#[repr(C)]
pub struct DDISegmentHeader {
pub segment_pointer: u32,
pub segment_size: u32,
}
#[repr(C)]
pub struct DDIRelocationHeader {
pub relocation_segment: u32, // what segment this relocation is for
pub relocation_pointer: u32, // offset from the start of the segment
pub target_segment: u32, // what segment this relocation points to
pub target_pointer: u16, // offset from the start of the target segment
pub relocation_type: u16, // arch specific, see arch/riscv32/mod.rs
}
pub fn read_ddi_header(data: &[u8]) -> Option<DDIHeader> {
if data.len() < size_of::<DDIHeader>() {
return None;
}
Some(DDIHeader {
magic: data[0..4].try_into().ok()?,
version: data[4],
ddi_type: match data[5] {
x if x == DDIType::LBOSRiscV as u8 => DDIType::LBOSRiscV as u8,
_ => return None,
}
})
}
pub fn write_ddi_header(data: &mut [u8], header: &DDIHeader) {
data[0..4].copy_from_slice(&header.magic);
data[4] = header.version;
data[5] = header.ddi_type;
}
pub fn read_ddi_lbos_header(data: &[u8]) -> Option<DDILBOSHeader> {
if data.len() < size_of::<DDIHeader>() + size_of::<DDILBOSHeader>() {
return None;
}
let header_offset = size_of::<DDIHeader>();
Some(DDILBOSHeader {
entrypoint: u32::from_be_bytes((&data[header_offset..header_offset+4]).try_into().ok()?),
segment_header_pointer: u32::from_be_bytes((&data[header_offset+4..header_offset+8]).try_into().ok()?),
segment_header_count: u32::from_be_bytes((&data[header_offset+8..header_offset+12]).try_into().ok()?),
relocation_header_pointer: u32::from_be_bytes((&data[header_offset+12..header_offset+16]).try_into().ok()?),
relocation_header_count: u32::from_be_bytes((&data[header_offset+16..header_offset+20]).try_into().ok()?),
entrypoint_segment: u32::from_be_bytes((&data[header_offset+20..header_offset+24]).try_into().ok()?),
})
}
pub fn write_ddi_lbos_header(data: &mut [u8], header: &DDILBOSHeader) {
let header_offset = size_of::<DDIHeader>();
data[header_offset..header_offset+4].copy_from_slice(&header.entrypoint.to_be_bytes());
data[header_offset+4..header_offset+8].copy_from_slice(&header.segment_header_pointer.to_be_bytes());
data[header_offset+8..header_offset+12].copy_from_slice(&header.segment_header_count.to_be_bytes());
data[header_offset+12..header_offset+16].copy_from_slice(&header.relocation_header_pointer.to_be_bytes());
data[header_offset+16..header_offset+20].copy_from_slice(&header.relocation_header_count.to_be_bytes());
data[header_offset+20..header_offset+24].copy_from_slice(&header.entrypoint_segment.to_be_bytes());
}
pub fn read_ddi_segment_header(data: &[u8], header: &DDILBOSHeader, index: usize) -> Option<DDISegmentHeader> {
if data.len() < header.segment_header_pointer as usize + size_of::<DDISegmentHeader>() + (index * size_of::<DDISegmentHeader>()) {
return None;
}
let header_offset = header.segment_header_pointer as usize + (index * size_of::<DDISegmentHeader>());
Some(DDISegmentHeader {
segment_pointer: u32::from_be_bytes((&data[header_offset..header_offset+4]).try_into().ok()?),
segment_size: u32::from_be_bytes((&data[header_offset+4..header_offset+8]).try_into().ok()?),
})
}
pub fn write_ddi_segment_header(data: &mut [u8], header: &DDILBOSHeader, index: usize, datum: &DDISegmentHeader) {
let header_offset = header.segment_header_pointer as usize + (index * size_of::<DDISegmentHeader>());
data[header_offset..header_offset+4].copy_from_slice(&datum.segment_pointer.to_be_bytes());
data[header_offset+4..header_offset+8].copy_from_slice(&datum.segment_size.to_be_bytes());
}
pub fn read_ddi_relocation_header(data: &[u8], header: &DDILBOSHeader, index: usize) -> Option<DDIRelocationHeader> {
if data.len() < header.relocation_header_pointer as usize + size_of::<DDIRelocationHeader>() + (index * size_of::<DDIRelocationHeader>()) {
return None;
}
let header_offset = header.relocation_header_pointer as usize + (index * size_of::<DDIRelocationHeader>());
Some(DDIRelocationHeader {
relocation_segment: u32::from_be_bytes((&data[header_offset..header_offset+4]).try_into().ok()?),
relocation_pointer: u32::from_be_bytes((&data[header_offset+4..header_offset+8]).try_into().ok()?),
target_segment: u32::from_be_bytes((&data[header_offset+8..header_offset+12]).try_into().ok()?),
target_pointer: u16::from_be_bytes((&data[header_offset+12..header_offset+14]).try_into().ok()?),
relocation_type: u16::from_be_bytes((&data[header_offset+14..header_offset+16]).try_into().ok()?),
})
}
pub fn write_ddi_relocation_header(data: &mut [u8], header: &DDILBOSHeader, index: usize, datum: &DDIRelocationHeader) {
let header_offset = header.relocation_header_pointer as usize + (index * size_of::<DDIRelocationHeader>());
data[header_offset..header_offset+4].copy_from_slice(&datum.relocation_segment.to_be_bytes());
data[header_offset+4..header_offset+8].copy_from_slice(&datum.relocation_pointer.to_be_bytes());
data[header_offset+8..header_offset+12].copy_from_slice(&datum.target_segment.to_be_bytes());
data[header_offset+12..header_offset+14].copy_from_slice(&datum.target_pointer.to_be_bytes());
data[header_offset+14..header_offset+16].copy_from_slice(&datum.relocation_type.to_be_bytes());
}

106
ddi/src/main.rs Normal file
View file

@ -0,0 +1,106 @@
use ddi::{
DDIType, read_ddi_header, read_ddi_lbos_header, read_ddi_relocation_header,
read_ddi_segment_header,
};
use ddi::arch::riscv32::{apply_relocation, RiscVRelocationType};
pub fn main() {
let args = std::env::args().collect::<Vec<_>>();
let input = args.get(1).expect("no input file");
let buf = std::fs::read(input).expect("failed to read file");
let header = read_ddi_header(&buf);
if let Some(header) = header {
println!("UWU header:");
if header.magic == *b"ddI\0" {
println!("magic: ddI\\0 (valid)");
} else {
println!("magic: invalid");
}
match &header.ddi_type {
x if *x == DDIType::LBOSRiscV as u8 => {
println!("type: lbos riscv");
}
_ => {
println!("type: invalid");
}
}
println!("version: {}", header.version);
let mut segment_buffers = Vec::new();
let mut fake_segment_bases = Vec::new();
let mut last_base = 0x80023C00;
match header.ddi_type {
x if x == DDIType::LBOSRiscV as u8 => {
if let Some(lbos) = read_ddi_lbos_header(&buf) {
println!("entrypoint: {:x}", lbos.entrypoint);
println!("segments:");
for i in 0..lbos.segment_header_count as usize {
let header = read_ddi_segment_header(&buf, &lbos, i);
if let Some(header) = header {
println!(" segment {}:", i);
println!(" located at {}", header.segment_pointer);
println!(" size: {}", header.segment_size);
segment_buffers.push(
buf[header.segment_pointer as usize
..(header.segment_pointer + header.segment_size) as usize]
.to_vec(),
);
fake_segment_bases.push(last_base);
last_base += 0x1000;
} else {
println!("invalid fvb segment header at {i}");
continue;
}
}
println!("relocations:");
for i in 0..lbos.relocation_header_count as usize {
let header = read_ddi_relocation_header(&buf, &lbos, i);
if let Some(header) = header {
println!(" relocation {}:", i);
println!(" segment: {}", header.relocation_segment);
println!(" pointer: {:x}", header.relocation_pointer);
println!(" target: {}", header.target_segment);
println!(" target pointer: {:x}", header.target_pointer);
println!(" reltype: {}", header.relocation_type);
let initial_instruction = &segment_buffers[header.relocation_segment as usize][header.relocation_pointer as usize..header.relocation_pointer as usize +4];
let second_instruction = if header.relocation_type == RiscVRelocationType::CallPLT as u16 || header.relocation_type == RiscVRelocationType::Call as u16 {
&segment_buffers[header.relocation_segment as usize][header.relocation_pointer as usize + 4..header.relocation_pointer as usize + 8]
} else {
initial_instruction
};
println!(" initial instruction: {:x} {:x} {:x} {:x}", initial_instruction[0], initial_instruction[1], initial_instruction[2], initial_instruction[3]);
if header.relocation_type == RiscVRelocationType::CallPLT as u16 || header.relocation_type == RiscVRelocationType::Call as u16 {
println!(" second instruction: {:x} {:x} {:x} {:x}", second_instruction[0], second_instruction[1], second_instruction[2], second_instruction[3]);
}
let current_segment_base = fake_segment_bases[header.relocation_segment as usize];
let target_segment_base = fake_segment_bases[header.target_segment as usize];
apply_relocation(&mut segment_buffers[header.target_segment as usize], current_segment_base, target_segment_base, &header, |x| {
println!(" unhandled relocation: {:x}", x);
});
let final_instruction = &segment_buffers[header.relocation_segment as usize][header.relocation_pointer as usize..header.relocation_pointer as usize +4];
let second_final_instruction = if header.relocation_type == RiscVRelocationType::CallPLT as u16 || header.relocation_type == RiscVRelocationType::Call as u16 {
&segment_buffers[header.relocation_segment as usize][header.relocation_pointer as usize + 4..header.relocation_pointer as usize + 8]
} else {
final_instruction
};
println!(" (relocating to: {:x})", target_segment_base);
println!(" final instruction: {:x} {:x} {:x} {:x}", final_instruction[0], final_instruction[1], final_instruction[2], final_instruction[3]);
if header.relocation_type == RiscVRelocationType::CallPLT as u16 || header.relocation_type == RiscVRelocationType::Call as u16 {
println!(" second final instruction: {:x} {:x} {:x} {:x}", second_final_instruction[0], second_final_instruction[1], second_final_instruction[2], second_final_instruction[3]);
}
}
}
} else {
println!("invalid header");
}
}
_ => {
println!("invalid header");
}
}
} else {
println!("no ddi header");
}
}

2
example/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

14
example/Cargo.lock generated Normal file
View file

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "example"
version = "0.1.0"
dependencies = [
"liblbos",
]
[[package]]
name = "liblbos"
version = "0.1.0"

25
example/Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "example"
version = "0.1.0"
edition = "2024"
[dependencies]
liblbos = { path = "../liblbos" }
[profile.dev]
panic = "abort"
opt-level = "s"
debug-assertions = false
overflow-checks = false
[profile.release]
panic = "abort"
opt-level = "z"
debug-assertions = false
overflow-checks = false
lto = true
codegen-units = 1
[features]
default = ["arch_riscv32"]
arch_riscv32 = ["liblbos/arch_riscv32"]

16
example/build.rs Normal file
View file

@ -0,0 +1,16 @@
fn main() {
let arch = std::env::var("LBOS_ARCH").unwrap_or("riscv32".to_string());
println!("cargo:rerun-if-env-changed=LBOS_ARCH");
println!("cargo:rustc-cfg=feature=\"arch_{}\"", arch);
println!("cargo:rerun-if-changed=src/arch/{}/asm", arch);
// specify the linker.ld script
println!("cargo:rustc-link-arg=-Tsrc/arch/{arch}/asm/linker.ld");
// output relocation info
println!("cargo:rustc-link-arg=--emit-relocs");
// don't page align sections
println!("cargo:rustc-link-arg=-n");
}

2
example/src/arch/mod.rs Normal file
View file

@ -0,0 +1,2 @@
#[cfg(feature = "arch_riscv32")]
mod riscv32;

View file

@ -0,0 +1,34 @@
OUTPUT_FORMAT( "elf32-littleriscv" )
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
program (wxa) : ORIGIN = 0x0, LENGTH = 20480
}
PHDRS
{
prg PT_LOAD ;
}
SECTIONS {
.text : {
. = ALIGN(512);
*(.text .text.*)
} >program AT>program :prg
.rodata : {
*(.rodata .rodata.*)
} >program AT>program :prg
.data : {
*(.sdata .sdata.*) *(.data .data.*)
} >program AT>program :prg
.bss : {
*(.sbss .sbss.*)
*(.bss .bss.*)
} >program AT>program :prg
}

View file

@ -0,0 +1,10 @@
use crate::main;
#[unsafe(no_mangle)]
extern "C" fn _start() -> isize {
unsafe {
main();
}
0
}

17
example/src/main.rs Normal file
View file

@ -0,0 +1,17 @@
#![no_std]
#![no_main]
use crate::terminal::println;
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
liblbos::syscalls::write_terminal(b"abort\n");
loop {}
}
mod arch;
mod terminal;
#[unsafe(no_mangle)]
extern "C" fn main() {
println("hello world!");
}

10
example/src/terminal.rs Normal file
View file

@ -0,0 +1,10 @@
pub fn print(s: &str) {
unsafe {
liblbos::syscalls::write_terminal(s.as_bytes());
}
}
pub fn println(s: &str) {
print(s);
print("\n");
}

2
liblbos/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

7
liblbos/Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "liblbos"
version = "0.1.0"

10
liblbos/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "liblbos"
version = "0.1.0"
edition = "2024"
[dependencies]
[features]
default = []
arch_riscv32 = []

16
liblbos/src/arch/mod.rs Normal file
View file

@ -0,0 +1,16 @@
use crate::syscalls::{sc2usize, SysCall};
#[cfg(feature = "arch_riscv32")]
pub mod riscv32;
pub fn stall() {
#[cfg(feature = "arch_riscv32")]
riscv32::stall();
}
pub fn syscall(sc: SysCall, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize) -> usize {
#[cfg(feature = "arch_riscv32")]
{
riscv32::virt_syscall(sc2usize(sc) as _, a1 as _, a2 as _, a3 as _, a4 as _, a5 as _, a6 as _) as _
}
}

View file

@ -0,0 +1,7 @@
.option pic
.option norvc
.section .text
.global _syscall
_syscall:
ecall
ret

View file

@ -0,0 +1,17 @@
use core::arch::{asm, global_asm};
global_asm!(include_str!("asm/syscall.s"));
pub fn stall() {
unsafe {
asm!("wfi");
}
}
unsafe extern "C" {
fn _syscall(syscall_num: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32, a6: u32) -> u32;
}
pub fn virt_syscall(num: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32, a6: u32) -> u32 {
unsafe { _syscall(num, a1, a2, a3, a4, a5, a6) }
}

55
liblbos/src/fs.rs Normal file
View file

@ -0,0 +1,55 @@
#[repr(C)]
pub struct FileSystem {
pub unknown: [u8; 32]
}
impl FileSystem {
pub fn empty() -> Self {
Self {
unknown: [0; 32]
}
}
}
#[repr(C)]
pub struct DirectoryReader {
pub unknown: [u8; 32]
}
impl DirectoryReader {
pub fn empty() -> Self {
Self {
unknown: [0; 32]
}
}
}
#[repr(C)]
pub struct FileReader {
pub unknown: [u8; 32]
}
impl FileReader {
pub fn empty() -> Self {
Self {
unknown: [0; 32]
}
}
}
#[repr(u8)]
pub enum RecordType {
None = 0,
Directory = 1,
File = 2,
Unknown = 3,
}
#[repr(C)]
pub struct Record {
pub name: [u8; 12],
pub record_type: u8,
/// this is fs implementation specific, don't trust this value outside of kernel
pub target: u32,
pub total_size_bytes: u32,
}

310
liblbos/src/ktask.rs Normal file
View file

@ -0,0 +1,310 @@
#[repr(u16)]
pub enum KTaskRequestType {
DoNothing = 0,
/// prints a message to the terminal greeting the user (:
SayHi = 1,
/// readies the filesystem for use
/// takes a FileSystem struct as defined in crate::fs, this struct contains unknown members.
/// that depend on what hbd filesystem the kernel was compiled with, but will always be 32 bytes.
/// it's probably best to stack allocate it in the calling procees.
/// 0 indicates success, 1 indicates failure
FSOpen = 2,
/// takes a string, interprets it as an absolute filesystem path, and opens it as a directory reader.
/// requires a previous call to FSOpen.
/// 0 indicates success,
/// 1 = directory not found
/// 2 = invalid record (i.e. you tried to open a file as a directory)
/// 3 = user error (you probably didn't prefix the path with '/')
FSOpenDir = 3,
/// takes a directory reader and an empty record struct, and reads one record from the directory into it.
/// requires a previous call to FSOpenDir
/// 0 = success
/// 1 = end of directory
/// 2 = other error
FSReadDir = 4,
/// takes a file record and an empty file reader struct, and readies the file reader struct for reading
/// requires a previous call to fsopen
/// 0 = success
/// 1 = failure
FSOpenFile = 5,
/// reads data from the file reader into the provided buffer, until either the buffer is full or the file is at the end.
/// requires a previous call to FSOpenFile
/// returns success on all calls, including the last one; if EOF is reached, the buffer was not touched
/// 0 = success
/// 1 = end of file
/// 2 = other error
FSReadFile = 6,
/// seeks forward N sectors in the file.
/// requires a previous call to FSOpenFile
/// 0 = success
/// 1 = failure (i.e. cannot seek due to EOF)
FSSeek = 7,
/// given a DDI file buffer, loads the file into memory and returns the required information to execute it
/// after loading, you can free the file buffer without concern
/// 0 = success
/// 1 = failure
LoadDDIFile = 8,
}
#[repr(C)]
pub struct KTaskRequest {
pub req: u16,
pub replyto: u8,
pub retval: u32,
}
#[repr(C)]
pub struct KTaskFSOpenRequest {
pub base: KTaskRequest,
pub fs: *mut crate::fs::FileSystem,
}
#[repr(C)]
pub struct KTaskFSOpenDirRequest {
pub base: KTaskRequest,
pub fs: *const crate::fs::FileSystem,
pub dir: *mut crate::fs::DirectoryReader,
pub path: *const u8,
pub pathlen: u32,
}
#[repr(C)]
pub struct KTaskFSReadDirRequest {
pub base: KTaskRequest,
pub fs: *const crate::fs::FileSystem,
pub dir: *mut crate::fs::DirectoryReader,
pub record: *mut crate::fs::Record,
}
#[repr(C)]
pub struct KTaskFSOpenFileRequest {
pub base: KTaskRequest,
pub fs: *const crate::fs::FileSystem,
pub record: *const crate::fs::Record,
pub reader: *mut crate::fs::FileReader,
}
#[repr(C)]
pub struct KTaskFSReadFileRequest {
pub base: KTaskRequest,
pub fs: *const crate::fs::FileSystem,
pub reader: *mut crate::fs::FileReader,
pub buffer: *mut u8,
pub bufferlen: u32,
}
#[repr(C)]
pub struct KTaskFSSeek {
pub base: KTaskRequest,
pub fs: *const crate::fs::FileSystem,
pub reader: *mut crate::fs::FileReader,
pub sectors: u32,
}
#[repr(C)]
pub struct KTaskLoadDDIFile {
pub base: KTaskRequest,
pub file_buf_pointer: usize,
pub file_buf_len: usize,
pub task_setup_ptr: usize,
}
pub fn request_type_from_req(req: u16) -> KTaskRequestType {
match req {
0 => KTaskRequestType::DoNothing,
1 => KTaskRequestType::SayHi,
2 => KTaskRequestType::FSOpen,
3 => KTaskRequestType::FSOpenDir,
4 => KTaskRequestType::FSReadDir,
5 => KTaskRequestType::FSOpenFile,
6 => KTaskRequestType::FSReadFile,
7 => KTaskRequestType::FSSeek,
8 => KTaskRequestType::LoadDDIFile,
_ => KTaskRequestType::DoNothing,
}
}
pub fn query_ktask(req: usize) {
crate::syscalls::send_notification(0, req);
crate::syscalls::wait_for_notif_ack(0);
let tmp = crate::syscalls::wait_for_notification();
if tmp != req {
crate::syscalls::write_terminal(b"ktask failed to respond properly!\n");
}
}
pub fn ktask_sayhi() {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskRequest) };
req.req = KTaskRequestType::SayHi as u16;
req.replyto = ci;
}
query_ktask(req);
crate::syscalls::free_blocks(req, 1);
}
pub fn ktask_fsopen(fs: &mut crate::fs::FileSystem) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSOpenRequest) };
req.base.req = KTaskRequestType::FSOpen as u16;
req.base.replyto = ci;
req.fs = fs as *mut _;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSOpenRequest) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_fsopendir(
fs: &crate::fs::FileSystem,
dir: &mut crate::fs::DirectoryReader,
path: &[u8],
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSOpenDirRequest) };
req.base.req = KTaskRequestType::FSOpenDir as u16;
req.base.replyto = ci;
req.fs = fs as *const _;
req.dir = dir as *mut _;
req.path = path.as_ptr();
req.pathlen = path.len() as u32;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSOpenDirRequest) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_fsreaddir(
fs: &crate::fs::FileSystem,
dir: &mut crate::fs::DirectoryReader,
record: &mut crate::fs::Record,
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSReadDirRequest) };
req.base.req = KTaskRequestType::FSReadDir as u16;
req.base.replyto = ci;
req.fs = fs as *const _;
req.dir = dir as *mut _;
req.record = record as *mut _;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSReadDirRequest) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_fsopenfile(
fs: &crate::fs::FileSystem,
record: &crate::fs::Record,
reader: &mut crate::fs::FileReader,
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSOpenFileRequest) };
req.base.req = KTaskRequestType::FSOpenFile as u16;
req.base.replyto = ci;
req.fs = fs as *const _;
req.record = record as *const _;
req.reader = reader as *mut _;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSOpenFileRequest) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_fsreadfile(
fs: &crate::fs::FileSystem,
reader: &mut crate::fs::FileReader,
buffer: &mut [u8],
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSReadFileRequest) };
req.base.req = KTaskRequestType::FSReadFile as u16;
req.base.replyto = ci;
req.fs = fs as *const _;
req.reader = reader as *mut _;
req.buffer = buffer.as_mut_ptr();
req.bufferlen = buffer.len() as u32;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSReadFileRequest) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_fsseek(
fs: &crate::fs::FileSystem,
reader: &mut crate::fs::FileReader,
sectors: u32,
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskFSSeek) };
req.base.req = KTaskRequestType::FSSeek as u16;
req.base.replyto = ci;
req.fs = fs as *const _;
req.reader = reader as *mut _;
req.sectors = sectors;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskFSSeek) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}
pub fn ktask_loadddifile(
file_buf_pointer: usize,
file_buf_len: usize,
task_setup_ptr: usize,
) -> usize {
let ci = crate::syscalls::current_task();
let req = crate::syscalls::alloc_blocks(1);
{
let req = unsafe { &mut *(req as *mut KTaskLoadDDIFile) };
req.base.req = KTaskRequestType::LoadDDIFile as u16;
req.base.replyto = ci;
req.file_buf_pointer = file_buf_pointer;
req.file_buf_len = file_buf_len;
req.task_setup_ptr = task_setup_ptr;
}
query_ktask(req);
let retval = {
let req = unsafe { &mut *(req as *mut KTaskLoadDDIFile) };
req.base.retval
};
crate::syscalls::free_blocks(req, 1);
retval as usize
}

14
liblbos/src/lib.rs Normal file
View file

@ -0,0 +1,14 @@
#![no_std]
pub mod fs;
pub mod ktask;
pub mod syscalls;
mod arch;
#[repr(C)]
pub struct TaskSetup {
pub epc: usize,
pub ddi_first_addr: usize,
pub ddi_size: usize,
pub wait_for_task_exit: bool,
}

164
liblbos/src/syscalls.rs Normal file
View file

@ -0,0 +1,164 @@
use crate::arch::{stall, syscall};
use crate::TaskSetup;
#[repr(C)]
pub struct KernelInfo {
pub current_process_count: usize,
pub total_mem_blocks: usize,
pub free_mem_blocks: usize,
}
#[repr(u16)]
pub enum KTaskNotificationRequest {
DoNothing = 0,
SayHi = 1,
}
#[repr(u32)]
pub enum SysCall {
/// does absolutely nothing
NoAction = 0,
/// provide pointer to kernel info struct in first arg and it will be filled
KernelInfo = 1,
/// provide TaskSetup pointer in first arg and task will spawn, returns new task id
CreateTask = 2,
/// the currently executing task will exit
ExitTask = 3,
/// returns the current task id
CurrentTask = 4,
/// takes all available characters in the INBUF, places them in the given buffer, and returns the number of characters placed in the buffer
ReadInbuf = 5,
/// writes the given N characters in the buffer to the serial port
WriteTerminal = 6,
/// allocates N blocks of contiguous memory and returns the first block
AllocBlocks = 7,
/// frees N blocks starting at the given block
FreeBlocks = 8,
/// reads N sectors from the hard block device and writes them to the given block, this syscall will block the current task
/// returns 0 on success and 1 on failure
ReadHBD = 9,
/// sends a notification to the given task.
/// (taskid, addr) -> ret
/// returns 0 on success and 1 on failure (i.e. taskid is invalid)
/// addr is one block of memory.
/// calling this syscall will NOT block the current process, you must call WaitForNotification to
/// receive your reply.
/// the point at which you are allowed to free the block is dependent on the task that is waiting for the notification,
/// however in general it is not safe to free the block until a WaitForNotifAck is called.
/// IMPORTANT NOTE:
/// sending multiple notifications to a single task without calling WaitForNotifAck is NOT SUPPORTED
/// doing this results in undefined behavior
SendNotification = 10,
/// waits for a notification to arrive
/// () -> addr
/// this will block indefinitely until a notification arrives,^
/// use PendingNotifications to check if there are any pending notifications if you want to avoid blocking.
/// return value is an address to one block of memory.
/// this return value should also be used as a method of identified associated notifications,
/// i.e. in a system like the following
/// TASK1 - - - TASK2 - - - TASK1
/// send recv,send recv
/// the same block should be used for all notifications, and overwritten repeatedly
WaitForNotification = 11,
/// returns number of pending notifications
PendingNotifications = 12,
/// waits for a sent notification to be received
/// (taskid) -> 0
WaitForNotifAck = 13,
/// initializes kernel, ONLY CALL ONCE! IN FACT, YOU PROBABLY NEVER NEED TO CALL THIS
InitKernel = 666
}
pub fn sc2usize(sc: SysCall) -> usize {
sc as usize
}
pub fn usize2sc(u: usize) -> SysCall {
match u {
0 => SysCall::NoAction,
1 => SysCall::KernelInfo,
2 => SysCall::CreateTask,
3 => SysCall::ExitTask,
4 => SysCall::CurrentTask,
5 => SysCall::ReadInbuf,
6 => SysCall::WriteTerminal,
7 => SysCall::AllocBlocks,
8 => SysCall::FreeBlocks,
9 => SysCall::ReadHBD,
10 => SysCall::SendNotification,
11 => SysCall::WaitForNotification,
12 => SysCall::PendingNotifications,
13 => SysCall::WaitForNotifAck,
666 => SysCall::InitKernel,
_ => SysCall::NoAction,
}
}
pub fn kinfo() -> KernelInfo {
let mut kinfo = KernelInfo {
current_process_count: 0,
total_mem_blocks: 0,
free_mem_blocks: 0,
};
syscall(SysCall::KernelInfo, &mut kinfo as *mut KernelInfo as usize, 0, 0, 0, 0, 0);
kinfo
}
pub fn create_task(ts: TaskSetup) -> u8 {
syscall(SysCall::CreateTask, &ts as *const TaskSetup as usize, 0, 0, 0, 0, 0) as u8
}
pub fn exit() -> ! {
syscall(SysCall::ExitTask, 0, 0, 0, 0, 0, 0);
loop {
stall();
}
}
pub fn current_task() -> u8 {
syscall(SysCall::CurrentTask, 0, 0, 0, 0, 0, 0) as u8
}
pub fn read_inbuf(buf: &mut [u8]) -> usize {
syscall(SysCall::ReadInbuf, buf.as_mut_ptr() as usize, buf.len(), 0, 0, 0, 0)
}
pub fn alloc_blocks(n: usize) -> usize {
syscall(SysCall::AllocBlocks, n, 0, 0, 0, 0, 0)
}
pub fn free_blocks(addr: usize, n: usize) {
syscall(SysCall::FreeBlocks, addr, n, 0, 0, 0, 0);
}
pub fn write_terminal(buf: &[u8]) {
syscall(SysCall::WriteTerminal, buf.as_ptr() as usize, buf.len(), 0, 0, 0, 0);
}
pub fn read_hbd(sector: usize, buf_addr: usize, count: usize) -> usize {
syscall(SysCall::ReadHBD, sector, buf_addr, count, 0, 0, 0)
}
pub fn send_notification(taskid: u8, addr: usize) {
syscall(SysCall::SendNotification, taskid as usize, addr, 0, 0, 0, 0);
}
pub fn wait_for_notification() -> usize {
syscall(SysCall::WaitForNotification, 0, 0, 0, 0, 0, 0)
}
pub fn pending_notifications() -> usize {
syscall(SysCall::PendingNotifications, 0, 0, 0, 0, 0, 0)
}
pub fn wait_for_notif_ack(taskid: u8) {
syscall(SysCall::WaitForNotifAck, taskid as usize, 0, 0, 0, 0, 0);
}
pub fn init_kernel() {
syscall(SysCall::InitKernel, 0, 0, 0, 0, 0, 0);
}

15
load_ppc.sh Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
storage_device="${1}"
umount "${storage_device}"
echo "MOUNTING..."
mkdir -p /mnt/lbos-mnt
mount "${storage_device}" /mnt/lbos-mnt
echo "COPYING..."
cp -v target/powerpc-unknown-linux-gnu/release/lbos /mnt/lbos-mnt
echo "UNMOUNTING..."
umount "${storage_device}"
echo "COMPLETE"

2
makeddi/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

21
makeddi/Cargo.lock generated Normal file
View file

@ -0,0 +1,21 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ddi"
version = "0.1.0"
[[package]]
name = "elf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070"
[[package]]
name = "makeddi"
version = "0.1.0"
dependencies = [
"ddi",
"elf",
]

8
makeddi/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "makeddi"
version = "0.1.0"
edition = "2024"
[dependencies]
elf = "0.8.0"
ddi = { path = "../ddi" }

179
makeddi/src/main.rs Normal file
View file

@ -0,0 +1,179 @@
use std::collections::BTreeMap;
use elf::{abi, ElfBytes};
use elf::endian::LittleEndian;
use ddi::{write_ddi_header, write_ddi_lbos_header, write_ddi_relocation_header, write_ddi_segment_header, DDIHeader, DDILBOSHeader, DDIRelocationHeader, DDISegmentHeader, DDIType};
fn main() {
let args = std::env::args().collect::<Vec<_>>();
let input = args.get(1).expect("input file required as first argument");
let output = args
.get(2)
.expect("output file required as second argument");
let raw = std::fs::read(input).expect("failed to read input file");
let elf = ElfBytes::<LittleEndian>::minimal_parse(&raw).expect(
"failed to parse input file as ELF",
);
let mut obuf = vec![0u8; size_of::<DDIHeader>()];
let header = DDIHeader {
magic: *b"ddI\0",
version: 1,
ddi_type: DDIType::LBOSRiscV as u8,
};
write_ddi_header(&mut obuf, &header);
let mut header = DDILBOSHeader {
entrypoint: 0,
segment_header_pointer: 0,
segment_header_count: 0,
relocation_header_pointer: 0,
relocation_header_count: 0,
entrypoint_segment: 0,
};
let dataoff = (obuf.len() + size_of::<DDILBOSHeader>()) as u32;
let mut usegs = vec![];
let mut section_to_segment = BTreeMap::new();
let mut segment_to_base = BTreeMap::new();
let mut data = vec![];
for eseg in elf.segments().expect("failed to get ELF segments") {
const PT_LOAD: u32 = 0x00000001;
if eseg.p_type == PT_LOAD {
let seg = DDISegmentHeader {
segment_pointer: data.len() as u32 + dataoff,
segment_size: eseg.p_memsz as u32,
};
println!("segment {} needs align {}, handle in the future", usegs.len(), eseg.p_align);
if elf.ehdr.e_entry as usize >= eseg.p_vaddr as usize && elf.ehdr.e_entry <= eseg.p_vaddr + eseg.p_memsz {
// entry point is in this segment
let ep = elf.ehdr.e_entry as usize;
header.entrypoint = ep as u32;
header.entrypoint_segment = usegs.len() as u32;
}
let d = &raw[eseg.p_offset as usize..(eseg.p_offset + eseg.p_filesz) as usize];
let mut d = d.to_vec();
if eseg.p_memsz as u32 > d.len() as u32 {
d.resize(eseg.p_memsz as usize, 0);
}
data.extend_from_slice(&d);
header.segment_header_count += 1;
let section_headers = elf.section_headers().expect("failed to load section headers");
let mut found = false;
for (i, section) in section_headers.iter().enumerate() {
if eseg.p_vaddr <= section.sh_addr && eseg.p_vaddr + eseg.p_memsz >= section.sh_addr + section.sh_size {
println!("found section {} for segment {}", section.sh_name, usegs.len());
section_to_segment.insert(section.sh_name, usegs.len() as u32);
found = true;
}
}
if !found {
eprintln!("no section found for segment {}", usegs.len());
}
segment_to_base.insert(usegs.len(), eseg.p_vaddr);
usegs.push(seg);
}
}
println!("found {} segments", usegs.len());
println!("section mapper: {:?}", section_to_segment);
header.segment_header_pointer = data.len() as u32 + dataoff;
obuf.extend_from_slice(&[0u8; size_of::<DDILBOSHeader>()]);
write_ddi_lbos_header(&mut obuf, &header);
obuf.extend_from_slice(&data);
for (i, useg) in usegs.iter().enumerate() {
obuf.extend_from_slice(&[0u8; size_of::<DDISegmentHeader>()]);
write_ddi_segment_header(&mut obuf, &header, i, useg);
}
// relocations
let offset = obuf.len() as u32;
let mut relocations = vec![];
for section_header in elf.section_headers().expect("failed to get ELF section headers") {
match section_header.sh_type {
abi::SHT_REL => {
let rels = elf.section_data_as_rels(&section_header).expect("failed to get ELF section data");
for (i, rel) in rels.enumerate() {
let mut segment_containing_instruction = None;
for (j, seg) in usegs.iter().enumerate() {
let base = segment_to_base.get(&j).expect("failed to get segment base");
if section_header.sh_addr <= *base && section_header.sh_addr + section_header.sh_size >= (*base + seg.segment_size as u64) {
segment_containing_instruction = Some(j);
break;
}
}
if let Some(segment_containing_instruction) = segment_containing_instruction {
let symtab = elf.symbol_table().expect("failed to get symbol table");
let symbol = symtab.as_ref().expect("failed to get symbol table entry");
let section = elf.section_headers().unwrap().get(symbol.0.get(rel.r_sym as _).expect("bad rel symbol").st_shndx as _).expect("failed to get section");
if let Some(target_segment) = section_to_segment.get(&section.sh_name) {
let offset = symbol.0.get(rel.r_sym as _).expect("bad rel symbol").st_value as u32;
let ddirel = DDIRelocationHeader {
relocation_segment: segment_containing_instruction as u32,
relocation_pointer: rel.r_offset as u32,
target_segment: *target_segment as u32,
target_pointer: offset as u16,
relocation_type: rel.r_type as u16,
};
relocations.push(ddirel);
} else {
println!("not including relocation {i} in section {} due to missing section {}", section_header.sh_name, section.sh_name);
}
} else {
eprintln!("failed to find segment for relocation {i} in section {}", section_header.sh_name);
}
}
}
abi::SHT_RELA => {
let rels = elf.section_data_as_relas(&section_header).expect("failed to get ELF section data");
for (i, rel) in rels.enumerate() {
let mut segment_containing_instruction = None;
for (j, seg) in usegs.iter().enumerate() {
let base = segment_to_base.get(&j).expect("failed to get segment base");
if *base <= section_header.sh_addr && (*base + seg.segment_size as u64) >= (section_header.sh_addr + section_header.sh_size) {
segment_containing_instruction = Some(j);
break;
}
}
if let Some(segment_containing_instruction) = segment_containing_instruction {
let symtab = elf.symbol_table().expect("failed to get symbol table");
let symbol = symtab.as_ref().expect("failed to get symbol table entry");
let section = elf.section_headers().unwrap().get(symbol.0.get(rel.r_sym as _).expect("bad rel symbol").st_shndx as _).expect("failed to get section");
if let Some(target_segment) = section_to_segment.get(&section.sh_name) {
let offset = symbol.0.get(rel.r_sym as _).expect("bad rel symbol").st_value as u32;
println!("addend: {}", rel.r_addend);
let ddirel = DDIRelocationHeader {
relocation_segment: segment_containing_instruction as u32,
relocation_pointer: rel.r_offset as u32,
target_segment: *target_segment as u32,
target_pointer: offset as u16,
relocation_type: rel.r_type as u16,
};
relocations.push(ddirel);
} else {
println!("not including relocation {i} in section {} due to missing section {}", section_header.sh_name, section.sh_name);
}
} else {
eprintln!("failed to find segment for relocation {i} in section {}", section_header.sh_name);
}
}
}
_ => {}
}
}
header.relocation_header_pointer = offset;
header.relocation_header_count = relocations.len() as u32;
write_ddi_lbos_header(&mut obuf, &header);
for (i, rel) in relocations.iter().enumerate() {
obuf.extend_from_slice(&[0u8; size_of::<DDIRelocationHeader>()]);
write_ddi_relocation_header(&mut obuf, &header, i, rel);
}
let len = obuf.len();
std::fs::write(output, obuf).expect("failed to write output file");
println!("wrote {} bytes to {}", len, output);
}

2
qemurun.sh Executable file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
qemu-system-riscv32 -machine virt -bios none -display gtk -drive if=none,format=raw,file=/dev/loop0,id=disk1 -device virtio-blk-device,drive=disk1 -serial mon:stdio -d guest_errors,unimp -m 5M -kernel target/riscv32imac-unknown-none-elf/release/lbos

8
qemurun_ppc.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
# todo: this is incredibly system specific, change it
sudo losetup -fP test_ppc.img
sudo ./load_ppc.sh /dev/loop0p2
sudo losetup -d /dev/loop0
qemu-system-ppc -machine mac99,via=pmu -L pc-bios -display sdl -prom-env "auto-boot?=true" -prom-env "boot-device=hd:,lbos" -drive if=ide,format=raw,file=test_ppc.img,index=1 -m 256

36
src/arch/mod.rs Normal file
View file

@ -0,0 +1,36 @@
use crate::syscalls::{sc2usize, SysCall};
use crate::uart::{Serial};
#[cfg(feature = "arch_virt")]
pub mod virt;
#[cfg(feature = "arch_ppc32")]
pub mod ppc32;
#[cfg(any(feature = "arch_ofw", feature = "arch_ppc32"))]
pub mod ofw;
pub fn stall() {
#[cfg(feature = "arch_virt")]
virt::stall();
}
pub const fn serial_port() -> Option<&'static dyn Serial> {
#[cfg(feature = "arch_virt")]
{
virt::serial_port()
}
#[cfg(feature = "arch_ofw")]
{
Some(&ofw::ofw_framebuffer::terminal::TerminalLayer)
}
}
pub fn syscall(sc: SysCall, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize, a6: usize) -> usize {
#[cfg(feature = "arch_virt")]
{
virt::virt_syscall(sc2usize(sc) as _, a1 as _, a2 as _, a3 as _, a4 as _, a5 as _, a6 as _) as _
}
#[cfg(feature = "arch_ppc32")]
{
unsafe { ppc32::_ppc32_syscall(sc2usize(sc), a1, a2, a3, a4, a5, a6) }
}
}

50
src/arch/ofw/cell_conv.rs Normal file
View file

@ -0,0 +1,50 @@
/// A cell is a single FORTH stack value, all arguments and return values given to OpenFirmware
/// must fit in a cell.
pub type Cell = i32;
/// # DoubleWord
/// a structure containing a low and high cell
/// # example
/// ```
/// use ofw::cell_conv::DoubleWord;
/// let value: u64 = 0xabcdef0123456789;
/// let dw: DoubleWord = value.into();
/// assert_eq!(dw.high as u32, 0xabcdef01);
/// assert_eq!(dw.low as u32, 0x23456789);
/// ```
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct DoubleWord {
pub low: Cell,
pub high: Cell,
}
impl From<i64> for DoubleWord {
fn from(dw: i64) -> Self {
Self {
low: dw as Cell,
high: (dw >> 32) as Cell,
}
}
}
impl From<u64> for DoubleWord {
fn from(dw: u64) -> Self {
Self {
low: dw as Cell,
high: (dw >> 32) as Cell,
}
}
}
impl From<DoubleWord> for i64 {
fn from(dw: DoubleWord) -> Self {
(dw.high as i64) << 32 | dw.low as i64
}
}
impl From<DoubleWord> for u64 {
fn from(dw: DoubleWord) -> Self {
(dw.high as u64) << 32 | dw.low as u64
}
}

89
src/arch/ofw/deviceio.rs Normal file
View file

@ -0,0 +1,89 @@
#![no_std]
use super::EntryFunction;
use super::{IHandle, ntstr};
use super::cell_conv::DoubleWord;
use super::ntstr::NTSTR;
use crate::call;
/// # io_open
/// from ieee 1275, 6.3.2.3 Device I/O
/// IN: device-specifier: NTSTR
/// OUT: ihandle
/// opens the package named by device-specifier as with open-dev, returning the instance identifier
/// ihandle. ihandle is zero if the operation fails.
/// the same package can be opened more than once if the particular package permits it, in
/// which case a distinct ihandle will be returned each time.
pub fn io_open(entry_fn: EntryFunction, device_specifier: NTSTR) -> Option<IHandle> {
let (success, returns) = call!(entry_fn, ntstr!("open"), 1, 1, device_specifier);
if success {
Some(returns[0] as IHandle)
} else {
None
}
}
/// # io_close
/// from ieee 1275, 6.3.2.3 Device I/O
/// IN: ihandle
/// OUT: none
/// closes the instance identifed by ihandle as with close-dev; subsequent use of that ihandle
/// is invalid. a client program should close instances it has opened after the instances are
/// no longer needed, in order to release resources and to deactivate any associated devices.
pub fn io_close(entry_fn: EntryFunction, ihandle: IHandle) -> bool {
let (success, _) = call!(entry_fn, ntstr!("close"), 1, 0, ihandle);
success
}
/// # io_read
/// from ieee 1275, 6.3.2.3 Device I/O
/// IN: ihandle, addr: &mut [u8], len
/// OUT: actual
/// executes the read method in the instance ihandle with arguments addr and len.
/// actual is either the value returned by that read method or 1 if that instance
/// does not have a read method.
pub fn io_read(entry_fn: EntryFunction, ihandle: IHandle, addr: *const u8, len: usize) -> Option<i32> {
let (success, returns) = call!(entry_fn, ntstr!("read"), 3, 1, ihandle, addr as i32, len as i32);
if success {
Some(returns[0])
} else {
None
}
}
/// # io_write
/// from ieee 1275, 6.3.2.3 Device I/O
/// IN: ihandle, addr: &mut [u8], len
/// OUT: actual
/// executes the write method in the instance ihandle with arguments addr and len.
/// actual is either the value returned by that write method or 1 if that instance
/// does not have a write method.
pub fn io_write(entry_fn: EntryFunction, ihandle: IHandle, addr: *const u8, len: usize) -> Option<i32> {
let (success, returns) = call!(entry_fn, ntstr!("write"), 3, 1, ihandle, addr as i32, len as i32);
if success {
Some(returns[0])
} else {
None
}
}
/// # io_seek
/// from ieee 1275, 6.3.2.3 Device I/O
/// IN: ihandle, pos: DoubleWord
/// OUT: status
/// executes the seek method in the instance ihandle with arguments pos.hi and pos.lo.
/// status is either the value returned by that seek method or 1 if that instance
/// does not have a seek method.
pub fn io_seek(entry_fn: EntryFunction, ihandle: IHandle, pos: DoubleWord) -> Option<i32> {
let (success, returns) = call!(entry_fn, ntstr!("seek"), 3, 1, ihandle, pos.high, pos.low);
if success {
Some(returns[0])
} else {
None
}
}

236
src/arch/ofw/devicetree.rs Normal file
View file

@ -0,0 +1,236 @@
#![no_std]
use super::EntryFunction;
use super::{PHandle, ntstr, IHandle};
use super::ntstr::NTSTR;
use crate::call;
/// # dt_peer
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle
/// OUT: sibling-phandle
/// sibling-phandle is either the identifier of the device node that is the next sibling of the
/// device node identified by phandle, or 0 if there are no more siblings.
/// if phandle is 0, sibling-phandle is the identifier of the root node.
pub fn dt_peer(entry_fn: EntryFunction, phandle: PHandle) -> Option<PHandle> {
let (success, returns) = call!(entry_fn, ntstr!("peer"), 1, 1, phandle);
if success {
Some(returns[0] as PHandle)
} else {
None
}
}
/// # dt_child
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle
/// OUT: child-phandle
/// child-phandle is either the identifier of the device node that is the first child of the
/// device node identified by phandle or zero if there are no children.
pub fn dt_child(entry_fn: EntryFunction, phandle: PHandle) -> Option<PHandle> {
let (success, returns) = call!(entry_fn, ntstr!("child"), 1, 1, phandle);
if success {
Some(returns[0] as PHandle)
} else {
None
}
}
/// # dt_parent
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle
/// OUT: parent-phandle
/// parent-phandle is either the node identifier of the device node that is the parent of the
/// device node identified by phandle or zero if phandle is the identifier of the root node.
pub fn dt_parent(entry_fn: EntryFunction, phandle: PHandle) -> Option<PHandle> {
let (success, returns) = call!(entry_fn, ntstr!("parent"), 1, 1, phandle);
if success {
Some(returns[0] as PHandle)
} else {
None
}
}
/// # dt_instance_to_package
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: ihandle
/// OUT: phandle
/// phandle is either the identifier corresponding to the instance identifier ihandle or 1 if there
/// is no instance identifier ihandle.
/// if phandle is 1, OpenFirmware was unable to translate ihandle. OpenFirmware may, but is not
/// required to, check the validity of an ihandle.
pub fn dt_instance_to_package(entry_fn: EntryFunction, ihandle: IHandle) -> Option<PHandle> {
let (success, returns) = call!(entry_fn, ntstr!("instance-to-package"), 1, 1, ihandle);
if success {
Some(returns[0] as PHandle)
} else {
None
}
}
/// # dt_getproplen
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle, name: NTSTR
/// OUT: proplen
/// proplen is either the length of the value associated with the property name in the device node
/// identified by phandle, zero if the property name exists but has no corresponding value, or 1
/// if the property name does not exist.
pub fn dt_getproplen(entry_fn: EntryFunction, phandle: PHandle, name: NTSTR) -> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("getproplen"), 2, 1, phandle, name);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # dt_getprop
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle, name: NTSTR, buf: &mut [u8], buflen
/// OUT: size
/// copies a maximum of buflen bytes of the value of the property name in the device node
/// identified by phandle into the memory pointed to by buf. size is either the actual size of
/// the property, or 1 if name does not exist.
pub fn dt_getprop<T>(
entry_fn: EntryFunction, phandle: PHandle, name: NTSTR, buf: *mut T, buflen: usize
)-> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("getprop"), 4, 1, phandle, name, buf as i32, buflen as i32);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # dt_nextprop
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle, previous: NTSTR, buf: &mut [u8; 32]
/// OUT: flag
/// copies the name of the property following previous in the property list of the device node
/// identified by phandle into buf, as a null-terminated string. buf is the address of a 32-byte
/// region of memory. if previous is zero or a pointer to a null string, copies the name of the
/// device nodes first property. if there are no more properties after previous or if previous
/// is invalid (i.e., names a property which does not exist in that device node), copies a
/// null string. the return value flag is 1 if previous is invalid, zero if there are no
/// more properties after previous, or 1 otherwise.
pub fn dt_nextprop(
entry_fn: EntryFunction, phandle: PHandle, previous: NTSTR, buf: &mut [u8; 32]
)-> Option<i32> {
let (success, returns) = call!(entry_fn, ntstr!("nextprop"), 3, 1, phandle, previous, buf.as_ptr() as i32);
if success {
Some(returns[0])
} else {
None
}
}
/// # dt_setprop
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle, name: NTSTR, buf: &mut [u8], len
/// OUT: size
/// sets the property value of the property name in the device node identified by phandle to the
/// value beginning at memory address buf and continuing for len bytes, attempting to create the
/// property if it does not exist. size is the actual length of the new value, or 1 if the
/// property value could not be set or could not be created.
/// ## NOTE
/// there may be a length limitation on the property values of the “/options” node,
/// which are stored as fields in nonvolatile RAM. in such cases, the property value could be
/// truncated to fit the available space.
pub fn dt_setprop(
entry_fn: EntryFunction, phandle: PHandle, name: NTSTR, buf: *const u8, len: usize
)-> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("setprop"), 4, 1, phandle, name, buf as i32, len as i32);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # dt_canon
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: device-specifier: NTSTR, buf: &mut [u8], buflen
/// OUT: length
/// this service converts the possibly ambiguous device-specifier to a fully qualified pathname,
/// storing, at most, buflen bytes as a null-terminated string in the memory buffer starting at the
/// address buf. if the length of the null-terminated pathname is greater than buflen, the
/// trailing characters and the null terminator are not stored. length is the length of the fully
/// qualified pathname excluding any null terminator, or 1 if the pathname is invalid.
pub fn dt_canon(
entry_fn: EntryFunction, device_specifier: NTSTR, buf: *const u8, buflen: usize
)-> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("canon"), 3, 1, device_specifier, buf as i32, buflen as i32);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # dt_finddevice
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: device-specifier: NTSTR
/// OUT: phandle
/// phandle is the identifier of the device node selected by device-specifier, as with
/// find-device, or 1 if device-specifier cannot be matched. in either case, the active package
/// is unaffected.
pub fn dt_finddevice(entry_fn: EntryFunction, device_specifier: NTSTR) -> Option<PHandle> {
let (success, returns) = call!(entry_fn, ntstr!("finddevice"), 1, 1, device_specifier);
if success {
Some(returns[0])
} else {
None
}
}
/// # dt_instance_to_path
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: ihandle, buf: &mut [u8], buflen
/// OUT: length
/// this service returns the fully qualified pathname corresponding to the identifier ihandle,
/// storing, at most, buflen bytes as a null-terminated string in the memory buffer starting at
/// the address buf. if the length of the null-terminated pathname is greater than buflen, the
/// trailing characters and the null terminator are not stored. length is the length of the
/// fully qualified pathname excluding any null terminator, or 1 if ihandle is invalid.
pub fn dt_instance_to_path(
entry_fn: EntryFunction, ihandle: IHandle, buf: *const u8, buflen: usize
)-> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("instance-to-path"), 3, 1, ihandle, buf as i32, buflen as i32);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # dt_package_to_path
/// from ieee 1275, 6.3.2.2 Device tree
/// IN: phandle, buf: &mut [u8], buflen
/// OUT: length
/// returns the fully qualified pathname corresponding to the node identifier phandle, storing,
/// at most, buflen bytes as a null-terminated string in the memory buffer starting at the address
/// buf. if the length of the null-terminated pathname is greater than buflen, the trailing characters
/// and the null terminator are not stored. length is the length of the fully qualified pathname
/// excluding any null terminator, or 1 if phandle is invalid.
pub fn dt_package_to_path(
entry_fn: EntryFunction, phandle: PHandle, buf: *const u8, buflen: usize
)-> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("package-to-path"), 3, 1, phandle, buf as i32, buflen as i32);
if success {
Some(returns[0] as usize)
} else {
None
}
}

40
src/arch/ofw/memory.rs Normal file
View file

@ -0,0 +1,40 @@
#![no_std]
use super::EntryFunction;
use super::{ntstr};
use crate::call;
/// # m_claim
/// from ieee 1275, 6.3.2.4 Memory
/// IN: virt: usize, size, align
/// OUT: baseaddr: usize
/// allocates size bytes of memory. if align is zero, the allocated range begins at the
/// virtual address virt. otherwise, an aligned address is automatically chosen and the
/// input argument virt is ignored. the alignment boundary is the smallest power of two
/// greater than or equal to the value of align; an align value of 1 signifies 1-byte alignment.
/// base is the beginning address of the allocated memory (equal to virt if align was 0)
/// or 1 if the operation fails (for example, if the requested virtual address is unavailable).
/// the range of physical memory and virtual addresses affected by this operation will be
/// unavailable for subsequent mapping or allocation operations until freed by release.
pub fn m_claim(entry_fn: EntryFunction, virt: usize, size: i32, align: i32) -> Option<usize> {
let (success, returns) = call!(entry_fn, ntstr!("claim"), 3, 1, virt as i32, size, align);
if success {
Some(returns[0] as usize)
} else {
None
}
}
/// # m_release
/// from ieee 1275, 6.3.2.4 Memory
/// IN: virt: usize, size
/// OUT: none
/// frees size bytes of physical memory starting at virtual address virt, making that physical
/// memory and the corresponding range of virtual address space available for later use.
/// that memory must have been previously allocated by claim.
pub fn m_release(entry_fn: EntryFunction, virt: usize, size: i32) -> bool {
let (success, _) = call!(entry_fn, ntstr!("release"), 2, 0, virt as i32, size);
success
}

242
src/arch/ofw/mod.rs Normal file
View file

@ -0,0 +1,242 @@
/// # OFW_TRUE
/// a constant representing the OpenFirmware "true" value
pub const OFW_TRUE: i32 = -1;
/// # OFW_FALSE
/// a constant representing the OpenFirmware "false" value
pub const OFW_FALSE: i32 = 0;
/// # OFWBoolean
/// a type representing the OpenFirmware boolean type
/// can be either OFW_TRUE or OFW_FALSE
pub type OFWBoolean = i32;
/// # PHandle
/// handle to a "package" in OpenFirmware
/// usually used to access properties
pub type PHandle = i32;
/// # IHandle
/// handle to a package instance in OpenFirmware
/// usually used to call methods from a package
pub type IHandle = i32;
use ntstr::NTSTR;
use crate::ntstr;
pub type EntryFunction = extern "C" fn (*mut Args) -> i32;
/// # ntstr
/// module for handling null-terminated strings
pub mod ntstr;
/// # cell_conv
/// module for converting between OpenFirmware cells and Rust integers
pub mod cell_conv;
#[cfg(test)]
mod tests;
pub mod runtime;
pub mod deviceio;
pub mod devicetree;
pub mod memory;
pub mod ofw_framebuffer;
pub mod keyboard;
/// # Args
/// a base structure for all calls to the OpenFirmware entry function,
/// shouldn't be used directly but rather used as a base for other "service" structures
/// an example "service struct" is as follows
/// ```
/// use ofw::{Args, OFWBoolean};
/// use ofw::ntstr::NTSTR;
/// /// # test
/// /// nargs: 1
/// /// nrets: 1
/// /// takes a service name string, and returns OFW_TRUE if the service is missing or OFW_FALSE if it is present
/// #[repr(C)]
/// pub struct TestService {
/// pub args: Args,
/// pub service: NTSTR,
/// pub missing: OFWBoolean,
/// }
/// ```
#[repr(C)]
pub struct Args {
pub service: NTSTR,
// ieee 1275, 2.4.1 General conventions: "All numbers on the stack are signed integers, _32 bits[...]"
pub nargs: i32,
pub nrets: i32,
// inheriting structs will continue from here
}
/// # call!
/// a macro for calling OpenFirmware services
/// ## example
/// ```
/// use ofw::{call, service_result, ServiceResult};
/// use ofw::arch::EntryFunction;
/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere");
/// // <entry function>, <service name>, <# of args>, <# of rets>, <args...>
/// let (success, returns): (bool, [i32; 1]) = call!(entry_fn, ntstr!("test"), 1, 1, ntstr!("test"));
/// if success {
/// let test_result: i32 = returns[0];
/// }
/// ```
#[macro_export]
macro_rules! call {
($entry_fn:expr, $service:expr, $nargs:expr, $nrets:expr, $($args:expr),*) => {
{
let mut args_and_rets: [i32; $nargs as usize + $nrets as usize] = [0; $nargs as usize + $nrets as usize];
let mut rets: [i32; $nrets as usize] = [0; $nrets as usize];
let mut i = 0;
#[allow(unused_assignments)]
{
$(
args_and_rets[i] = $args.into();
i += 1;
)*
}
let success: (bool, [i32; $nargs as usize + $nrets as usize]) = $crate::arch::ofw::ofw_call($entry_fn, $service, $nargs, $nrets, args_and_rets);
for i in 0..$nrets as usize {
rets[i] = success.1[i + $nargs as usize];
}
(success.0, rets)
}
};
}
/// # call_method!
/// a macro for calling package methods
/// ## example
/// ```
/// use ofw::arch::EntryFunction;
/// use ofw::{call_method, method_service_result, ServiceResult};
///
/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere");
/// let package_instance: i32 = unimplemented!("get the package instance from somewhere");
/// // <entry function>, <package instance>, <method name>, <# of args>, <# of rets>, <args...>
/// let (success, returns): (bool, [i32; 1]) = call_method!(entry_fn, package_instance, ntstr!("seek"), 2, 1, 0, 0);
/// if success {
/// let seek_result: i32 = returns[0];
/// }
/// ```
#[macro_export]
macro_rules! call_method {
($entry_fn:expr, $package_instance:expr, $service:expr, $nargs:expr, $nrets:expr, $($args:expr),*) => {
{
let mut args_and_rets: [i32; $nargs as usize + $nrets as usize + 1] = [0; $nargs as usize + $nrets as usize + 1];
let mut rets: [i32; $nrets as usize] = [0; $nrets as usize];
let mut i = 0;
#[allow(unused_assignments)]
{
$(
args_and_rets[i] = $args.into();
i += 1;
)*
}
let success: (bool, [i32; $nargs as usize + $nrets as usize + 1]) = $crate::arch::ofw::ofw_call_method($entry_fn, $package_instance, $service, $nargs, $nrets, args_and_rets);
for i in 0..$nrets as usize {
rets[i] = success.1[i + $nargs as usize + 1];
}
(success.0 && success.1[$nargs as usize] != 0, rets)
}
};
}
/// # ofw_call
/// a function to call any OpenFirmware-provided service
/// usage of the macro `call!` is preferred, but this function can be used directly
/// ## example
/// ```
/// use ofw::arch::EntryFunction;
/// use ofw::{ntstr, ofw_call};
/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere");
/// let args: [i32; 2] = [ntstr!("test") as i32, 0];
/// let (was_service_successfully_called, args): (bool, [i32; 2]) = ofw_call(entry_fn, ntstr!("test"), 1, 1, args);
/// let test_result: i32 = args[1];
/// ```
pub fn ofw_call<T: Sized>(entry_fn: EntryFunction, service: NTSTR, num_args: i32, num_rets: i32, args_and_rets: T) -> (bool, T) {
#[repr(C)]
struct GenericArgs<U: Sized> {
args: Args,
buffer: U,
}
let mut generic_args = GenericArgs {
args: Args {
service,
nrets: num_rets,
nargs: num_args,
},
buffer: args_and_rets,
};
let result: bool;
#[cfg(target_arch = "powerpc")]
{
result = entry_fn(&mut generic_args as *mut _ as *mut Args) != -1;
}
#[cfg(target_arch = "x86_64")]
{
result = entry_fn(&mut generic_args as *mut _ as *mut Args) != -1;
}
(result, generic_args.buffer)
}
/// # ofw_call_method
/// a function to call a method provided by a package instance
/// usage of the `call_method!` macro is preferred, but this function can be used directly
/// ## example
/// ```
/// // we will be calling the "seek" method on an imaginary block device
/// // the seek method takes 2 arguments, and returns 1 value
/// // addr_hi, addr_lo -- okay?
/// // however, we must add another cell in order to account for the catch-result
/// // (see ieee 1275, 6.3.2.2 Device tree)
/// use ofw::{IHandle, ntstr, ofw_call_method};
/// use ofw::arch::EntryFunction;
/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere");
/// let instance: IHandle = unimplemented!("get the package instance from somewhere");
/// // addr_hi, addr_lo, catch-result, okay?
/// let args: [i32; 4] = [0, 0, 0, 0];
/// // note that num_args and num_rets are not the same as the length of args, this is intentional
/// let (was_service_successfully_called, args): (bool, [i32; 4]) = ofw_call_method(entry_fn, instance, ntstr!("seek"), 2, 1, args);
/// if was_service_successfully_called && args[2] != 0 {
/// let seek_result: i32 = args[3];
/// }
/// ```
pub fn ofw_call_method<T>(entry_fn: EntryFunction, ihandle: IHandle, method: NTSTR, num_args: i32, num_rets: i32, args_and_rets: T) -> (bool, T) {
#[repr(C)]
struct CallMethodArgs<U> {
args: Args,
method: NTSTR,
ihandle: IHandle,
buffer: U,
}
let mut call_method_args = CallMethodArgs {
args: Args {
service: ntstr!("call-method"),
nrets: num_rets + 1,
nargs: num_args + 2,
},
method,
ihandle,
buffer: args_and_rets,
};
let result: bool;
#[cfg(target_arch = "powerpc")]
{
result = entry_fn(&mut call_method_args as *mut _ as *mut Args) != OFW_TRUE;
}
#[cfg(target_arch = "x86_64")]
{
result = entry_fn(&mut call_method_args as *mut _ as *mut Args) != OFW_TRUE;
}
(result, call_method_args.buffer)
}

94
src/arch/ofw/ntstr.rs Normal file
View file

@ -0,0 +1,94 @@
use core::fmt::{Debug, Display, Formatter, Write};
/// # ntstr!
/// macro for creating a null-terminated string
/// ## example
/// ```
/// use ofw::ntstr;
/// let ntstr = ntstr!("Hello, world!");
/// ```
/// becomes
/// ```
/// let ntstr = ofw::ntstr::NTSTR::new(b"Hello, world!\0".as_ptr());
/// ```
#[macro_export]
macro_rules! ntstr {
($str:expr) => {
$crate::arch::ofw::ntstr::NTSTR::new(concat!($str, "\0").as_ptr())
};
}
/// # NTSTR
/// a null-terminated string, defined as a pointer to an array of u8 characters ending with a null byte
/// not mutable
#[repr(C)]
pub struct NTSTR {
ptr: *const u8,
}
impl From<usize> for NTSTR {
fn from(ptr: usize) -> Self {
Self { ptr: ptr as *const u8 }
}
}
impl From<*const u8> for NTSTR {
fn from(ptr: *const u8) -> Self {
Self { ptr }
}
}
#[allow(clippy::from_over_into)]
impl Into<i32> for NTSTR {
fn into(self) -> i32 {
self.ptr as i32
}
}
impl NTSTR {
pub const fn new(ptr: *const u8) -> Self {
Self { ptr }
}
fn print(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let mut i = 0;
loop {
let a = unsafe { *self.ptr.add(i) };
if a == 0 {
break;
}
f.write_char(a as char)?;
i += 1;
}
Ok(())
}
}
impl PartialEq for NTSTR {
fn eq(&self, other: &Self) -> bool {
let mut i = 0;
loop {
let a = unsafe { *self.ptr.add(i) };
let b = unsafe { *other.ptr.add(i) };
if a != b {
return false;
}
if a == 0 {
return true;
}
i += 1;
}
}
}
impl Debug for NTSTR {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.print(f)
}
}
impl Display for NTSTR {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.print(f)
}
}

View file

@ -0,0 +1,15 @@
use crate::arch::ofw::ofw_framebuffer::OFColor;
pub const DARKBG_DEF: OFColor = OFColor {
red: 37,
green: 33,
blue: 56,
};
pub const DARKBG: u8 = 1;
pub const WHITE_DEF: OFColor = OFColor {
red: 255,
green: 255,
blue: 255,
};
pub const WHITE: u8 = 2;

View file

@ -0,0 +1,16 @@
pub const C_STAR: char = 128 as char;
pub const C_HAPPY: char = 129 as char;
pub const C_SAD: char = 130 as char;
pub const C_OK: char = 131 as char;
pub const C_NG: char = 132 as char;
pub const C_VEXKA: char = 133 as char;
pub const C_VORE: char = 134 as char;
pub const C_VAP: char = 135 as char;
pub const C_TRADEMARK: char = 136 as char;
pub const C_COPYRIGHT: char = 137 as char;
pub const C_REGISTERED: char = 138 as char;
pub const C_COLONTHREE: char = 139 as char;
pub const C_ENVELOPE: char = 140 as char;
pub const C_LOCKED: char = 141 as char;
pub const C_UNLOCKED: char = 142 as char;
pub const C_KEY: char = 143 as char;

View file

@ -0,0 +1,289 @@
pub mod colors;
pub mod extrachars;
pub mod terminal;
use colors::*;
use super::runtime::{OFW_RUNTIME, ofw_malloc};
use crate::worm::Worm;
use core::alloc::Layout;
use core::fmt::Write;
use core::mem::size_of;
use core::ops::Deref;
use super::ntstr;
use crate::{call, call_method};
use super::deviceio::io_open;
use super::devicetree::{dt_finddevice, dt_getprop};
pub const VAPFONT: &[u8] = include_bytes!("./vapfont.data");
pub const VAPFONT_W: usize = 256;
pub const VAPFONT_H: usize = 112;
#[derive(Clone, Copy)]
pub struct OFColor {
pub red: u8,
pub green: u8,
pub blue: u8,
}
pub struct OFFramebuffer {
pub address: usize,
pub width: usize,
pub height: usize,
pub linebytes: usize,
pub depth: usize,
pub ofscr_buf: *mut u8,
palette: [OFColor; 256],
}
pub static FRAMEBUFFER: Worm<Option<OFFramebuffer>> = Worm::new(None);
impl OFFramebuffer {
pub fn try_attach() -> Option<Self> {
let entry = {
let rt = &OFW_RUNTIME;
rt.entry_fn.unwrap()
};
let screen = dt_finddevice(entry, ntstr!("screen"))?;
if screen == -1 {
let mut rt = unsafe { OFW_RUNTIME.lock() };
rt.write_str("no screen device found!");
return None;
}
let mut address: usize = 0;
let mut width: usize = 0;
let mut height: usize = 0;
let mut linebytes: usize = 0;
let mut depth: usize = 0;
dt_getprop(
entry,
screen,
ntstr!("address"),
&mut address,
size_of::<u32>(),
);
if address == 0 {
// try getting frame-buffer-adr instead
let (success, returns) = call!(
entry,
ntstr!("interpret"),
1,
3,
ntstr!("frame-buffer-adr"),
(&mut address) as *mut _ as usize as i32
);
if !success {
let mut rt = unsafe { OFW_RUNTIME.lock() };
rt.write_str("screen.address and frame-buffer-adr failed, no framebuffer");
return None;
}
address = returns[2] as usize;
}
dt_getprop(entry, screen, ntstr!("width"), &mut width, size_of::<u32>());
dt_getprop(
entry,
screen,
ntstr!("height"),
&mut height,
size_of::<u32>(),
);
dt_getprop(
entry,
screen,
ntstr!("linebytes"),
&mut linebytes,
size_of::<u32>(),
);
dt_getprop(entry, screen, ntstr!("depth"), &mut depth, size_of::<u32>());
let ofscr_buf = {
ofw_malloc(Layout::array::<u8>(width * height).unwrap())
};
Some(OFFramebuffer {
address,
width,
height,
linebytes,
depth,
ofscr_buf,
palette: [const {
OFColor {
red: 0,
green: 0,
blue: 0,
}
}; 256],
})
}
pub fn set_default_colors() {
OFFramebuffer::set_colors(&[DARKBG_DEF, WHITE_DEF]);
}
pub fn set_colors(colors: &[OFColor]) {
let color_count = colors.len();
let colorbuf = {
let ptr = ofw_malloc(Layout::array::<u8>(color_count * 3).unwrap());
unsafe { core::slice::from_raw_parts_mut(ptr, color_count * 3) }
};
for (i, color) in colors.iter().enumerate() {
colorbuf[i * 3] = color.red;
colorbuf[i * 3 + 1] = color.green;
colorbuf[i * 3 + 2] = color.blue;
}
let entry = { OFW_RUNTIME.entry_fn.unwrap() };
let screen = io_open(entry, ntstr!("screen")).expect("CANNOT OPEN OFW SCREEN");
unsafe {
let mut fb = FRAMEBUFFER.lock();
let mut fb = fb.as_mut().unwrap();
if fb.depth == 8 {
let (success, _returns) = call_method!(
entry,
screen,
ntstr!("set-colors"),
3,
0,
color_count as i32,
1,
colorbuf.as_ptr() as i32
);
if success {
let mut rt = unsafe { OFW_RUNTIME.lock() };
rt.write_str("failed to set framebuffer colors!");
loop {}
}
}
for (i, color) in colors.into_iter().enumerate() {
fb.palette[i] = color.clone();
}
}
}
pub fn clear_screen(color: u8) {
if let Some(fb) = FRAMEBUFFER.as_ref() {
OFFramebuffer::rectangle(color as usize, 0, 0, fb.width, fb.height);
}
}
pub fn swap_buffer() {
if let Some(fb) = FRAMEBUFFER.as_ref() {
let mem = fb.address as *mut u8;
let stride = fb.linebytes;
let width = fb.width;
let height = fb.height;
let mut i = 0;
for y in 0..height {
for x in 0..width {
let color = unsafe { fb.ofscr_buf.add(i).read_volatile() };
unsafe {
match fb.depth {
8 => {
*mem.add((y * stride) + x) = color;
}
_ => {
let clr = &fb.palette[color as usize - 1];
let offset = (y * stride) + (x * 4);
*mem.add(offset) = 0;
*mem.add(offset + 3) = clr.blue;
*mem.add(offset + 2) = clr.green;
*mem.add(offset + 1) = clr.red;
}
}
}
i += 1;
}
}
}
}
pub fn rectangle(colour: usize, x1: usize, y1: usize, x2: usize, y2: usize) {
if let Some(fb) = FRAMEBUFFER.as_ref() {
//let mem = fb.address as *mut u8;
//let stride = fb.linebytes;
let width = fb.width;
let height = fb.height;
for y in y1..y2 {
for x in x1..x2 {
if x < width && y < height {
unsafe { fb.ofscr_buf.add((y * width) + x).write_volatile(colour as u8) };
//unsafe {
// match fb.depth {
// 8 => {
// *mem.add((y * stride) + x) = colour as u8;
// }
// _ => {
// let clr = &fb.palette[colour - 1];
// let offset = (y * stride) + (x * 4);
// *mem.add(offset) = 0;
// *mem.add(offset + 3) = clr.blue;
// *mem.add(offset + 2) = clr.green;
// *mem.add(offset + 1) = clr.red;
// }
// }
//}
}
}
}
}
}
pub fn put_char_array(mut x: usize, mut y: usize, s: &[char], color: u8) {
let ogx = x;
if let Some(fb) = FRAMEBUFFER.as_ref() {
const CHAR_SIZE: usize = 16;
for c in s {
let c = *c;
if c == '\n' {
y += CHAR_SIZE;
x = ogx;
continue;
} else if c == ' ' {
x += CHAR_SIZE;
} else if c as u8 > 32 {
let c = c as u8 - 32;
let cx = (c % 16) as usize * CHAR_SIZE;
let cy = (c / 16) as usize * CHAR_SIZE;
for row in 0..CHAR_SIZE {
for col in 0..CHAR_SIZE {
let coff = (VAPFONT_W * (cy + row)) + (cx + col);
if coff >= VAPFONT.len() {
continue;
}
let draw = VAPFONT[coff];
if draw != 0 {
unsafe { fb.ofscr_buf.add((y + row) * fb.width + (x + col)).write_volatile(color) };
//match fb.depth {
// 8 => {
// let mem = fb.address as *mut u8;
// let offset = (y + row) * fb.linebytes + (x + col);
// unsafe {
// *mem.add(offset) = color;
// }
// }
// _ => {
// let mem = fb.address as *mut u8;
// let offset = (y + row) * fb.linebytes + (x + col) * 4;
// if color != 0 {
// let color = &fb.palette[color as usize - 1];
// unsafe {
// *mem.add(offset) = 0;
// *mem.add(offset + 3) = color.blue;
// *mem.add(offset + 2) = color.green;
// *mem.add(offset + 1) = color.red;
// }
// }
// }
//}
}
}
}
x += CHAR_SIZE;
}
}
}
}
}

View file

@ -0,0 +1,103 @@
use core::arch::asm;
use core::fmt::Write;
use crate::arch::ofw::ofw_framebuffer::{colors, OFFramebuffer};
use crate::spinlock::Spinlock;
use crate::uart::Serial;
pub struct FramebufferTerminal {
pub buffer: [[char; 48]; 30],
pub cursor: (usize, usize),
}
pub static FB_TERMINAL: Spinlock<FramebufferTerminal> = Spinlock::new(FramebufferTerminal::empty());
impl FramebufferTerminal {
pub const fn empty() -> Self {
Self {
buffer: [[' '; 48]; 30],
cursor: (0, 0),
}
}
pub fn scroll_terminal(&mut self) {
for line_num in 0..(self.buffer.len() - 1) {
let copy_from = self.buffer[line_num + 1];
self.buffer[line_num] = copy_from;
}
self.buffer[self.buffer.len() - 1] = [' '; 48];
}
pub fn clear_terminal(&mut self) {
self.buffer = [[' '; 48]; 30];
self.cursor = (0, 0);
}
pub fn printstr(&mut self, str: &str) {
for c in str.chars() {
if self.cursor.1 >= self.buffer.len() {
self.cursor.1 = 0;
}
if self.cursor.0 >= self.buffer[0].len() {
self.cursor.0 = 0;
}
if c == '\n' || c == '\r' {
self.cursor.0 = 0;
self.cursor.1 += 1;
if self.cursor.1 >= self.buffer.len() {
self.cursor.1 -= 1;
self.scroll_terminal();
}
} else {
self.buffer[self.cursor.1][self.cursor.0] = c;
self.cursor.0 += 1;
if self.cursor.0 >= self.buffer[0].len() {
self.cursor.0 = 0;
self.cursor.1 += 1;
if self.cursor.1 >= self.buffer.len() {
self.cursor.1 -= 1;
self.scroll_terminal();
}
}
}
}
self.render();
}
pub fn render(&self) {
OFFramebuffer::clear_screen(colors::DARKBG);
for (i, line) in self.buffer.iter().enumerate() {
OFFramebuffer::put_char_array(2, 2 + (i * 18), line, colors::WHITE);
}
OFFramebuffer::swap_buffer();
unsafe {
asm!("sync");
asm!("isync");
}
}
}
impl Write for FramebufferTerminal {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.printstr(s);
Ok(())
}
}
pub struct TerminalLayer;
impl Serial for TerminalLayer {
fn put(&self, c: u8) {
let s = [c];
FB_TERMINAL.lock().printstr(unsafe { str::from_utf8_unchecked(&s) })
}
fn putstr(&self, s: &str) {
FB_TERMINAL.lock().printstr(s);
}
fn put_bytes(&self, s: &[u8]) {
FB_TERMINAL.lock().printstr(unsafe { str::from_utf8_unchecked(s) })
}
}

Binary file not shown.

149
src/arch/ofw/runtime.rs Normal file
View file

@ -0,0 +1,149 @@
use core::alloc::Layout;
use core::fmt::Write;
use core::mem::{size_of, transmute};
use super::{IHandle};
use super::deviceio::{io_open, io_write};
use super::devicetree::{dt_child, dt_finddevice, dt_getprop};
use super::memory::{m_claim, m_release};
use crate::arch::ofw::EntryFunction;
use crate::ntstr;
use crate::worm::Worm;
pub enum OfwRuntimeStage {
None,
Stdout,
}
pub struct OfwRuntime {
pub stage: OfwRuntimeStage,
pub entry_fn: Option<EntryFunction>,
stdout: Option<IHandle>,
}
pub static OFW_RUNTIME: Worm<OfwRuntime> = Worm::new(OfwRuntime {
stage: OfwRuntimeStage::None,
entry_fn: None,
stdout: None,
});
pub fn init_ofw_runtime(entry: EntryFunction) -> bool {
unsafe {
let mut rt = OFW_RUNTIME.lock();
rt.entry_fn = Some(entry);
drop(rt);
}
let chosen = dt_finddevice(entry, ntstr!("/chosen"));
if chosen.is_none() {
return false;
}
let chosen = chosen.unwrap();
if chosen == -1 {
return false;
}
let mut buf = [0u8; size_of::<IHandle>()];
let _ = dt_getprop(
entry,
chosen,
ntstr!("stdout"),
buf.as_mut_ptr(),
size_of::<IHandle>(),
);
let stdout = unsafe { transmute::<_, IHandle>(buf) };
if stdout == -1 {
// todo: continue without stdout instead of exiting
return false;
}
unsafe {
let mut rt = OFW_RUNTIME.lock();
rt.stdout = Some(stdout);
rt.stage = OfwRuntimeStage::Stdout;
}
true
}
pub fn primary_cpu_freq() -> u32 {
let entry = { OFW_RUNTIME.entry_fn }
.expect("alloc before ofw init! not like you can see this panic meesage though");
let root = dt_finddevice(entry, ntstr!("/cpus")).unwrap();
let first = dt_child(entry, root).unwrap();
let mut freq = 0;
let _ = dt_getprop(
entry,
first,
ntstr!("timebase-frequency"),
&mut freq as *mut u32 as *mut u8,
size_of::<u32>(),
);
freq
}
pub fn ofw_malloc(layout: Layout) -> *mut u8 {
let entry = { OFW_RUNTIME.entry_fn }
.expect("alloc before ofw init! not like you can see this panic meesage though");
let addr = m_claim(entry, 0, layout.size() as _, layout.align() as _);
addr.expect("out of memory") as _
}
pub fn ofw_free(ptr: *mut u8, layout: Layout) {
let entry = { OFW_RUNTIME.entry_fn }
.expect("free before ofw init! not like you can see this panic meesage though");
m_release(entry, ptr as usize, layout.size() as _);
}
impl Write for OfwRuntime {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if matches!(self.stage, OfwRuntimeStage::None) {
panic!("not like you're going to be able to see this panic message, but oh well");
}
let len = s.len();
let _ = io_write(
self.entry_fn.unwrap(),
self.stdout.unwrap(),
s.as_ptr(),
len,
);
Ok(())
}
}
#[macro_export]
macro_rules! ofw_print {
($($arg:tt)*) => {$crate::ofw_runtime::_ofw_print(format_args!($($arg)*))};
}
#[macro_export]
macro_rules! ofw_println {
($($arg:tt)*) => {$crate::ofw_print!("{}\r\n", format_args!($($arg)*))};
}
#[doc(hidden)]
pub fn _ofw_print(args: core::fmt::Arguments) {
struct Wrapper<'a>(&'a Worm<OfwRuntime>);
impl<'a> core::fmt::Write for Wrapper<'a> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if matches!(self.0.stage, OfwRuntimeStage::None) {
panic!("not like you're going to be able to see this panic message, but oh well");
}
let len = s.len();
let _ = io_write(
self.0.entry_fn.unwrap(),
self.0.stdout.unwrap(),
s.as_ptr(),
len,
);
Ok(())
}
}
let rt = &OFW_RUNTIME;
Wrapper(rt).write_fmt(args).unwrap()
}

170
src/arch/ofw/tests.rs Normal file
View file

@ -0,0 +1,170 @@
use crate::arch::EntryFunction;
use crate::{Args, call, call_method, IHandle, ntstr, OFW_FALSE, OFW_TRUE};
use crate::cell_conv::DoubleWord;
use crate::ntstr::NTSTR;
#[test]
fn ntstr_macro() {
let a = ntstr!("Hello, world!");
let b = NTSTR::new(b"Hello, world!\0".as_ptr());
assert_eq!(a, b);
}
#[test]
fn doubleword_conversion() {
let a: u64 = 0xabcdef0123456789;
let a_dw: DoubleWord = a.into();
let b: u64 = a_dw.into();
assert_eq!(a, b);
assert_eq!(a_dw.high as u32, 0xabcdef01);
assert_eq!(a_dw.low as u32, 0x23456789);
}
#[test]
fn call_ofw() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct GenericArgs {
args: Args,
buffer: [i32; 2],
}
let args = unsafe { &mut *(args as *mut GenericArgs) };
assert_eq!(args.args.service, ntstr!("test"));
assert_eq!(args.args.nargs, 1);
assert_eq!(args.args.nrets, 1);
args.buffer[1] = OFW_FALSE; // not missing
0
}
let entry_fn: EntryFunction = test_entry_function;
let (success, results) = call!(entry_fn, ntstr!("test"), 1, 1, ntstr!("test"));
assert!(success);
assert_eq!(results[0], OFW_FALSE);
}
#[test]
fn call_ofw_missing() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct GenericArgs {
args: Args,
buffer: [i32; 2],
}
let args = unsafe { &mut *(args as *mut GenericArgs) };
assert_eq!(args.args.service, ntstr!("test"));
assert_eq!(args.args.nargs, 1);
assert_eq!(args.args.nrets, 1);
args.buffer[1] = OFW_TRUE; // missing
0
}
let entry_fn: EntryFunction = test_entry_function;
let (success, results) = call!(entry_fn, ntstr!("test"), 1, 1, ntstr!("aioshjfoahwfi"));
assert!(success);
assert_eq!(results[0], OFW_TRUE);
}
#[test]
fn call_ofw_failure() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct GenericArgs {
args: Args
}
let args = unsafe { &mut *(args as *mut GenericArgs) };
assert_eq!(args.args.service, ntstr!("this-method-does-not-exist"));
assert_eq!(args.args.nargs, 1);
assert_eq!(args.args.nrets, 1);
-1
}
let entry_fn: EntryFunction = test_entry_function;
let (success, _) = call!(entry_fn, ntstr!("this-method-does-not-exist"), 1, 1, ntstr!("aioshjfoahwfi"));
assert!(!success);
}
#[test]
fn call_method_ofw() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct CallMethodArgs {
args: Args,
method: NTSTR,
ihandle: IHandle,
buffer: [i32; 4],
}
let args = unsafe { &mut *(args as *mut CallMethodArgs) };
assert_eq!(args.args.service, ntstr!("call-method"));
assert_eq!(args.method, ntstr!("seek"));
assert_eq!(args.ihandle, 0);
assert_eq!(args.args.nargs, 2 + 2);
assert_eq!(args.args.nrets, 1 + 1);
// 2 offset from args in buffer
args.buffer[2] = 1; // set catch-result to non-zero
args.buffer[2 + 1] = OFW_TRUE; // set okay? to true
0
}
let entry_fn: EntryFunction = test_entry_function;
let ih: IHandle = 0;
let (success, returns) = call_method!(entry_fn, ih, ntstr!("seek"), 2, 1, 0, 0);
assert!(success);
assert_eq!(returns[0], OFW_TRUE);
}
#[test]
fn call_method_ofw_seek_failure() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct CallMethodArgs {
args: Args,
method: NTSTR,
ihandle: IHandle,
buffer: [i32; 4],
}
let args = unsafe { &mut *(args as *mut CallMethodArgs) };
assert_eq!(args.args.service, ntstr!("call-method"));
assert_eq!(args.method, ntstr!("seek"));
assert_eq!(args.ihandle, 0);
assert_eq!(args.args.nargs, 2 + 2);
assert_eq!(args.args.nrets, 1 + 1);
// 2 offset from args in buffer
args.buffer[2] = 1; // set catch-result to non-zero
args.buffer[2 + 1] = OFW_FALSE; // set okay? to false
0
}
let entry_fn: EntryFunction = test_entry_function;
let ih: IHandle = 0;
let (success, returns) = call_method!(entry_fn, ih, ntstr!("seek"), 2, 1, 0, 0);
assert!(success);
assert_eq!(returns[0], OFW_FALSE);
}
#[test]
fn call_method_ofw_failure() {
pub extern "C" fn test_entry_function(args: *mut Args) -> i32 {
#[repr(C)]
struct CallMethodArgs {
args: Args,
method: NTSTR,
ihandle: IHandle,
buffer: [i32; 4],
}
let args = unsafe { &mut *(args as *mut CallMethodArgs) };
assert_eq!(args.args.service, ntstr!("call-method"));
assert_eq!(args.method, ntstr!("asiofhawoptgh"));
assert_eq!(args.ihandle, 0);
assert_eq!(args.args.nargs, 2 + 2);
assert_eq!(args.args.nrets, 1 + 1);
// 2 offset from args in buffer
args.buffer[2] = 0; // set catch-result to 0 (caught error, package doesn't have this method)
0
}
let entry_fn: EntryFunction = test_entry_function;
let ih: IHandle = 0;
let (success, _) = call_method!(entry_fn, ih, ntstr!("asiofhawoptgh"), 2, 1, 0, 0);
assert!(!success);
}

View file

@ -0,0 +1,38 @@
OUTPUT_FORMAT("elf32-powerpc")
OUTPUT_ARCH("powerpc:common")
ENTRY(_start)
SECTIONS {
.text 0x100000 : AT(0x100000) {
*(.text .text.*)
}
.vector_container : {
VAP_VECTOR_LOCATION = .;
*(V_TEXT_GENESIS)
*(V_VECTOR_DEFS)
}
.stack : {
*(VAP_STACK_SECTION)
}
.rodata : {
*(.rodata .rodata.*)
}
.data : {
*(.data .data.*)
}
.bss : {
*(COMMON)
*(.bss .bss.*)
}
.other : {
*(*)
}
}

View file

@ -0,0 +1,39 @@
#include "regnames.h"
.section .text
.global _get_real
_get_real:
mflr r5
mfmsr r3
// disable interrupts
rlwinm r3, r3, 0, 17, 15
mtmsr r3
lis r4, _real_mode@ha
addi r4, r4, _real_mode@l
mtspr srr0, r4
rlwinm r3, r3, 0, 28, 25 // disable IR/DR
mtspr srr1, r3
rfi
_real_mode:
mtlr r5
blr
.global _set_msr
// r3 = msr value, returns old msr
_set_msr:
mtsrr1 r3
mflr r12
lis r3, _set_msr_next@ha
addi r3, r3, _set_msr_next@l
mtsrr0 r3
mfmsr r3
rfi
_set_msr_next:
mtlr r12
blr

View file

@ -0,0 +1,175 @@
#define cr0 0
#define cr1 1
#define cr2 2
#define cr3 3
#define cr4 4
#define cr5 5
#define cr6 6
#define cr7 7
#define r0 0
#define r1 1
#define r2 2
#define r3 3
#define r4 4
#define r5 5
#define r6 6
#define r7 7
#define r8 8
#define r9 9
#define r10 10
#define r11 11
#define r12 12
#define r13 13
#define r14 14
#define r15 15
#define r16 16
#define r17 17
#define r18 18
#define r19 19
#define r20 20
#define r21 21
#define r22 22
#define r23 23
#define r24 24
#define r25 25
#define r26 26
#define r27 27
#define r28 28
#define r29 29
#define r30 30
#define r31 31
#define sp 1
#define fr0 0
#define fr1 1
#define fr2 2
#define fr3 3
#define fr4 4
#define fr5 5
#define fr6 6
#define fr7 7
#define fr8 8
#define fr9 9
#define fr10 10
#define fr11 11
#define fr12 12
#define fr13 13
#define fr14 14
#define fr15 15
#define fr16 16
#define fr17 17
#define fr18 18
#define fr19 19
#define fr20 20
#define fr21 21
#define fr22 22
#define fr23 23
#define fr24 24
#define fr25 25
#define fr26 26
#define fr27 27
#define fr28 28
#define fr29 29
#define fr30 30
#define fr31 31
#define vr0 0
#define vr1 1
#define vr2 2
#define vr3 3
#define vr4 4
#define vr5 5
#define vr6 6
#define vr7 7
#define vr8 8
#define vr9 9
#define vr10 10
#define vr11 11
#define vr12 12
#define vr13 13
#define vr14 14
#define vr15 15
#define vr16 16
#define vr17 17
#define vr18 18
#define vr19 19
#define vr20 20
#define vr21 21
#define vr22 22
#define vr23 23
#define vr24 24
#define vr25 25
#define vr26 26
#define vr27 27
#define vr28 28
#define vr29 29
#define vr30 30
#define vr31 31
#define evr0 0
#define evr1 1
#define evr2 2
#define evr3 3
#define evr4 4
#define evr5 5
#define evr6 6
#define evr7 7
#define evr8 8
#define evr9 9
#define evr10 10
#define evr11 11
#define evr12 12
#define evr13 13
#define evr14 14
#define evr15 15
#define evr16 16
#define evr17 17
#define evr18 18
#define evr19 19
#define evr20 20
#define evr21 21
#define evr22 22
#define evr23 23
#define evr24 24
#define evr25 25
#define evr26 26
#define evr27 27
#define evr28 28
#define evr29 29
#define evr30 30
#define evr31 31
#define xer 1
#define lr 8
#define ctr 9
#define dec 22
#define sdr1 25
#define srr0 26
#define srr1 27
#define sprg0 272
#define sprg1 273
#define sprg2 274
#define sprg3 275
#define prv 287
#define ibat0u 528
#define ibat0l 529
#define ibat1u 530
#define ibat1l 531
#define ibat2u 532
#define ibat2l 533
#define ibat3u 534
#define ibat3l 535
#define dbat0u 536
#define dbat0l 537
#define dbat1u 538
#define dbat1l 539
#define dbat2u 540
#define dbat2l 541
#define dbat3u 542
#define dbat3l 543
#define tlbmiss 980
#define ptehi 981
#define ptelo 982
#define hid0 1008

345
src/arch/ppc32/asm/trap.S Normal file
View file

@ -0,0 +1,345 @@
#include "regnames.h"
#define ISTATE_OFFSET_SP_FRAME 0x00
#define ISTATE_OFFSET_LR_FRAME 0x04
#define ISTATE_OFFSET_R0 0x08
#define ISTATE_OFFSET_R2 0x0c
#define ISTATE_OFFSET_R3 0x10
#define ISTATE_OFFSET_R4 0x14
#define ISTATE_OFFSET_R5 0x18
#define ISTATE_OFFSET_R6 0x1c
#define ISTATE_OFFSET_R7 0x20
#define ISTATE_OFFSET_R8 0x24
#define ISTATE_OFFSET_R9 0x28
#define ISTATE_OFFSET_R10 0x2c
#define ISTATE_OFFSET_R11 0x30
#define ISTATE_OFFSET_R13 0x34
#define ISTATE_OFFSET_R14 0x38
#define ISTATE_OFFSET_R15 0x3c
#define ISTATE_OFFSET_R16 0x40
#define ISTATE_OFFSET_R17 0x44
#define ISTATE_OFFSET_R18 0x48
#define ISTATE_OFFSET_R19 0x4c
#define ISTATE_OFFSET_R20 0x50
#define ISTATE_OFFSET_R21 0x54
#define ISTATE_OFFSET_R22 0x58
#define ISTATE_OFFSET_R23 0x5c
#define ISTATE_OFFSET_R24 0x60
#define ISTATE_OFFSET_R25 0x64
#define ISTATE_OFFSET_R26 0x68
#define ISTATE_OFFSET_R27 0x6c
#define ISTATE_OFFSET_R28 0x70
#define ISTATE_OFFSET_R29 0x74
#define ISTATE_OFFSET_R30 0x78
#define ISTATE_OFFSET_R31 0x7c
#define ISTATE_OFFSET_CR 0x80
#define ISTATE_OFFSET_SRR0 0x84
#define ISTATE_OFFSET_SRR1 0x88
#define ISTATE_OFFSET_LR 0x8c
#define ISTATE_OFFSET_CTR 0x90
#define ISTATE_OFFSET_XER 0x94
#define ISTATE_OFFSET_DAR 0x98
#define ISTATE_OFFSET_R12 0x9c
#define ISTATE_OFFSET_SP 0xa0
#define ISTATE_SIZE 0xa4
#define ALIGN_UP(s, a) (((s) + ((a) - 1)) & ~((a) - 1))
.section V_TEXT_GENESIS, "ax"
.macro CTX_SAVE
mtsprg1 r12
mfcr r12
mtsprg2 sp
lis sp, EXCEPTION_STACK@ha
addi sp, sp, EXCEPTION_STACK@l
subi sp, sp, ALIGN_UP(ISTATE_SIZE, 16)
stw r0, ISTATE_OFFSET_R0(sp)
stw r2, ISTATE_OFFSET_R2(sp)
stw r3, ISTATE_OFFSET_R3(sp)
stw r4, ISTATE_OFFSET_R4(sp)
stw r5, ISTATE_OFFSET_R5(sp)
stw r6, ISTATE_OFFSET_R6(sp)
stw r7, ISTATE_OFFSET_R7(sp)
stw r8, ISTATE_OFFSET_R8(sp)
stw r9, ISTATE_OFFSET_R9(sp)
stw r10, ISTATE_OFFSET_R10(sp)
stw r11, ISTATE_OFFSET_R11(sp)
stw r13, ISTATE_OFFSET_R13(sp)
stw r14, ISTATE_OFFSET_R14(sp)
stw r15, ISTATE_OFFSET_R15(sp)
stw r16, ISTATE_OFFSET_R16(sp)
stw r17, ISTATE_OFFSET_R17(sp)
stw r18, ISTATE_OFFSET_R18(sp)
stw r19, ISTATE_OFFSET_R19(sp)
stw r20, ISTATE_OFFSET_R20(sp)
stw r21, ISTATE_OFFSET_R21(sp)
stw r22, ISTATE_OFFSET_R22(sp)
stw r23, ISTATE_OFFSET_R23(sp)
stw r24, ISTATE_OFFSET_R24(sp)
stw r25, ISTATE_OFFSET_R25(sp)
stw r26, ISTATE_OFFSET_R26(sp)
stw r27, ISTATE_OFFSET_R27(sp)
stw r28, ISTATE_OFFSET_R28(sp)
stw r29, ISTATE_OFFSET_R29(sp)
stw r30, ISTATE_OFFSET_R30(sp)
stw r31, ISTATE_OFFSET_R31(sp)
stw r12, ISTATE_OFFSET_CR(sp)
mfsrr0 r12
stw r12, ISTATE_OFFSET_SRR0(sp)
mfsrr1 r12
stw r12, ISTATE_OFFSET_SRR1(sp)
mflr r12
stw r12, ISTATE_OFFSET_LR(sp)
mfctr r12
stw r12, ISTATE_OFFSET_CTR(sp)
mfxer r12
stw r12, ISTATE_OFFSET_XER(sp)
mfdar r12
stw r12, ISTATE_OFFSET_DAR(sp)
mfsprg1 r12
stw r12, ISTATE_OFFSET_R12(sp)
mfsprg2 r12
stw r12, ISTATE_OFFSET_SP(sp)
li r12, 0
stw r12, ISTATE_OFFSET_LR_FRAME(sp)
stw r12, ISTATE_OFFSET_SP_FRAME(sp)
.endm
// CTX_SAVE is 232 bytes, you get 6 more instructions
// _decrementer_exception (1)
// called when the decrementer exception occurs (i.e. when it reaches 0)
.global _decrementer_exception
_decrementer_exception:
CTX_SAVE
lis r12, _trap_de@ha
addi r12, r12, _trap_de@l
mtlr r12
blr
// end of _decrementer_exception
.global _decrementer_exception_end
_decrementer_exception_end:
.global _trap_de
_trap_de:
lis r12, trap@ha
addi r12, r12, trap@l
mr r3, sp
li r4, 1 // 1 = dec
mtsrr0 r12
mfmsr r12
rlwinm r12, r12, 0, 28, 25 // unset IR and DR
andi. r12, r12, ~(1 << 15)@l // disable interrupts
mtsrr1 r12
rfi
// _external_interrupt (2)
// called on all external hardware interrupts
.global _external_interrupt
_external_interrupt:
CTX_SAVE
lis r12, _trap_ex@ha
addi r12, r12, _trap_ex@l
mtlr r12
blr
// end of _external_interrupt
.global _external_interrupt_end
_external_interrupt_end:
.global _trap_ex
_trap_ex:
lis r12, trap@ha
addi r12, r12, trap@l
mr r3, sp
li r4, 2 // 2 = external
mtsrr0 r12
mfmsr r12
rlwinm r12, r12, 0, 28, 25 // unset IR and DR
andi. r12, r12, ~(1 << 15)@l // disable interrupts
mtsrr1 r12
rfi
// _syscall_exception (3)
// called when sc instruction is executed
.global _syscall_exception
_syscall_exception:
CTX_SAVE
lis r12, _trap_sc@ha
addi r12, r12, _trap_sc@l
mtlr r12
blr
// end of _syscall_exception
.global _syscall_exception_end
_syscall_exception_end:
.global _trap_sc
_trap_sc:
lis r12, trap@ha
addi r12, r12, trap@l
mr r3, sp
li r4, 3 // 3 = syscall
mtsrr0 r12
mfmsr r12
rlwinm r12, r12, 0, 28, 25 // unset IR and DR
andi. r12, r12, ~(1 << 15)@l // disable interrupts
mtsrr1 r12
rfi
// _bad_exception (4)
// generic handler for when something bad happens
.global _be_ds
_be_ds:
CTX_SAVE
lis r12, _trap_be@ha
addi r12, r12, _trap_be@l
li r5, 3 // data storage
mtlr r12
blr
// end of _be_ds
.global _be_ds_end
_be_ds_end:
.global _be_is
_be_is:
CTX_SAVE
lis r12, _trap_be@ha
addi r12, r12, _trap_be@l
li r5, 4 // instruction storage
mtlr r12
blr
// end of _be_is
.global _be_is_end
_be_is_end:
.global _be_al
_be_al:
CTX_SAVE
lis r12, _trap_be@ha
addi r12, r12, _trap_be@l
li r5, 6 // alignment
mtlr r12
blr
// end of _be_al
.global _be_al_end
_be_al_end:
.global _be_pr
_be_pr:
CTX_SAVE
lis r12, _trap_be@ha
addi r12, r12, _trap_be@l
li r5, 7 // program (illegal instruction)
mtlr r12
blr
// end of _be_pr
.global _be_pr_end
_be_pr_end:
.global _be_fp
_be_fp:
CTX_SAVE
lis r12, _trap_be@ha
addi r12, r12, _trap_be@l
li r5, 8 // floating point unavailable
mtlr r12
blr
// end of _be_fp
.global _be_fp_end
_be_fp_end:
.global _trap_be
_trap_be:
lis r12, trap@ha
addi r12, r12, trap@l
mr r3, sp
li r4, 4 // 4 = bad exception
mtsrr0 r12
rlwinm r12, r12, 0, 28, 25 // unset IR and DR
andi. r12, r12, ~(1 << 15)@l // disable interrupts
mtsrr1 r12
rfi
.global _vap_return_from_exception
_vap_return_from_exception:
// load r3 into sp
mr sp, r3
sync
isync
// restore context
lwz r0, ISTATE_OFFSET_R0(sp)
lwz r2, ISTATE_OFFSET_R2(sp)
lwz r3, ISTATE_OFFSET_R3(sp)
lwz r4, ISTATE_OFFSET_R4(sp)
lwz r5, ISTATE_OFFSET_R5(sp)
lwz r6, ISTATE_OFFSET_R6(sp)
lwz r7, ISTATE_OFFSET_R7(sp)
lwz r8, ISTATE_OFFSET_R8(sp)
lwz r9, ISTATE_OFFSET_R9(sp)
lwz r10, ISTATE_OFFSET_R10(sp)
lwz r11, ISTATE_OFFSET_R11(sp)
lwz r13, ISTATE_OFFSET_R13(sp)
lwz r14, ISTATE_OFFSET_R14(sp)
lwz r15, ISTATE_OFFSET_R15(sp)
lwz r16, ISTATE_OFFSET_R16(sp)
lwz r17, ISTATE_OFFSET_R17(sp)
lwz r18, ISTATE_OFFSET_R18(sp)
lwz r19, ISTATE_OFFSET_R19(sp)
lwz r20, ISTATE_OFFSET_R20(sp)
lwz r21, ISTATE_OFFSET_R21(sp)
lwz r22, ISTATE_OFFSET_R22(sp)
lwz r23, ISTATE_OFFSET_R23(sp)
lwz r24, ISTATE_OFFSET_R24(sp)
lwz r25, ISTATE_OFFSET_R25(sp)
lwz r26, ISTATE_OFFSET_R26(sp)
lwz r27, ISTATE_OFFSET_R27(sp)
lwz r28, ISTATE_OFFSET_R28(sp)
lwz r29, ISTATE_OFFSET_R29(sp)
lwz r30, ISTATE_OFFSET_R30(sp)
lwz r31, ISTATE_OFFSET_R31(sp)
lwz r12, ISTATE_OFFSET_CR(sp)
mtcr r12
lwz r12, ISTATE_OFFSET_SRR0(sp)
mtsrr0 r12
lwz r12, ISTATE_OFFSET_SRR1(sp)
mtsrr1 r12
lwz r12, ISTATE_OFFSET_LR(sp)
mtlr r12
lwz r12, ISTATE_OFFSET_CTR(sp)
mtctr r12
lwz r12, ISTATE_OFFSET_XER(sp)
mtxer r12
lwz r12, ISTATE_OFFSET_R12(sp)
lwz sp, ISTATE_OFFSET_SP(sp)
// return to task
rfi
.global _hung_system
_hung_system:
b _hung_system
.align 4
.space 0x2000
EXCEPTION_STACK:

0
src/arch/ppc32/memory.rs Normal file
View file

242
src/arch/ppc32/mod.rs Normal file
View file

@ -0,0 +1,242 @@
pub mod trap;
mod trap_patch;
mod memory;
use crate::arch::ofw::EntryFunction;
use crate::arch::ofw::ofw_framebuffer::terminal::FB_TERMINAL;
use crate::arch::ofw::ofw_framebuffer::{FRAMEBUFFER, OFFramebuffer};
use crate::arch::ofw::runtime::{OFW_RUNTIME, init_ofw_runtime, primary_cpu_freq};
use crate::arch::ppc32::trap::TrapFrame;
use core::arch::{asm};
use core::fmt::Write;
use core::panic::PanicInfo;
use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use crate::{arch, ktask};
use crate::memory::TOTAL_MEMORY;
use crate::syscalls::create_task;
use crate::trafficcontrol::TaskSetup;
pub static PPC_HEAP_START: AtomicUsize = AtomicUsize::new(0);
#[unsafe(no_mangle)]
extern "C" fn eh_personality() {}
#[unsafe(no_mangle)]
extern "C" fn rust_eh_personality() {}
#[panic_handler]
fn panic_handler(info: &PanicInfo) -> ! {
let msr = MSRSetup {
external_interrupt_enable: false,
usermode: false,
floating_point_enable: false,
machine_check_enable: false,
instruction_translation: false,
data_translation: false,
};
unsafe {
set_msr(msr_value(&msr));
}
unsafe {
FB_TERMINAL.force_unlock();
FRAMEBUFFER.force_unlock();
OFW_RUNTIME.force_unlock();
}
let mut serial = FB_TERMINAL.lock();
serial.write_fmt(format_args!("panic! {}", info.message()));
serial.write_fmt(format_args!("{:?}\n", info));
loop {
unsafe { asm!("nop") }
}
}
pub static DEC_SPEED: AtomicU32 = AtomicU32::new(1000000);
unsafe extern "C" {
fn _get_real();
fn _set_msr(msr: usize) -> usize;
}
#[unsafe(no_mangle)]
#[unsafe(link_section = ".text")]
extern "C" fn _start(_r3: usize, _r4: usize, entry: EntryFunction) -> isize {
if !init_ofw_runtime(entry) {
return -1;
}
// init framebuffer
{
let fb = OFFramebuffer::try_attach();
if fb.is_none() {
return -1;
}
let fb = fb.unwrap();
unsafe {
*FRAMEBUFFER.lock() = Some(fb);
}
OFFramebuffer::set_default_colors();
}
let serial = crate::arch::serial_port().unwrap();
serial.putstr("lbos PowerPC kernel boot...\n");
// setup DEC
{
let freq = primary_cpu_freq();
let decspeed = freq / 100;
DEC_SPEED.store(decspeed, Ordering::Relaxed);
}
// setup heap
{
let rt = unsafe { &OFW_RUNTIME };
let heap = arch::ofw::memory::m_claim(rt.entry_fn.unwrap(), 0, TOTAL_MEMORY as i32, 512);
if heap.is_none() {
panic!("failed to claim heap");
}
PPC_HEAP_START.store(heap.unwrap(), Ordering::Relaxed);
}
unsafe {
_get_real();
}
serial.putstr("realmode asserted\n");
serial.putstr("patching trap vecs...\n");
trap_patch::patch_trap_handlers();
serial.putstr("done\n");
let msr = MSRSetup {
external_interrupt_enable: true,
usermode: false,
floating_point_enable: false,
machine_check_enable: false,
instruction_translation: false,
data_translation: false,
};
unsafe {
set_msr(msr_value(&msr));
}
unsafe {
set_dec(DEC_SPEED.load(Ordering::Relaxed));
}
create_task(TaskSetup {
epc: ktask as usize,
});
loop {
unsafe { asm!("nop") }
}
}
pub unsafe fn set_dec(dec: u32) {
asm!("mtdec {}", in(reg) dec);
}
pub struct MSRSetup {
pub external_interrupt_enable: bool,
pub usermode: bool,
pub floating_point_enable: bool,
pub machine_check_enable: bool,
pub instruction_translation: bool,
pub data_translation: bool,
}
pub fn msr_value(setup: &MSRSetup) -> u32 {
let mut value = 0;
if setup.external_interrupt_enable {
value |= 1 << 15;
}
if setup.usermode {
value |= 1 << 14;
}
if setup.floating_point_enable {
value |= 1 << 13;
}
if setup.machine_check_enable {
value |= 1 << 12;
}
if setup.instruction_translation {
value |= 1 << 5;
}
if setup.data_translation {
value |= 1 << 4;
}
value
}
#[unsafe(no_mangle)]
pub unsafe fn set_msr(msr: u32) -> u32 {
//asm!("mtmsr {}", in(reg) msr);
_set_msr(msr as usize) as u32
}
pub fn user_program_initial_trapframe(ep: usize, sp: usize) -> TrapFrame {
TrapFrame {
sp_frame: 0,
lr_frame: 0,
r0: 0,
r2: 0,
r3: 0,
r4: 0,
r5: 0,
r6: 0,
r7: 0,
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r13: 0,
r14: 0,
r15: 0,
r16: 0,
r17: 0,
r18: 0,
r19: 0,
r20: 0,
r21: 0,
r22: 0,
r23: 0,
r24: 0,
r25: 0,
r26: 0,
r27: 0,
r28: 0,
r29: 0,
r30: 0,
r31: 0,
cr: 0,
srr0: ep,
srr1: msr_value(&MSRSetup {
external_interrupt_enable: true,
usermode: false,
floating_point_enable: false,
machine_check_enable: false,
instruction_translation: false,
data_translation: false,
}) as usize,
lr: 0,
ctr: 0,
xer: 0,
dar: 0,
r12: 0,
sp,
}
}
#[inline]
pub unsafe fn _ppc32_syscall(
syscall_number: usize,
arg1: usize,
arg2: usize,
arg3: usize,
arg4: usize,
arg5: usize,
arg6: usize,
) -> usize {
let mut res;
unsafe { asm!("sc", in("r3") syscall_number, in("r4") arg1, in("r5") arg2, in("r6") arg3, in("r7") arg4, in("r8") arg5, in("r9") arg6, lateout("r3") res) };
res
}

202
src/arch/ppc32/trap.rs Normal file
View file

@ -0,0 +1,202 @@
use core::sync::atomic::Ordering;
use crate::arch::ppc32::{set_dec, user_program_initial_trapframe, DEC_SPEED};
use crate::arch::{ofw, serial_port};
use crate::strprint::u32_hex;
use crate::syscalls::usize2sc;
use crate::trafficcontrol::{context_switch, handle_syscall, MAX_TASKS, TC};
#[repr(C, align(16))]
#[derive(Clone)]
pub struct TrapFrame {
pub sp_frame: usize,
pub lr_frame: usize,
pub r0: usize,
pub r2: usize,
pub r3: usize,
pub r4: usize,
pub r5: usize,
pub r6: usize,
pub r7: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub r16: usize,
pub r17: usize,
pub r18: usize,
pub r19: usize,
pub r20: usize,
pub r21: usize,
pub r22: usize,
pub r23: usize,
pub r24: usize,
pub r25: usize,
pub r26: usize,
pub r27: usize,
pub r28: usize,
pub r29: usize,
pub r30: usize,
pub r31: usize,
pub cr: usize,
pub srr0: usize,
pub srr1: usize,
pub lr: usize,
pub ctr: usize,
pub xer: usize,
pub dar: usize,
pub r12: usize,
pub sp: usize,
}
#[repr(usize)]
#[derive(Debug)]
pub enum TrapCode {
Decrementer = 1,
External = 2,
Syscall = 3,
Bad = 4,
}
#[repr(usize)]
#[derive(Debug)]
pub enum BadExtra {
DataStorage = 3,
InstructionStorage = 4,
Alignment = 6,
Program = 7,
FloatingPointUnavail = 8,
}
unsafe extern "C" {
fn _vap_return_from_exception(frame: *mut TrapFrame) -> !;
fn _hung_system() -> !;
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn trap(frame_ptr: *mut TrapFrame, code: usize, extra: usize) -> ! {
{
let mut frame = unsafe { frame_ptr.read_volatile().clone() };
let code = match code {
x if x == TrapCode::Decrementer as usize => TrapCode::Decrementer,
x if x == TrapCode::External as usize => TrapCode::External,
x if x == TrapCode::Syscall as usize => TrapCode::Syscall,
x if x == TrapCode::Bad as usize => TrapCode::Bad,
_ => panic!("bad trap code {}", code),
};
match code {
TrapCode::Decrementer => {
//frame = crate::trafficcontrol::context_switch(frame);
//unsafe { frame_ptr.write_volatile(frame) }
// context switch
let mut tc = TC.lock();
if !tc.first_task_setup {
let mut first_task = MAX_TASKS + 1;
for i in 0..MAX_TASKS {
if tc.tasks[i].is_some() {
first_task = i;
break;
}
}
if first_task < MAX_TASKS {
if let Some(task) = tc.tasks[first_task].take() {
tc.first_task_setup = true;
unsafe { frame_ptr.write_volatile(task.trap_frame.clone()) }
tc.tasks[first_task] = Some(task);
tc.current = first_task;
}
}
} else {
let ci = tc.current;
if tc.tasks[ci].is_some() {
// only switch if we have a task
let mut curtask = unsafe { tc.tasks[ci].take().unwrap() };
if !tc.hung_system {
curtask.trap_frame = frame;
}
let next = context_switch(&mut tc, curtask);
if let Some(next) = next {
unsafe { frame_ptr.write_volatile(next.trap_frame.clone()) };
tc.hung_system = false;
} else {
tc.hung_system = true;
unsafe { frame_ptr.write_volatile(user_program_initial_trapframe(_hung_system as usize, 0)) };
}
} else {
panic!("no task to switch to!");
}
}
let decspeed = DEC_SPEED.load(Ordering::Relaxed);
if decspeed > 0 {
unsafe {
set_dec(decspeed);
}
} else {
panic!("decspeed is 0!!! this is bad!!!\n");
}
}
TrapCode::External => {
}
TrapCode::Syscall => {
/*
if log_would_block() {
panic!("syscall while log would block!")
}
logln("syscall trap\n");
let ret = {
let syscall_number = frame.r3;
let a1 = frame.r4;
let a2 = frame.r5;
let a3 = frame.r6;
let a4 = frame.r7;
let syscall = syscall_from_number(syscall_number);
crate::trafficcontrol::syscall(syscall, a1, a2, a3, a4)
};
frame.r3 = ret;
*/
let (ret, sus) = handle_syscall(
usize2sc(frame.r3),
frame.r4,
frame.r5,
frame.r6,
frame.r7,
frame.r8,
frame.r9,
);
frame.r3 = ret;
if sus {
// move to another task
let mut tc = TC.lock();
let ci = tc.current;
let mut curtask = unsafe { tc.tasks[ci].take().unwrap() };
if !tc.hung_system {
curtask.trap_frame = frame.clone();
}
let next = context_switch(&mut tc, curtask);
if let Some(next) = next {
unsafe { frame_ptr.write_volatile(next.trap_frame.clone()) };
tc.hung_system = false;
} else {
tc.hung_system = true;
unsafe { frame_ptr.write_volatile(user_program_initial_trapframe(_hung_system as usize, 0)) };
}
} else {
unsafe { frame_ptr.write_volatile(frame.clone()) };
}
}
TrapCode::Bad => {
unsafe { panic!("bad trap: {}\npc: {:x}, msr: {:x}, sp: {:x}", extra, frame.srr0, frame.srr1, frame.sp); }
}
}
}
unsafe {
_vap_return_from_exception(frame_ptr)
}
}

View file

@ -0,0 +1,57 @@
unsafe extern "C" {
// 0x900
fn _decrementer_exception();
fn _decrementer_exception_end();
// 0x500
fn _external_interrupt();
fn _external_interrupt_end();
// 0xC00
fn _syscall_exception();
fn _syscall_exception_end();
// 0x300
fn _be_ds();
fn _be_ds_end();
// 0x400
fn _be_is();
fn _be_is_end();
// 0x600
fn _be_al();
fn _be_al_end();
// 0x700
fn _be_pr();
fn _be_pr_end();
// 0x800
fn _be_fp();
fn _be_fp_end();
}
fn patch_trap(vector: u32, handler: usize, handler_end: usize) {
if handler_end - handler >= 256 {
panic!("trap handler too large for vector {:x}!", vector);
}
let src = handler as *const u8;
let dst = vector as *mut u8;
for i in 0..(handler_end - handler) {
unsafe {
dst.add(i).write_volatile(src.add(i).read_volatile());
}
}
}
pub fn patch_trap_handlers() {
patch_trap(0x900, _decrementer_exception as usize, _decrementer_exception_end as usize);
patch_trap(0x500, _external_interrupt as usize, _external_interrupt_end as usize);
patch_trap(0xC00, _syscall_exception as usize, _syscall_exception_end as usize);
patch_trap(0x300, _be_ds as usize, _be_ds_end as usize);
patch_trap(0x400, _be_is as usize, _be_is_end as usize);
patch_trap(0x600, _be_al as usize, _be_al_end as usize);
patch_trap(0x700, _be_pr as usize, _be_pr_end as usize);
patch_trap(0x800, _be_fp as usize, _be_fp_end as usize);
}

43
src/arch/virt/asm/boot.s Normal file
View file

@ -0,0 +1,43 @@
.option pic
.option norvc
.section .data
.section .text.init
.global _start
_start:
csrr t0, mhartid
bnez t0, 2f
csrw satp, zero
.option push
.option norelax
la gp, _global_pointer
.option pop
la a0, _bss_start
la a1, _bss_end
1:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
la sp, _stack_end
li t0, (0b11 << 11) | (1 << 7) | (1 << 3)
csrw mstatus, t0
la t1, _virt_init
csrw mepc, t1
la t2, _trap
csrw mtvec, t2
csrw mie, zero
la ra, 2f
mret
2:
wfi
j 2b
.global asm_enable_traps
asm_enable_traps:
csrw mie, t3
csrw mepc, ra
mret

View file

@ -0,0 +1,58 @@
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
rom (wxa) : ORIGIN = 0x80000000, LENGTH = 64 * 1024
ram (wxa) : ORIGIN = 0x80010000, LENGTH = 1048510
}
PHDRS
{
text PT_LOAD;
data PT_LOAD;
bss PT_LOAD;
}
SECTIONS {
.text : {
*(.text.init)
*(.text .text.*)
} >rom AT>rom :text
PROVIDE(_global_pointer = .);
.rodata : {
*(.rodata .rodata.*)
PROVIDE(_kernel_rom_end = .);
} >rom AT>rom :text
.data : {
*(.sdata .sdata.*) *(.data .data.*)
} >ram AT>ram :data
.bss : {
PROVIDE(_bss_start = .);
*(.sbss .sbss.*)
*(.bss .bss.*)
. = ALIGN(4096);
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
PROVIDE(_MEM_START = ORIGIN(ram));
PROVIDE(_MEM_END = ORIGIN(ram) + LENGTH(ram));
PROVIDE(_SROM_START = _kernel_rom_end);
PROVIDE(_SROM_SIZE = ORIGIN(rom) + LENGTH(rom) - _kernel_rom_end);
PROVIDE(_SROM_END = _SROM_START + _SROM_SIZE);
PROVIDE(_stack_start = _bss_end);
PROVIDE(_stack_end = _stack_start + 16384);
PROVIDE(_tstack_start = _stack_end);
PROVIDE(_tstack_end = _tstack_start + 16384);
PROVIDE(_virtio_virtqueue_start = _tstack_end);
PROVIDE(_virtio_virtqueue_end = _virtio_virtqueue_start + 32768);
PROVIDE(_heap_start = _virtio_virtqueue_end);
PROVIDE(_heap_size = _MEM_END - _heap_start);
}

62
src/arch/virt/asm/trap.s Normal file
View file

@ -0,0 +1,62 @@
.option pic
.option norvc
.section .text
.global _trap
# these macros are stolen from https://osblog.stephenmarz.com/ch4.html
# i am too lazy to write my own register saving macros
.altmacro
.set NUM_GP_REGS, 32 # Number of registers per context
.set REG_SIZE, 4 # Register size (in bytes)
# Use macros for saving and restoring multiple registers
.macro save_gp i, basereg=t6
sw x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.macro load_gp i, basereg=t6
lw x\i, ((\i)*REG_SIZE)(\basereg)
.endm
.align 4
_trap:
csrrw t6, mscratch, t6
.set i, 1
.rept 30
save_gp %i
.set i, i+1
.endr
mv t5, t6
csrr t6, mscratch
save_gp 31, t5
csrw mscratch, t5
csrr a0, mepc
csrr a1, mtval
csrr a2, mcause
csrr a3, mhartid
csrr a4, mstatus
mv a5, t5
lw sp, 132(a5)
call ktrap
# returned ra into a0
csrw mepc, a0
csrr t6, mscratch
.set i, 1
.rept 31
load_gp %i
.set i, i+1
.endr
mret
.global _hung_system
_hung_system:
1:
wfi
j 1b

113
src/arch/virt/mod.rs Normal file
View file

@ -0,0 +1,113 @@
mod plic;
pub mod tasks;
pub mod trap;
use crate::ktask;
use crate::syscalls::{create_task};
use crate::uart::{Serial, UART};
use core::arch::{asm, global_asm};
#[cfg(feature = "debug_messages")]
use core::fmt::Write;
use liblbos::TaskSetup;
global_asm!(include_str!("asm/boot.s"));
global_asm!(include_str!("asm/trap.s"));
#[unsafe(no_mangle)]
extern "C" fn eh_personality() {}
#[panic_handler]
//#[cfg(feature = "debug_messages")]
fn panic(info: &core::panic::PanicInfo) -> ! {
use core::fmt::Write;
let mut uart = UART::new(0x1000_0000);
let _ = writeln!(uart, "abort");
let _ = writeln!(uart, "message: {}", info.message());
if let Some(p) = info.location() {
let _ = writeln!(
uart,
"line {}, column {}, file {}",
p.line(),
p.column(),
p.file()
);
}
loop {
unsafe {
asm!("wfi");
}
}
}
/*
#[panic_handler]
#[cfg(not(feature = "debug_messages"))]
fn panic(_info: &core::panic::PanicInfo) -> ! {
let uart = UART::new(0x1000_0000);
for c in b"PANIC".iter() {
uart.put(*c);
}
loop {
unsafe {
asm!("wfi");
}
}
}
*/
pub fn virt_rough_panic(errcode: [char; 3]) -> ! {
let uart = UART::new(0x1000_0000);
for c in b"PANIC".iter() {
uart.put(*c);
}
for c in errcode.iter() {
uart.put(*c as _);
}
loop {
unsafe {
asm!("wfi");
}
}
}
#[unsafe(no_mangle)]
extern "C" fn _virt_init() -> ! {
UART::new_and_init(0x1000_0000);
plic::set_threshold(0);
for i in 1..=10 {
plic::enable(i);
plic::set_priority(i, 1);
}
trap::enable_traps();
create_task(TaskSetup {
epc: ktask as usize,
ddi_first_addr: 0,
ddi_size: 0,
wait_for_task_exit: false,
});
loop {
stall()
}
}
pub fn stall() {
unsafe {
asm!("wfi");
}
}
pub const fn serial_port() -> Option<&'static dyn Serial> {
static UART_STATIC: UART = UART::new(0x1000_0000);
Some(&UART_STATIC)
}
unsafe extern "C" {
fn _syscall(syscall_num: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32, a6: u32) -> u32;
}
pub fn virt_syscall(num: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32, a6: u32) -> u32 {
unsafe { _syscall(num, a1, a2, a3, a4, a5, a6) }
}

42
src/arch/virt/plic.rs Normal file
View file

@ -0,0 +1,42 @@
pub fn enable(id: u32) {
let enables = 0x0c00_2000 as *mut u32;
let actual_id = 1 << id;
unsafe {
enables.write_volatile(enables.read_volatile() | actual_id);
}
}
pub fn set_priority(id: u32, priority: u8) {
let actual_priority = priority as u32 & 7;
let priority_reg = 0x0c00_0000 as *mut u32;
unsafe {
priority_reg.add(id as usize).write_volatile(actual_priority);
}
}
pub fn set_threshold(threshold: u8) {
let actual_threshold = threshold as u32 & 7;
let threshold_reg = 0x0c20_0000 as *mut u32;
unsafe {
threshold_reg.write_volatile(actual_threshold);
}
}
pub fn next() -> Option<u32> {
let claim_reg = 0x0c20_0004 as *const u32;
let claim_num = unsafe { claim_reg.read_volatile() };
if claim_num == 0 {
None
} else {
Some(claim_num)
}
}
pub fn complete(id: u32) {
let complete_reg = 0x0c20_0004 as *mut u32;
unsafe {
complete_reg.write_volatile(id);
}
}

13
src/arch/virt/tasks.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::arch::virt::trap::{TrapFrame, _tstack_end};
pub fn setup_task(sp: usize) -> TrapFrame {
let mut tf = TrapFrame {
regs: [0; 32],
satp: 0,
trap_stack: _tstack_end as usize,
hartid: 0,
};
tf.regs[1] = crate::trafficcontrol::program_default_exit as usize;
tf.regs[2] = sp;
tf
}

251
src/arch/virt/trap.rs Normal file
View file

@ -0,0 +1,251 @@
use crate::arch::virt::{plic, serial_port};
use crate::rough_panic;
use crate::syscalls::usize2sc;
use crate::trafficcontrol::{TC, context_switch, handle_syscall, MAX_TASKS};
use core::arch::asm;
use crate::spinlock::Spinlock;
unsafe extern "C" {
pub fn _tstack_end();
pub fn _hung_system() -> !;
}
pub static KERNEL_TRAP_FRAME: Spinlock<TrapFrame> = Spinlock::new(TrapFrame {
regs: [0; 32],
satp: 0,
trap_stack: 0,
hartid: 0,
});
#[repr(C)]
#[derive(Clone)]
pub struct TrapFrame {
pub regs: [usize; 32], // 0 - 128
pub satp: usize, // 128 - 132
pub trap_stack: usize, // 132 - 136
pub hartid: usize, // 136 - 140
}
pub fn enable_traps() {
let tf = {
let mut lock = KERNEL_TRAP_FRAME.lock();
lock.trap_stack = _tstack_end as usize;
unsafe { KERNEL_TRAP_FRAME.leak() }
};
// setup mscratch to hold trap frame
unsafe {
let addr = tf;
asm!("csrw mscratch, {}", in(reg) addr);
let mie: u32 = (1 << 3) | (1 << 7) | (1 << 11);
asm!("csrw mie, {}", in(reg) mie);
}
}
#[unsafe(no_mangle)]
extern "C" fn ktrap(
epc: usize,
tval: usize,
cause: usize,
hart: usize,
status: usize,
frame: &mut TrapFrame,
) -> usize {
#[allow(arithmetic_overflow)]
let is_async = { (cause >> 63) & 1 == 1 };
let cause_num = cause & 0xfff;
let mut return_pc = epc;
if is_async {
match cause_num {
3 => {
//send_ka(KernelAlert::MachineSoftwareInterrupt);
}
7 => {
let mtimecmp = 0x0200_4000 as *mut u64;
let mtime = 0x0200_bff8 as *const u64;
// one sec
const FREQ: u64 = 10_000_000;
const CSPS: u64 = 5000;
unsafe { mtimecmp.write_volatile(mtime.read_volatile().wrapping_add(FREQ / CSPS)) };
//send_ka(KernelAlert::TimerInterrupt);
// context switch
let mut tc = TC.lock();
if !tc.first_task_setup {
let mut first_task = MAX_TASKS + 1;
for i in 0..MAX_TASKS {
if tc.tasks[i].is_some() {
first_task = i;
break;
}
}
if first_task < MAX_TASKS {
if let Some(task) = tc.tasks[first_task].take() {
tc.first_task_setup = true;
return_pc = task.epc;
*frame = task.trap_frame.clone();
tc.tasks[first_task] = Some(task);
tc.current = first_task;
}
}
} else {
let ci = tc.current;
if tc.tasks.get(ci).is_some() {
// only switch if we have a task
let mut curtask = unsafe { tc.tasks[ci].take().unwrap_unchecked() };
if !tc.hung_system {
curtask.epc = epc;
curtask.trap_frame = frame.clone();
}
let next = context_switch(&mut tc, curtask);
if let Some(next) = next {
return_pc = next.epc;
*frame = next.trap_frame.clone();
tc.hung_system = false;
} else {
return_pc = _hung_system as usize;
tc.hung_system = true;
}
} else {
rough_panic(['n', 'm', 'p']);
}
}
}
11 => {
//send_ka(KernelAlert::MachineExternalInterrupt);
if let Some(interrupt) = plic::next() {
#[cfg(feature = "dev_virtio")]
if (1..=8).contains(&interrupt) {
// virtio
let mut tc = TC.lock();
crate::dev::virtio::handle_interrupt(interrupt, &mut tc);
}
if interrupt == 10 {
// uart
let uart = unsafe { serial_port().unwrap_unchecked() };
if let Some(c) = uart.get() {
if c != 0 {
let mut tc = TC.lock();
tc.write_inbuf(c);
}
}
}
plic::complete(interrupt);
}
}
_ => {
#[cfg(feature = "debug_messages")]
panic!("unhandled async trap cpu{} -> {}", hart, cause_num);
#[cfg(not(feature = "debug_messages"))]
rough_panic(['t', 'r', 'a'])
}
}
} else {
match cause_num {
2 => {
#[cfg(feature = "debug_messages")]
panic!(
"illegal instruction CPU#{} -> 0x{:08x}: 0x{:08x}\n",
hart, epc, tval
);
#[cfg(not(feature = "debug_messages"))]
rough_panic(['t', 'i', 'l'])
}
x if x == 8 || x == 9 || x == 11 => {
let (ret, sus) = handle_syscall(
usize2sc(frame.regs[10]),
frame.regs[11],
frame.regs[12],
frame.regs[13],
frame.regs[14],
frame.regs[15],
frame.regs[16],
);
frame.regs[10] = ret;
return_pc += 4;
if sus {
// move to another task
let mut tc = TC.lock();
let ci = tc.current;
let mut curtask = unsafe { tc.tasks[ci].take().unwrap_unchecked() };
if !tc.hung_system {
curtask.epc = return_pc;
curtask.trap_frame = frame.clone();
}
let next = context_switch(&mut tc, curtask);
if let Some(next) = next {
return_pc = next.epc;
*frame = next.trap_frame.clone();
tc.hung_system = false;
} else {
return_pc = _hung_system as usize;
tc.hung_system = true;
}
}
}
/*
9 => {
// super
//send_ka(KernelAlert::SysCall);
let ret = handle_syscall(
usize2sc(frame.regs[10]),
frame.regs[11],
frame.regs[12],
frame.regs[13],
frame.regs[14],
frame.regs[15],
frame.regs[16],
);
frame.regs[10] = ret;
return_pc += 4;
}
11 => {
// mach
//send_ka(KernelAlert::SysCall);
let ret = handle_syscall(
usize2sc(frame.regs[10]),
frame.regs[11],
frame.regs[12],
frame.regs[13],
frame.regs[14],
frame.regs[15],
frame.regs[16],
);
frame.regs[10] = ret;
return_pc += 4;
}
*/
12 => {
//send_ka(KernelAlert::IPageFault);
return_pc += 4;
}
13 => {
//send_ka(KernelAlert::LPageFault);
return_pc += 4;
}
15 => {
//send_ka(KernelAlert::SPageFault);
return_pc += 4;
}
_ => {
#[cfg(feature = "debug_messages")]
panic!("unhandled sync trap cpu{} -> {}", hart, cause_num);
#[cfg(not(feature = "debug_messages"))]
{
let uart = crate::uart::UART::new(0x1000_0000);
uart.put(b'0' + cause_num as u8);
rough_panic(['t', 'r', 's'])
}
}
}
}
if return_pc < 0x8000_0000 {
rough_panic(['r', 'a', 'n'])
}
return_pc
}

165
src/ddi.rs Normal file
View file

@ -0,0 +1,165 @@
use ddi::{read_ddi_header, read_ddi_lbos_header, read_ddi_relocation_header, read_ddi_segment_header, DDIType};
use crate::rough_panic;
use crate::strprint::u32_hex;
pub enum DDILoadError {
InvalidMagic,
UnsupportedDDI,
UnsupportedDDIVersion,
CorruptDDI,
OutOfMemory,
}
/// takes a ddi file and loads the segments into memory
/// returns (entrypoint, first addr, amount of bytes)
/// performs relocations
pub fn load_ddi(file_contents: &[u8]) -> Result<(usize, usize, usize), DDILoadError> {
let header = read_ddi_header(file_contents);
if let Some(header) = header {
if header.magic != *b"ddI\0" {
return Err(DDILoadError::InvalidMagic);
}
if header.ddi_type != DDIType::LBOSRiscV as u8 {
return Err(DDILoadError::UnsupportedDDI);
}
if header.version != 1 {
return Err(DDILoadError::UnsupportedDDIVersion);
}
let header = read_ddi_lbos_header(file_contents);
if let Some(header) = header {
// figure out the size
let mut total_size = 0;
for i in 0..header.segment_header_count as usize {
let seg = read_ddi_segment_header(file_contents, &header, i);
if let Some(seg) = seg {
let size = seg.segment_size as usize;
total_size += size;
} else {
return Err(DDILoadError::CorruptDDI);
}
if total_size % 512 != 0 {
// align up to 512 for the next segment
total_size += 512 - (total_size % 512);
}
}
if total_size % 512 != 0 {
rough_panic(['d', 'n', 'a' as char]);
}
// allocate memory
let addr = liblbos::syscalls::alloc_blocks(total_size / 512);
// copy segments
let mut cursor = 0;
for i in 0..header.segment_header_count as usize {
let seg = read_ddi_segment_header(file_contents, &header, i);
if let Some(seg) = seg {
let offset = seg.segment_pointer;
let slice = &file_contents[offset as usize..offset as usize + seg.segment_size as usize];
let target_slice = unsafe { core::slice::from_raw_parts_mut((addr + cursor) as *mut u8, seg.segment_size as usize) };
target_slice.copy_from_slice(slice);
cursor += seg.segment_size as usize;
} else {
rough_panic(['d', 'b', 's' as char]);
}
if cursor % 512 != 0 {
// align up cursor for next segment just like above
cursor += 512 - (cursor % 512);
}
}
// apply relocations
let cursor = 0;
for i in 0..header.relocation_header_count as usize {
let reloc = read_ddi_relocation_header(file_contents, &header, i);
if let Some(reloc) = reloc {
let segment = read_ddi_segment_header(file_contents, &header, reloc.relocation_segment as usize);
if let Some(segment) = segment {
let mut current_segment_base = 0;
for i in 0..header.segment_header_count as usize {
if i == reloc.relocation_segment as usize {
break;
}
let seg = read_ddi_segment_header(file_contents, &header, i);
if let Some(seg) = seg {
current_segment_base += seg.segment_size as usize;
}
if current_segment_base % 512 != 0 {
current_segment_base += 512 - (current_segment_base % 512);
}
}
let current_segment_base = current_segment_base + addr;
let mut target_segment_base = 0;
for i in 0..header.segment_header_count as usize {
if i == reloc.target_segment as usize {
break;
}
let seg = read_ddi_segment_header(file_contents, &header, i);
if let Some(seg) = seg {
target_segment_base += seg.segment_size as usize;
}
if target_segment_base % 512 != 0 {
target_segment_base += 512 - (target_segment_base % 512);
}
}
let target_slice = unsafe {
core::slice::from_raw_parts_mut((addr + target_segment_base) as *mut u8, segment.segment_size as usize)
};
let target_segment_base = target_segment_base + addr;
#[cfg(feature = "arch_virt")]
{
//let prev_instr = u32::from_le_bytes(target_slice[reloc.relocation_pointer as usize..reloc.relocation_pointer as usize + 4].try_into().unwrap());
//liblbos::syscalls::write_terminal(b"prev instruction ");
//liblbos::syscalls::write_terminal(&u32_hex(prev_instr));
//liblbos::syscalls::write_terminal(b"\n");
//if reloc.relocation_type == ddi::arch::riscv32::RiscVRelocationType::CallPLT as u16 || reloc.relocation_type == ddi::arch::riscv32::RiscVRelocationType::Call as u16 {
// let prev_instr_2 = u32::from_le_bytes(target_slice[reloc.relocation_pointer as usize + 4..reloc.relocation_pointer as usize + 8].try_into().unwrap());
// liblbos::syscalls::write_terminal(&u32_hex(prev_instr_2));
// liblbos::syscalls::write_terminal(b"\n");
//}
//liblbos::syscalls::write_terminal(b"\n");
ddi::arch::riscv32::apply_relocation(
target_slice,
current_segment_base as u32,
target_segment_base as u32,
&reloc,
|x| {
liblbos::syscalls::write_terminal(b"unhandled relocation type: ");
liblbos::syscalls::write_terminal(&u32_hex(x as u32));
liblbos::syscalls::write_terminal(b"\n");
}
);
//let final_instr = u32::from_le_bytes(target_slice[reloc.relocation_pointer as usize..reloc.relocation_pointer as usize + 4].try_into().unwrap());
//liblbos::syscalls::write_terminal(b"final instruction ");
//liblbos::syscalls::write_terminal(&u32_hex(final_instr));
//liblbos::syscalls::write_terminal(b"\n");
//if reloc.relocation_type == ddi::arch::riscv32::RiscVRelocationType::CallPLT as u16 || reloc.relocation_type == ddi::arch::riscv32::RiscVRelocationType::Call as u16 {
// let prev_instr_2 = u32::from_le_bytes(target_slice[reloc.relocation_pointer as usize + 4..reloc.relocation_pointer as usize + 8].try_into().unwrap());
// liblbos::syscalls::write_terminal(&u32_hex(prev_instr_2));
// liblbos::syscalls::write_terminal(b"\n");
//}
//liblbos::syscalls::write_terminal(b"\n");
}
//liblbos::syscalls::write_terminal(b"relocated segment ");
//liblbos::syscalls::write_terminal(&u32_hex(reloc.relocation_segment));
//liblbos::syscalls::write_terminal(b" at ");
//liblbos::syscalls::write_terminal(&u32_hex(target_segment_base as u32 + reloc.relocation_pointer));
//liblbos::syscalls::write_terminal(b"\n");
} else {
rough_panic(['d', 'r', 's' as char]);
}
} else {
rough_panic(['d', 'b', 'r' as char]);
}
}
// calculate entrypoint
let entrypoint = header.entrypoint as usize + addr;
Ok((entrypoint, addr, total_size))
} else {
Err(DDILoadError::InvalidMagic)
}
} else {
Err(DDILoadError::InvalidMagic)
}
}

15
src/dev/mod.rs Normal file
View file

@ -0,0 +1,15 @@
use crate::trafficcontrol::TrafficControl;
#[cfg(feature = "dev_virtio")]
pub mod virtio;
pub fn probe_devices(tc: &mut TrafficControl) {
#[cfg(feature = "dev_virtio")]
virtio::probe_virtio_devices(tc);
}
pub fn read_sector(tc: &mut TrafficControl, buffer: usize, size: u32, sector: u64) -> bool {
#[cfg(feature = "dev_virtio")]
return virtio::read_sector(tc, buffer, size, sector);
false
}

223
src/dev/virtio/block.rs Normal file
View file

@ -0,0 +1,223 @@
use crate::dev::virtio::{Descriptor, VIRTIO_DESC_F_NEXT, VIRTIO_DESC_F_WRITE, VIRTIO_MMIO_GUEST_FEATURES, VIRTIO_MMIO_GUEST_PAGE_SIZE, VIRTIO_MMIO_HOST_FEATURES, VIRTIO_MMIO_QUEUE_NOTIFY, VIRTIO_MMIO_QUEUE_NUM, VIRTIO_MMIO_QUEUE_NUM_MAX, VIRTIO_MMIO_QUEUE_PFN, VIRTIO_MMIO_QUEUE_SEL, VIRTIO_MMIO_STATUS, VIRTIO_MMIO_STATUS_ACKNOWLEDGE, VIRTIO_MMIO_STATUS_DRIVER, VIRTIO_MMIO_STATUS_DRIVER_OK, VIRTIO_MMIO_STATUS_FAILED, VIRTIO_MMIO_STATUS_FEATURES_OK, VIRTIO_QUEUE_SIZE, VirtQueue, Used};
use crate::trafficcontrol::{TaskWait, TrafficControl};
unsafe extern "C" {
fn _virtio_virtqueue_start();
}
#[repr(C)]
pub struct Status {
pub status: u8,
}
#[repr(C)]
pub struct Request {
// 23 bytes
pub blktype: u32,
pub reserved: u32,
pub sector: u64,
pub data: usize,
pub status: Status,
pub head: u16,
pub tid: u8,
}
pub struct VirtIoBlockDevice {
pub addr: usize,
pub queue: usize,
pub read_only: bool,
pub idx: u16,
pub ack_used_idx: u16,
}
pub enum VirtIoBlockDeviceError {
FeatureSetMismatch,
QueueSetupFailed,
}
impl VirtIoBlockDevice {
pub fn new_and_init(
tc: &mut TrafficControl,
addr: usize,
) -> Result<Self, VirtIoBlockDeviceError> {
// reset device (write 0 to status)
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(0);
}
// set ack bit
let mut status = VIRTIO_MMIO_STATUS_ACKNOWLEDGE;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// set driver bit
status |= VIRTIO_MMIO_STATUS_DRIVER;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// read host features
let host_features =
unsafe { ((addr + VIRTIO_MMIO_HOST_FEATURES) as *const u32).read_volatile() };
let guest_features = host_features & !(1 << 5);
let read_only = host_features & (1 << 5) != 0;
unsafe {
((addr + VIRTIO_MMIO_GUEST_FEATURES) as *mut u32).write_volatile(guest_features);
}
// set features ok
status |= VIRTIO_MMIO_STATUS_FEATURES_OK;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// make sure features ok is still set, otherwise failed
if unsafe { ((addr + VIRTIO_MMIO_STATUS) as *const u32).read_volatile() }
& VIRTIO_MMIO_STATUS_FEATURES_OK
== 0
{
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(VIRTIO_MMIO_STATUS_FAILED);
}
return Err(VirtIoBlockDeviceError::FeatureSetMismatch);
}
// setup queue
let queue_max_by_device =
unsafe { ((addr + VIRTIO_MMIO_QUEUE_NUM_MAX) as *const u32).read_volatile() };
if queue_max_by_device < VIRTIO_QUEUE_SIZE as _ {
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(VIRTIO_MMIO_STATUS_FAILED);
}
return Err(VirtIoBlockDeviceError::QueueSetupFailed);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_NUM) as *mut u32).write_volatile(VIRTIO_QUEUE_SIZE as _);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_SEL) as *mut u32).write_volatile(0);
}
let queue_ptr= _virtio_virtqueue_start as usize;
unsafe { ((addr + VIRTIO_MMIO_GUEST_PAGE_SIZE) as *mut u32).write_volatile(4096) }; // who knows if this actually works :p
unsafe {
((addr + VIRTIO_MMIO_QUEUE_PFN) as *mut u32).write_volatile(queue_ptr as u32 / 4096)
};
let block_device = VirtIoBlockDevice {
addr,
queue: queue_ptr,
read_only,
idx: 0,
ack_used_idx: 0,
};
status |= VIRTIO_MMIO_STATUS_DRIVER_OK;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
Ok(block_device)
}
pub fn fill_next_descriptor(&mut self, desc: Descriptor) -> u16 {
self.idx = (self.idx + 1) % VIRTIO_QUEUE_SIZE as u16;
unsafe {
(*(self.queue as *mut VirtQueue)).desc[self.idx as usize] = desc;
}
if unsafe { (*(self.queue as *mut VirtQueue)).desc[self.idx as usize].flags }
& VIRTIO_DESC_F_NEXT
!= 0
{
unsafe {
(*(self.queue as *mut VirtQueue)).desc[self.idx as usize].next =
(self.idx + 1) % VIRTIO_QUEUE_SIZE as u16;
}
}
self.idx
}
#[unsafe(no_mangle)]
pub extern "C" fn operation(
&mut self,
tc: &mut TrafficControl,
buffer: usize,
size: u32,
sector: u64,
write: bool,
) {
if self.read_only && write {
return;
}
let blk_request = {
let block = unsafe { tc.memory_manager.as_mut().unwrap_unchecked() }.alloc_one_block();
unsafe { tc.memory_manager.as_mut().unwrap_unchecked().block_to_addr(block) }
} as *mut Request;
let desc = Descriptor {
addr: unsafe { &(*blk_request) as *const _ as u64 },
len: size_of::<Request>() as u32,
flags: VIRTIO_DESC_F_NEXT,
next: 0,
};
let head = self.fill_next_descriptor(desc);
unsafe {
(*blk_request).blktype = if write { 1 } else { 0 };
}
unsafe {
(*blk_request).sector = sector;
}
unsafe {
(*blk_request).data = buffer;
}
unsafe {
(*blk_request).status.status = 111;
}
unsafe {
(*blk_request).tid = tc.current as u8;
}
let desc = Descriptor {
addr: buffer as u64,
len: size,
flags: VIRTIO_DESC_F_NEXT | if !write { VIRTIO_DESC_F_WRITE } else { 0 },
next: 0,
};
self.fill_next_descriptor(desc);
let desc = Descriptor {
addr: unsafe { &(*blk_request).status as *const Status as u64 },
len: size_of::<Status>() as u32,
flags: VIRTIO_DESC_F_WRITE,
next: 0,
};
self.fill_next_descriptor(desc);
unsafe {
(*(self.queue as *mut VirtQueue)).avail.ring
[(*(self.queue as *mut VirtQueue)).avail.idx as usize % VIRTIO_QUEUE_SIZE] = head;
}
unsafe {
(*(self.queue as *mut VirtQueue)).avail.idx =
(*(self.queue as *mut VirtQueue)).avail.idx.wrapping_add(1);
}
unsafe {
((self.addr + VIRTIO_MMIO_QUEUE_NOTIFY) as *mut u32).write_volatile(0);
}
}
pub fn pending(&mut self, tc: &mut TrafficControl) {
let queue = unsafe { &(*(self.queue as *mut VirtQueue)) };
while self.ack_used_idx != queue.used.idx {
let elem = &queue.used.ring[self.ack_used_idx as usize % VIRTIO_QUEUE_SIZE];
self.ack_used_idx = self.ack_used_idx.wrapping_add(1);
let rq = queue.desc[elem.id as usize].addr as *mut Request;
let tid = unsafe { (*rq).tid };
let rq_block = unsafe { tc.memory_manager.as_mut().unwrap_unchecked() }.addr_to_block(rq as usize);
unsafe { tc.memory_manager.as_mut().unwrap_unchecked().free_one_block(rq_block); }
// awaken
if let Some(Some(task)) = tc.tasks.get_mut(tid as usize) {
task.wait &= !(TaskWait::HardBlockDevOperation as u32);
}
}
}
}

175
src/dev/virtio/mod.rs Normal file
View file

@ -0,0 +1,175 @@
//! virtio devices
//! WARNING: virtio is currently completely broken! don't use it!
use crate::dev::virtio::block::{VirtIoBlockDevice, VirtIoBlockDeviceError};
use crate::spinlock::Spinlock;
use crate::strprint::twodigit;
use crate::trafficcontrol::{TrafficControl};
mod block;
pub const VIRTIO_MMIO_START: usize = 0x1000_1000;
pub const VIRTIO_MMIO_END: usize = 0x1000_8000;
pub const VIRTIO_MMIO_DEVSIZE: usize = 0x1000;
pub const VIRTIO_MMIO_DEVCOUNT: usize = ((VIRTIO_MMIO_END - VIRTIO_MMIO_START) / VIRTIO_MMIO_DEVSIZE) + 1;
pub const VIRTIO_MMIO_STATUS: usize = 0x70;
pub const VIRTIO_MMIO_HOST_FEATURES: usize = 0x10;
pub const VIRTIO_MMIO_GUEST_FEATURES: usize = 0x20;
pub const VIRTIO_MMIO_GUEST_PAGE_SIZE: usize = 0x28;
pub const VIRTIO_MMIO_QUEUE_SEL: usize = 0x30;
pub const VIRTIO_MMIO_QUEUE_NUM_MAX: usize = 0x34;
pub const VIRTIO_MMIO_QUEUE_NUM: usize = 0x38;
pub const VIRTIO_MMIO_QUEUE_PFN: usize = 0x40;
pub const VIRTIO_MMIO_QUEUE_NOTIFY: usize = 0x50;
pub const VIRTIO_MMIO_STATUS_ACKNOWLEDGE: u32 = 1 << 0;
pub const VIRTIO_MMIO_STATUS_DRIVER: u32 = 1 << 1;
pub const VIRTIO_MMIO_STATUS_DRIVER_OK: u32 = 1 << 2;
pub const VIRTIO_MMIO_STATUS_FEATURES_OK: u32 = 1 << 3;
pub const VIRTIO_MMIO_STATUS_FAILED: u32 = 1 << 7;
pub const VIRTIO_DESC_F_NEXT: u16 = 1 << 0;
pub const VIRTIO_DESC_F_WRITE: u16 = 1 << 1;
pub const VIRTIO_QUEUE_SIZE: usize = 4;
pub static VIRTIO_DEVICES: Spinlock<[Option<VirtIoDevice>; VIRTIO_MMIO_DEVCOUNT]> = Spinlock::new([const { None }; VIRTIO_MMIO_DEVCOUNT]);
pub static VIRTIO_HARD_BLOCK_DEVICE: Spinlock<Option<u8>> = Spinlock::new(None);
pub enum VirtIoDevice {
BlockDevice(VirtIoBlockDevice),
}
#[repr(C)]
pub struct Descriptor { // 16 bytes
pub addr: u64,
pub len: u32,
pub flags: u16,
pub next: u16,
}
#[repr(C)]
pub struct Available { // 14 bytes
pub flags: u16,
pub idx: u16,
pub ring: [u16; VIRTIO_QUEUE_SIZE],
pub event: u16,
}
#[repr(C)]
pub struct UsedElem { // 8 bytes
pub id: u32,
pub len: u32,
}
#[repr(C)]
pub struct Used { // 38 bytes
pub flags: u16,
pub idx: u16,
pub ring: [UsedElem; VIRTIO_QUEUE_SIZE],
pub event: u16,
}
#[repr(C)]
pub struct VirtQueue {
pub desc: [Descriptor; VIRTIO_QUEUE_SIZE],
pub avail: Available,
pub pad: [u8; 4096 - (size_of::<Descriptor>() * VIRTIO_QUEUE_SIZE) - size_of::<Available>()],
pub used: Used,
}
pub fn probe_virtio_devices(tc: &mut TrafficControl) {
let serial_port = crate::arch::serial_port();
let mut devices = VIRTIO_DEVICES.lock();
for (i, addr) in (VIRTIO_MMIO_START..=VIRTIO_MMIO_END).step_by(VIRTIO_MMIO_DEVSIZE).enumerate() {
let magic = unsafe { (addr as *const u32).read_volatile() };
const VIRTIO_MAGIC: u32 = 0x7472_6976;
if magic != VIRTIO_MAGIC {
continue;
}
let device_type = unsafe { ((addr + 8) as *const u32).read_volatile() };
if device_type == 0 {
// not connected
continue;
}
#[allow(clippy::single_match)]
match device_type {
2 => {
// block device
let block_device = VirtIoBlockDevice::new_and_init(tc, addr);
if let Ok(block_device) = block_device {
devices[i] = Some(VirtIoDevice::BlockDevice(block_device));
*VIRTIO_HARD_BLOCK_DEVICE.lock() = Some(i as u8);
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device found\n");
}
} else if let Err(e) = block_device {
match e {
VirtIoBlockDeviceError::FeatureSetMismatch => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device feature mismatch\n");
}
}
VirtIoBlockDeviceError::QueueSetupFailed => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio block device queue setup failed\n");
}
}
}
}
}
x => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("unsupported device type ");
serial_port.put_bytes(&twodigit(x as u8));
serial_port.putstr("\n");
}
} // don't handle unsupported devices yet
}
}
}
pub fn handle_interrupt(interrupt: u32, tc: &mut TrafficControl) {
let idx = interrupt as usize - 1;
let mut devices = VIRTIO_DEVICES.lock();
if let Some(Some(dev)) = devices.get_mut(idx) {
match dev {
VirtIoDevice::BlockDevice(blockdev) => {
let hbd = {
let lock = VIRTIO_HARD_BLOCK_DEVICE.lock();
*lock
};
if let Some(hbd) = hbd {
if hbd as usize == idx {
blockdev.pending(tc);
}
}
}
}
}
}
pub fn read_sector(tc: &mut TrafficControl, buffer: usize, size: u32, sector: u64) -> bool {
let idx = {
let lock = VIRTIO_HARD_BLOCK_DEVICE.lock();
*lock
};
let mut devices = VIRTIO_DEVICES.lock();
if let Some(idx) = idx {
if let Some(device) = devices[idx as usize].as_mut() {
match device {
VirtIoDevice::BlockDevice(device) => {
device.operation(tc, buffer, size, sector, false);
return true;
}
}
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no device")
}
} else {
let uart = crate::uart::UART::new(0x1000_0000);
uart.putstr("no idx")
}
false
}

418
src/fs/fat32.rs Normal file
View file

@ -0,0 +1,418 @@
use crate::fs::{Record, RecordType};
#[repr(packed(1), C)]
pub struct BPB {
pub bootjmp: [u8; 3],
pub oem_name: [u8; 8],
pub bytes_per_sector: u16,
pub sectors_per_cluster: u8,
pub reserved_sectors: u16,
pub num_fats: u8,
pub root_entry_count: u16,
pub total_sectors_16: u16,
pub media_type: u8,
pub sectors_per_fat16: u16,
pub sectors_per_track: u16,
pub head_count: u16,
pub hidden_sectors: u32,
pub large_sector_count: u32,
pub sectors_per_fat: u32,
pub flags: u16,
pub version: u16,
pub root_cluster: u32,
pub fsinfo_sector: u16,
pub backup_boot_sector: u16,
pub reserved0: [u8; 12],
pub drive_number: u8,
pub reserved1: u8,
pub signature: u8,
pub serial_number: u32,
pub label: [u8; 11],
pub identifier: [u8; 8],
}
#[repr(C)]
pub struct BPBUseful {
pub oem_name: [u8; 8],
pub reserved_sectors: u16,
pub num_fats: u8,
pub sectors_per_fat_16: u32,
pub sectors_per_fat: u32,
pub sectors_per_cluster: u8,
pub root_cluster: u32,
pub padding: [u8; 1],
}
impl BPBUseful {
pub fn empty() -> Self {
Self {
oem_name: [0; 8],
reserved_sectors: 0,
num_fats: 0,
sectors_per_fat_16: 0,
sectors_per_fat: 0,
sectors_per_cluster: 0,
root_cluster: 0,
padding: [0; 1],
}
}
pub fn fat_size(&self) -> usize {
self.sectors_per_fat as usize
}
pub fn first_data_sector(&self) -> usize {
self.reserved_sectors as usize + (self.num_fats as usize * self.fat_size())
}
pub fn first_fat_sector(&self) -> usize {
self.reserved_sectors as usize
}
}
#[repr(packed(1), C)]
pub struct DirectoryEntry {
pub name: [u8; 8],
pub ext: [u8; 3],
pub attributes: u8,
pub reserved: u8,
pub creation_time_tenth: u8,
pub creation_time: u16,
pub creation_date: u16,
pub last_access_date: u16,
pub first_cluster_high: u16,
pub last_modified_time: u16,
pub last_modified_date: u16,
pub first_cluster_low: u16,
pub file_size: u32,
}
/*
#[repr(packed(1), C)]
pub struct LongFileNameEntry {
pub order: u8,
pub first_five_two_byte_characters: [u16; 5], // [u8; 10]
pub attributes: u8, // always 0x0F
pub long_entry_type: u8,
pub checksum: u8,
pub next_six_two_byte_characters: [u16; 6], // [u8; 12]
pub reserved: u16,
pub final_two_two_byte_characters: [u16; 2], // [u8; 4]
pub directory_entry: DirectoryEntry,
}
*/
#[repr(C)]
pub struct Fat32DirectoryReader {
pub underlying: Fat32FileReader,
}
impl Fat32DirectoryReader {
pub fn empty() -> Self {
Self {
underlying: Fat32FileReader::empty(),
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Fat32FileReader {
pub cluster: usize,
pub sector: usize,
pub cluster_idx: usize,
pub sector_offset: usize,
pub eof: u8,
pub implementation: u32,
pub padding: [u8; 32 - 24],
}
impl Fat32FileReader {
pub fn empty() -> Self {
Self {
cluster: 0,
sector: 0,
cluster_idx: 0,
sector_offset: 0,
eof: 0,
implementation: 0,
padding: [0; 32 - 24],
}
}
}
pub enum Fat32Error {
CouldNotReadBPB,
}
pub fn read_bpb() -> Result<BPBUseful, Fat32Error> {
let bpb_addr = crate::syscalls::alloc_blocks(1);
if crate::syscalls::read_hbd(0, bpb_addr, 1) != 0 {
return Err(Fat32Error::CouldNotReadBPB);
}
let bpb = unsafe { (bpb_addr as *const BPB).read_volatile() };
crate::syscalls::free_blocks(bpb_addr, 1);
Ok(BPBUseful {
oem_name: bpb.oem_name,
reserved_sectors: bpb.reserved_sectors,
num_fats: bpb.num_fats,
sectors_per_fat_16: bpb.sectors_per_fat16 as u32,
sectors_per_fat: bpb.sectors_per_fat,
sectors_per_cluster: bpb.sectors_per_cluster,
root_cluster: bpb.root_cluster,
padding: [0; 1],
})
}
pub fn reader_from_cluster(bpb: &BPBUseful, cluster: usize) -> Fat32FileReader {
let first_sector_of_cluster =
((cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
Fat32FileReader {
cluster,
sector: first_sector_of_cluster,
cluster_idx: 0,
sector_offset: 0,
eof: 0,
implementation: 0,
padding: [0; 32 - 24],
}
}
pub fn root_reader(bpb: &BPBUseful) -> Fat32DirectoryReader {
let cluster = bpb.root_cluster as usize;
Fat32DirectoryReader {
underlying: reader_from_cluster(bpb, cluster),
}
}
/// returns true if the record was read successfully, false if the end of the directory was reached
pub fn read_one_record(
bpb: &BPBUseful,
reader: &mut Fat32DirectoryReader,
record: &mut Record,
) -> bool {
let block_addr = crate::syscalls::alloc_blocks(1);
let buf = unsafe { core::slice::from_raw_parts_mut(block_addr as *mut u8, 512) };
// read without seeking
let old_reader = reader.underlying;
if !read_file(bpb, &mut reader.underlying, buf) {
crate::syscalls::free_blocks(block_addr, 1);
crate::syscalls::write_terminal(b"eof on dir");
return false;
}
reader.underlying = old_reader;
let mut done = false;
while !done {
let entry = unsafe {
((buf.as_ptr() as usize + reader.underlying.implementation as usize)
as *const DirectoryEntry)
.read_volatile()
};
if entry.name[0] == 0x00 {
// end of directory
crate::syscalls::free_blocks(block_addr, 1);
return false;
}
#[allow(clippy::collapsible_if)]
if entry.name[0] != 0xE5 {
if entry.attributes != 0x0F {
// for now we do not support long filenames
let mut namelen = 0;
let mut last_was_space = false;
for i in 0..8 {
if last_was_space {
if entry.name[i] != b' ' {
last_was_space = false;
}
} else if entry.name[i] == b' ' {
namelen = i;
last_was_space = true;
}
}
if namelen == 0 {
namelen = 8;
}
record.name = [0; 12];
record.name[..namelen].copy_from_slice(&entry.name[..namelen]);
if entry.attributes & 0x10 != 0 {
record.record_type = RecordType::Directory as u8;
} else {
record.record_type = RecordType::File as u8;
record.name[namelen] = b'.';
record.name[namelen + 1] = entry.ext[0];
record.name[namelen + 2] = entry.ext[1];
record.name[namelen + 3] = entry.ext[2];
}
record.target =
(entry.first_cluster_high as u32) << 16 | (entry.first_cluster_low as u32);
record.total_size_bytes = entry.file_size;
done = true;
}
}
reader.underlying.implementation += 32;
if reader.underlying.implementation >= 512 {
reader.underlying.implementation = 0;
seek_forward_one_sector(bpb, &mut reader.underlying);
if !read_file(bpb, &mut reader.underlying, buf) {
crate::syscalls::free_blocks(block_addr, 1);
return false;
}
}
}
crate::syscalls::free_blocks(block_addr, 1);
true
}
pub fn seek_forward_one_sector(bpb: &BPBUseful, reader: &mut Fat32FileReader) -> bool {
if reader.eof != 0 {
return false;
}
reader.sector += 1;
reader.sector_offset = 0;
reader.cluster_idx += 1;
// if we reached the end of the cluster, let's consult the FAT to see where to go next
if reader.cluster_idx >= bpb.sectors_per_cluster as usize {
let fat_addr = crate::syscalls::alloc_blocks(1);
let fat_ofs = reader.cluster * 4;
let fat_sector = bpb.first_fat_sector() + (fat_ofs / 512);
let ent_offset = fat_ofs % 512;
if crate::syscalls::read_hbd(fat_sector, fat_addr, 1) != 0 {
crate::syscalls::free_blocks(fat_addr, 1);
crate::syscalls::write_terminal(b"fatfailure");
return false;
}
let table_value =
unsafe { ((fat_addr+(ent_offset)) as *const u32).read_volatile() } & 0x0FFFFFFF;
if table_value >= 0x0FFFFFF8 {
// no more clusters, whole file has been read
crate::syscalls::free_blocks(fat_addr, 1);
reader.eof = 1;
return false;
} else if table_value >= 0x00000002 {
reader.cluster = table_value as usize;
reader.cluster_idx = 0;
reader.sector =
((reader.cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
reader.sector_offset = 0;
if table_value == 0x00000000 || table_value == 0x00000001 {
crate::syscalls::free_blocks(fat_addr, 1);
reader.eof = 1;
return false;
} else if table_value == 0x0FFFFFF7 {
// bad cluster, stop reading
crate::syscalls::free_blocks(fat_addr, 1);
crate::syscalls::write_terminal(b"badcluster");
reader.eof = 1;
return false;
}
} else {
// cluster is free, keep going
reader.eof = 1;
return false;
}
}
// continue reading
true
}
pub fn read_file(bpb: &BPBUseful, reader: &mut Fat32FileReader, buffer: &mut [u8]) -> bool {
if reader.eof != 0 {
return false;
}
let mut buffer_idx = 0;
let sector_addr = crate::syscalls::alloc_blocks(1);
loop {
if reader.eof != 0 {
crate::syscalls::free_blocks(sector_addr, 1);
return false;
}
// read current sector
if crate::syscalls::read_hbd(reader.sector, sector_addr, 1) != 0 {
crate::syscalls::free_blocks(sector_addr, 1);
crate::syscalls::write_terminal(b"badhdb");
return false;
}
let mut left_in_sector = 512 - reader.sector_offset;
if left_in_sector == 0 {
crate::syscalls::write_terminal(b"SECTORNULL?");
return false;
}
while buffer_idx < buffer.len() && left_in_sector > 0 {
buffer[buffer_idx] = unsafe {
(sector_addr as *const u8)
.add(reader.sector_offset)
.read_volatile()
};
buffer_idx += 1;
left_in_sector -= 1;
reader.sector_offset += 1;
}
// if sector is full, make sure next read will start at the correct position
#[allow(clippy::collapsible_if)]
if left_in_sector == 0 {
// we don't care about the return value because if it fails, it'll be handled by the next iteration
seek_forward_one_sector(bpb, reader);
}
// either buffer got filled, or we need to read the next sector
if buffer_idx < buffer.len() {
// we must've reached the end of the sector, let's read the next one
continue;
} else {
// buffer got filled, we're done
crate::syscalls::free_blocks(sector_addr, 1);
return true;
}
}
}
/*
pub fn read_directory(bpb: &BPBUseful, cluster: usize) {
let mut first_sector_of_cluster =
((cluster - 2) * bpb.sectors_per_cluster as usize) + bpb.first_data_sector();
let mut fsoc_addr = crate::syscalls::alloc_blocks(1);
if crate::syscalls::read_hbd(first_sector_of_cluster, fsoc_addr, 1) != 0 {
crate::syscalls::free_blocks(fsoc_addr, 1);
crate::syscalls::write_terminal(b"fsoc");
return;
}
let mut traveled_in_sector = 0;
loop {
let entry = unsafe { (fsoc_addr as *const DirectoryEntry).read_volatile() };
if entry.name[0] == 0x00 {
break;
}
if entry.name[0] != 0xE5 {
if entry.attributes == 0x0F {
//crate::syscalls::write_terminal(b" unsupported utf16");
} else {
crate::syscalls::write_terminal(b" - ");
let namelen = entry.name.iter().position(|&x| x == 0).unwrap_or(8);
crate::syscalls::write_terminal(&entry.name[..namelen]);
if entry.attributes & 0x10 != 0 {
crate::syscalls::write_terminal(b" (dir)");
} else {
let extlen = entry.ext.iter().position(|&x| x == 0).unwrap_or(3);
crate::syscalls::write_terminal(b".");
crate::syscalls::write_terminal(&entry.ext[..extlen]);
}
crate::syscalls::write_terminal(b"\n");
}
}
fsoc_addr += 32;
traveled_in_sector += 32;
if traveled_in_sector >= 512 {
fsoc_addr -= traveled_in_sector;
traveled_in_sector = 0;
first_sector_of_cluster += 1;
if crate::syscalls::read_hbd(first_sector_of_cluster, fsoc_addr, 1) != 0 {
crate::syscalls::free_blocks(fsoc_addr, 1);
crate::syscalls::write_terminal(b"fsoc");
return;
}
}
continue;
}
}
*/

140
src/fs/mod.rs Normal file
View file

@ -0,0 +1,140 @@
//! file system code
//! everything here runs on the KTASK!!!
//! there are many things that rely on syscalls, so this code cannot be ran under the kernel
use liblbos::fs::{Record, RecordType};
/// ALWAYS 32 BYTES
#[cfg(feature = "fs_fat32")]
pub type FileSystem = fat32::BPBUseful;
pub const _FS_SIZE_CHECK: usize = (size_of::<FileSystem>() == 32) as usize - 1;
/// ALWAYS 32 BYTES
#[cfg(feature = "fs_fat32")]
pub type DirectoryReader = fat32::Fat32DirectoryReader;
pub const _DIRREADER_SIZE_CHECK: usize = (size_of::<DirectoryReader>() == 32) as usize - 1;
/// ALWAYS 32 BYTES
#[cfg(feature = "fs_fat32")]
pub type FileReader = fat32::Fat32FileReader;
pub const _FILEREADER_SIZE_CHECK: usize = (size_of::<FileReader>() == 32) as usize - 1;
#[cfg(feature = "fs_fat32")]
pub mod fat32;
pub fn open_fs(fs: &mut FileSystem) -> bool {
#[cfg(feature = "fs_fat32")]
{
match fat32::read_bpb() {
Ok(f) => {
*fs = f;
true
}
Err(e) => {
//match e { Fat32Error::CouldNotReadBPB => {} }
false
}
}
}
}
pub fn root_reader(fs: &FileSystem) -> DirectoryReader {
#[cfg(feature = "fs_fat32")]
{
fat32::root_reader(fs)
}
}
pub fn directory_reader(fs: &FileSystem, reader: &mut DirectoryReader, target: u32) {
#[cfg(feature = "fs_fat32")]
{
let new_reader = fat32::reader_from_cluster(fs, target as usize);
*reader = DirectoryReader {
underlying: new_reader,
};
}
}
pub fn open_file(fs: &FileSystem, record: &Record, reader: &mut FileReader) {
#[cfg(feature = "fs_fat32")]
{
let new_reader = fat32::reader_from_cluster(fs, record.target as usize);
*reader = new_reader;
}
}
pub fn seek_forward(fs: &FileSystem, reader: &mut FileReader, mut sectors: usize) -> bool {
#[cfg(feature = "fs_fat32")]
{
while sectors > 0 {
if !fat32::seek_forward_one_sector(fs, reader) {
return false;
}
sectors -= 1;
}
true
}
}
pub fn read_file(fs: &FileSystem, reader: &mut FileReader, buffer: &mut [u8]) -> usize {
#[cfg(feature = "fs_fat32")]
{
if fat32::read_file(fs, reader, buffer) {
0
} else {
1
}
}
}
pub fn read_one_record(fs: &FileSystem, reader: &mut DirectoryReader, record: &mut Record) -> bool {
#[cfg(feature = "fs_fat32")]
{
fat32::read_one_record(fs, reader, record)
}
}
pub fn open_directory(fs: &FileSystem, path: &mut [u8], reader: &mut DirectoryReader) -> usize {
path.make_ascii_uppercase();
if path[0] != b'/' {
return 3;
}
let root_reader = root_reader(fs);
if path == b"/" {
*reader = root_reader;
0
} else {
let mut record = Record {
name: [0; 12],
record_type: 0,
target: 0,
total_size_bytes: 0,
};
let mut current_reader = root_reader;
let mut next_path_start = path[1..].iter().position(|&x| x == b'/').unwrap_or(path.len());
let mut search_for = &path[1..next_path_start];
loop {
if !read_one_record(fs, &mut current_reader, &mut record) {
return 1;
}
let record_name = &record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)];
if record_name == search_for {
// this is the next record we want
if record.record_type == RecordType::Directory as u8 {
let new_path_start = next_path_start + 1; // skip the slash
if new_path_start >= path.len() {
// we're at the end of the path
directory_reader(fs, &mut current_reader, record.target);
*reader = current_reader;
return 0;
}
next_path_start = path[new_path_start..].iter().position(|&x| x == b'/').unwrap_or(path.len()) + new_path_start;
search_for = &path[next_path_start..next_path_start];
directory_reader(fs, &mut current_reader, record.target);
} else {
return 2;
}
}
}
}
}

219
src/main.rs Normal file
View file

@ -0,0 +1,219 @@
#![cfg_attr(feature = "arch_ppc32", feature(asm_experimental_arch))]
#![no_std]
#![no_main]
#[cfg(feature = "arch_ppc32")]
extern crate compiler_builtins;
use liblbos::ktask::{KTaskFSOpenDirRequest, KTaskFSOpenFileRequest, KTaskFSOpenRequest, KTaskFSReadDirRequest, KTaskFSReadFileRequest, KTaskFSSeek, KTaskLoadDDIFile, KTaskRequest, KTaskRequestType};
use liblbos::{ktask, syscalls, TaskSetup};
use liblbos::syscalls::{create_task};
mod arch;
mod uart;
mod trafficcontrol;
mod memory;
mod strprint;
//mod turntable;
mod spinlock;
mod dev;
mod fs;
#[cfg(feature = "arch_ppc32")]
mod worm;
mod ddi;
pub extern "C" fn ktask() -> ! {
fn print(bytes: &[u8]) {
syscalls::write_terminal(bytes);
}
print(b"ktask spawned! welcome to lifeblood os!\n");
print(b"initializing kernel...\n");
syscalls::init_kernel();
print(b"kernel ready!\n");
//let kinfo = kinfo();
//print(b"\nkinfo\n");
//print(b"task count ");
//print(&u32_hex(kinfo.current_process_count as u32));
//print(b"\ntotal mem ");
//print(&u32_hex(kinfo.total_mem_blocks as u32));
//print(b"\nfree mem ");
//print(&u32_hex(kinfo.free_mem_blocks as u32));
//print(b"\n");
// load TURNTBL.DDI
{
fn fserr() {
print(b"FILESYSTEM ERROR DURING STARTUP ):");
rough_panic(['f', 's', 'e']);
}
let mut fs = liblbos::fs::FileSystem::empty();
if !fs::open_fs(unsafe { &mut *(&mut fs as *mut _ as *mut _) }) {
fserr();
}
let mut path = [0; 1];
path[0] = b'/';
let mut dir = liblbos::fs::DirectoryReader::empty();
if fs::open_directory(unsafe { &*(&fs as *const _ as *const _) }, path.as_mut(), unsafe { &mut *(&mut dir as *mut _ as *mut _) }) != 0 {
fserr();
}
let mut record = liblbos::fs::Record {
name: [0; 12],
record_type: 0,
target: 0,
total_size_bytes: 0,
};
fn namecmp(record: &liblbos::fs::Record, name: &[u8]) -> bool {
let record_name = &record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)];
record_name == name
}
while !namecmp(&record, b"TURNTBL.DDI") {
if !fs::read_one_record(
unsafe { &*(&fs as *const _ as *const _) },
unsafe { &mut *(&mut dir as *mut _ as *mut _) },
unsafe { &mut *(&mut record as *mut _ as *mut _) }
) {
print(b"TURNTBL.DDI NOT FOUND ):");
print(b"\n");
rough_panic(['t', 't', 'b']);
}
}
// found turntable, load it
let mut reader = liblbos::fs::FileReader::empty();
fs::open_file(
unsafe { &*(&fs as *const _ as *const _) },
unsafe { &*(&mut record as *mut _ as *mut _) },
unsafe { &mut *(&mut reader as *mut _ as *mut _) }
);
let size = record.total_size_bytes as usize;
let buf = syscalls::alloc_blocks(size.div_ceil(512));
if fs::read_file(
unsafe { &*(&fs as *const _ as *const _) },
unsafe { &mut *(&mut reader as *mut _ as *mut _) },
unsafe { core::slice::from_raw_parts_mut(buf as *mut _, size) }
) != 0 {
print(b"TURNTBL.DDI READ ERROR ):");
print(b"\n");
rough_panic(['f', 'r', 'e']);
}
// load the ddi
let file_contents = unsafe { core::slice::from_raw_parts(buf as *const _, size) };
if let Ok((entrypoint, first_addr, size)) = ddi::load_ddi(&file_contents) {
create_task(TaskSetup {
epc: entrypoint,
ddi_first_addr: first_addr,
ddi_size: size,
wait_for_task_exit: false,
});
} else {
print(b"TURNTBL.DDI INVALID DDI ):");
print(b"\n");
rough_panic(['t', 't', 'b']);
}
}
loop {
let req = syscalls::wait_for_notification();
let header = unsafe { &*(req as *const KTaskRequest) };
let replyto = header.replyto;
let reqtype = ktask::request_type_from_req(header.req);
match reqtype {
KTaskRequestType::DoNothing => {}
KTaskRequestType::SayHi => {
print(b"hi from lifeblood os ktask!\n");
}
KTaskRequestType::FSOpen => {
let req = unsafe { &mut *(req as *mut KTaskFSOpenRequest) };
if fs::open_fs(unsafe { &mut *(req.fs as *mut _) }) {
// success
req.base.retval = 0;
} else {
// failure
req.base.retval = 1;
}
}
KTaskRequestType::FSOpenDir => {
let req = unsafe { &mut *(req as *mut KTaskFSOpenDirRequest) };
req.base.retval = fs::open_directory(
unsafe { &*(req.fs as *mut _) },
unsafe { core::slice::from_raw_parts_mut(req.path as *mut _, req.pathlen as usize) },
unsafe { &mut *(req.dir as *mut _) }
) as u32;
}
KTaskRequestType::FSReadDir => {
let req = unsafe { &mut *(req as *mut KTaskFSReadDirRequest) };
if fs::read_one_record(
unsafe { &*(req.fs as *mut _) },
unsafe { &mut *(req.dir as *mut _) },
unsafe { &mut *(req.record as *mut _) }
) {
req.base.retval = 0;
} else {
req.base.retval = 1;
}
}
KTaskRequestType::FSOpenFile => {
let req = unsafe { &mut *(req as *mut KTaskFSOpenFileRequest) };
fs::open_file(
unsafe { &*(req.fs as *mut _) },
unsafe { &*(req.record as *mut _) },
unsafe { &mut *(req.reader as *mut _) }
);
req.base.retval = 0;
}
KTaskRequestType::FSReadFile => {
let req = unsafe { &mut *(req as *mut KTaskFSReadFileRequest) };
req.base.retval = fs::read_file(
unsafe { &*(req.fs as *mut _) },
unsafe { &mut *(req.reader as *mut _) },
unsafe { core::slice::from_raw_parts_mut(req.buffer, req.bufferlen as usize) }
) as u32;
}
KTaskRequestType::FSSeek => {
let req = unsafe { &mut *(req as *mut KTaskFSSeek) };
if fs::seek_forward(
unsafe { &*(req.fs as *mut _) },
unsafe { &mut *(req.reader as *mut _) },
req.sectors as usize,
) {
req.base.retval = 0;
} else {
req.base.retval = 1;
}
}
KTaskRequestType::LoadDDIFile => {
let req = unsafe { &mut *(req as *mut KTaskLoadDDIFile) };
let file_buf = unsafe { core::slice::from_raw_parts(req.file_buf_pointer as *const u8, req.file_buf_len) };
let task_setup = unsafe { &mut *(req.task_setup_ptr as *mut TaskSetup) };
if let Ok((entrypoint, first_addr, size)) = ddi::load_ddi(&file_buf) {
task_setup.epc = entrypoint;
task_setup.ddi_first_addr = first_addr;
task_setup.ddi_size = size;
req.base.retval = 0;
} else {
req.base.retval = 1;
}
}
}
syscalls::send_notification(replyto, req);
syscalls::wait_for_notif_ack(replyto);
}
}
pub fn rough_panic(errcode: [char; 3]) -> ! {
#[cfg(feature = "arch_virt")]
{
arch::virt::virt_rough_panic(errcode)
}
#[cfg(feature = "arch_ppc32")]
{
panic!("PANIC{}{}{}", errcode[0], errcode[1], errcode[2])
}
}

130
src/memory.rs Normal file
View file

@ -0,0 +1,130 @@
use crate::rough_panic;
#[cfg(feature = "arch_virt")]
unsafe extern "C" {
fn _heap_start();
fn _heap_size();
}
pub const BLOCK_SIZE: usize = 512;
#[cfg(feature = "arch_virt")]
pub const TOTAL_MEMORY: usize = 1048510;
#[cfg(feature = "arch_ppc32")]
pub const TOTAL_MEMORY: usize = 1024 * 1024; // 1MiB;
pub const MEM_BLOCKS: usize = TOTAL_MEMORY / BLOCK_SIZE;
pub struct MemoryManager {
pub heap_start: usize,
pub heap_size: usize,
pub blockmap: [u8; (MEM_BLOCKS+7) / 8],
}
impl MemoryManager {
#[cfg(feature = "arch_virt")]
pub fn init() -> Self {
Self {
heap_start: _heap_start as _,
heap_size: _heap_size as _,
blockmap: [0; (MEM_BLOCKS+7) / 8],
}
}
#[cfg(feature = "arch_ppc32")]
pub fn init(heap_start: usize, heap_size: usize) -> Self {
Self {
heap_start: heap_start as _,
heap_size: heap_size as _,
blockmap: [0; (MEM_BLOCKS+7) / 8],
}
}
pub fn block_to_addr(&self, block: usize) -> usize {
block * BLOCK_SIZE + self.heap_start
}
pub fn addr_to_block(&self, addr: usize) -> usize {
(addr - self.heap_start) / BLOCK_SIZE
}
pub fn alloc_one_block(&mut self) -> usize {
/*
for (i, v) in self.blockmap.iter_mut().enumerate() {
for j in 0..8 {
let block = i * 8 + j;
let val = (1 << j) & *v;
if val == 0 {
// free
*v |= 1 << j;
return block;
}
}
}
rough_panic(['o', 'o', 'm'])
*/
self.alloc_n_blocks(1)
}
pub fn free_one_block(&mut self, block: usize) {
self.blockmap[block / 8] &= !(1 << (block % 8));
}
// can easily fail if too many blocks are requested, will return 0 on failure
pub fn alloc_n_blocks(&mut self, n: usize) -> usize {
if n == 0 {
return 0;
}
let mut first_block = None;
let mut found = 0;
for i in 0..self.blockmap.len() {
for j in 0..8 {
let block = i * 8 + j;
let val = (1 << j) & self.blockmap[i];
if val == 0 {
// this is free
self.blockmap[i] |= 1 << j;
if first_block.is_none() {
first_block = Some(block);
}
found += 1;
if found >= n {
return first_block.unwrap();
}
} else {
// used, restart search
let mut i = 0;
while found > 0 {
found -= 1;
self.free_one_block(first_block.unwrap() + i);
i += 1;
}
first_block = None;
}
}
}
0
}
pub fn free_n_blocks(&mut self, block: usize, n: usize) {
if n == 0 || block >= MEM_BLOCKS || block + n > MEM_BLOCKS {
return;
}
for i in 0..n {
self.free_one_block(block + i);
}
}
pub fn used_blocks(&self) -> usize {
let mut used_blocks = 0;
for v in self.blockmap.iter() {
for j in 0..8 {
let val = (1 << j) & *v;
if val != 0 {
// used
used_blocks += 1;
}
}
}
used_blocks
}
}

58
src/spinlock.rs Normal file
View file

@ -0,0 +1,58 @@
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::AtomicBool;
use crate::rough_panic;
pub struct Spinlock<T> {
inner: UnsafeCell<T>,
active: AtomicBool,
}
unsafe impl<T> Sync for Spinlock<T> {}
pub struct SpinlockGuard<'a, T: 'a>(&'a Spinlock<T>);
impl<'a, T: 'a> Drop for SpinlockGuard<'a, T> {
fn drop(&mut self) {
self.0.active.store(false, core::sync::atomic::Ordering::SeqCst);
}
}
impl<'a, T: 'a> Deref for SpinlockGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.0.inner.get() }
}
}
impl<'a, T: 'a> DerefMut for SpinlockGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0.inner.get() }
}
}
impl<T> Spinlock<T> {
pub const fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
active: AtomicBool::new(false),
}
}
pub fn lock(&self) -> SpinlockGuard<'_, T> {
if self.active.swap(true, core::sync::atomic::Ordering::SeqCst) {
rough_panic(['s', 'p', 'n'])
}
SpinlockGuard(self)
}
pub unsafe fn leak(&self) -> usize {
self.inner.get() as usize
}
pub fn force_unlock(&self) {
self.active.store(false, core::sync::atomic::Ordering::SeqCst);
}
}

21
src/strprint.rs Normal file
View file

@ -0,0 +1,21 @@
pub fn twodigit(v: u8) -> [u8; 2] {
let tens = v / 10;
let ones = v % 10;
let tens_c = tens + b'0';
let ones_c = ones + b'0';
[tens_c, ones_c]
}
pub fn u32_hex(mut v: u32) -> [u8; 8] {
let mut buf = [0u8; 8];
for i in (0..8).rev() {
let num = v & 0b1111;
v >>= 4;
if num < 10 {
buf[i] = b'0' + num as u8;
} else {
buf[i] = b'A' + (num as u8 - 10);
}
}
buf
}

483
src/trafficcontrol.rs Normal file
View file

@ -0,0 +1,483 @@
use core::sync::atomic::Ordering;
use liblbos::TaskSetup;
use crate::arch::serial_port;
use crate::memory::{BLOCK_SIZE, MemoryManager};
use crate::spinlock::Spinlock;
use crate::syscalls::SysCall;
use crate::{arch};
#[repr(u32)]
pub enum TaskWait {
HardBlockDevOperation = 1 << 0,
WaitForNotification = 1 << 1,
WaitForTaskExit = 1 << 2,
}
pub const MAX_TASKS: usize = 8;
pub static TC: Spinlock<TrafficControl> = Spinlock::new(TrafficControl::empty());
pub const INBUF_LEN: usize = 32;
#[repr(C)]
pub struct KernelInfo {
pub current_process_count: usize,
pub total_mem_blocks: usize,
pub free_mem_blocks: usize,
pub input_task: u8,
pub output_task: u8,
}
/// the bool indicates if the task is now suspended
pub fn handle_syscall(
sc: SysCall,
a1: usize,
a2: usize,
a3: usize,
a4: usize,
a5: usize,
a6: usize,
) -> (usize, bool) {
let mut suspend = false;
let mut tc = TC.lock();
(
match sc {
SysCall::NoAction => 0,
SysCall::KernelInfo => {
let ki = a1 as *mut KernelInfo;
let mut process_count = 0;
for ts in &tc.tasks {
if ts.is_some() {
process_count += 1;
}
}
unsafe { (*ki).current_process_count = process_count };
let heap_size = unsafe { tc.memory_manager.as_ref().unwrap_unchecked().heap_size };
unsafe {
(*ki).total_mem_blocks = heap_size / BLOCK_SIZE;
(*ki).free_mem_blocks = (heap_size / BLOCK_SIZE)
- tc.memory_manager.as_ref().unwrap_unchecked().used_blocks();
}
0
}
SysCall::CreateTask => {
let add = a1 as *mut TaskSetup;
tc.init_mem_if_not();
let blockalloc = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.alloc_n_blocks(16)
};
let sp = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.block_to_addr(blockalloc)
+ BLOCK_SIZE
};
#[cfg(feature = "arch_virt")]
let t = Some(Task {
epc: unsafe { (*add).epc },
want_exit: false,
sp,
wait: 0,
incoming_notifications: [const { None }; MAX_TASKS],
ackwait: [0; MAX_TASKS],
task_wait: 0,
ddi_mem_start_block: unsafe {
tc.memory_manager
.as_mut().unwrap_unchecked()
.addr_to_block((*add).ddi_first_addr)
},
ddi_mem_blocks_count: unsafe { (*add).ddi_size / BLOCK_SIZE },
trap_frame: arch::virt::tasks::setup_task(sp),
});
#[cfg(feature = "arch_ppc32")]
let t = Some(Task {
want_exit: false,
sp,
wait: 0,
incoming_notifications: [const { None }; MAX_TASKS],
ackwait: [0; MAX_TASKS],
trap_frame: arch::ppc32::user_program_initial_trapframe(
unsafe { (*add).epc },
sp,
)
});
for (i, ts) in tc.tasks.iter_mut().enumerate() {
if ts.is_none() {
*ts = t;
if unsafe { (*add).wait_for_task_exit } {
let ci = tc.current;
let current_task = tc.tasks[ci].as_mut().unwrap();
current_task.wait |= TaskWait::WaitForTaskExit as u32;
current_task.task_wait = i as u8;
return (i, true);
} else {
return (i, false);
}
}
}
unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.free_one_block(blockalloc);
}
MAX_TASKS + 1
}
SysCall::ExitTask => {
let current = tc.current;
if let Some(Some(task)) = &mut tc.tasks.get_mut(current) {
task.want_exit = true;
}
0
}
SysCall::CurrentTask => tc.current,
SysCall::ReadInbuf => {
let buf = unsafe { core::slice::from_raw_parts_mut(a1 as *mut u8, a2) };
let mut read = 0;
while let Some(c) = tc.read_inbuf() {
buf[read] = c;
read += 1;
if read >= buf.len() {
break;
}
}
read
}
SysCall::AllocBlocks => {
let count = a1;
let block = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.alloc_n_blocks(count)
};
let addr = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.block_to_addr(block)
};
addr
}
SysCall::FreeBlocks => {
let addr = a1;
let count = a2;
let block = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.addr_to_block(addr)
};
unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.free_n_blocks(block, count)
};
0
}
SysCall::WriteTerminal => {
let addr = a1;
let count = a2;
if let Some(uart) = serial_port() {
uart.put_bytes(unsafe {
core::slice::from_raw_parts(addr as *const u8, count)
});
}
0
}
SysCall::ReadHBD => {
let sector = a1;
let buf_addr = a2;
let count = a3;
suspend = true;
let ci = tc.current;
if let Some(task) = tc.tasks[ci].as_mut() {
task.wait |= TaskWait::HardBlockDevOperation as u32;
}
if crate::dev::read_sector(&mut tc, buf_addr, count as u32 * 512, sector as u64) {
0
} else {
suspend = false;
if let Some(task) = tc.tasks[ci].as_mut() {
task.wait &= !(TaskWait::HardBlockDevOperation as u32);
}
1
}
}
SysCall::InitKernel => {
tc.init_mem_if_not();
crate::dev::probe_devices(&mut tc);
0
}
SysCall::SendNotification => {
let taskid = a1;
let addr = a2;
let addr_block = unsafe { tc.memory_manager.as_mut().unwrap_unchecked().addr_to_block(addr) };
let ci = tc.current;
if let Some(Some(task)) = tc.tasks.get_mut(taskid).as_mut() {
let waiting = task.wait & TaskWait::WaitForNotification as u32 != 0;
if waiting {
// they should stop waiting
task.wait &= !(TaskWait::WaitForNotification as u32);
// setup the task's frame
#[cfg(feature = "arch_virt")]
{
task.trap_frame.regs[10] = addr;
}
// they preemptively acked, so lets note that down
if let Some(current_task) = tc.tasks[ci].as_mut() {
current_task.ackwait[taskid] = 1;
}
} else {
task.incoming_notifications[ci] = Some(addr_block); // queue it for them
}
0
} else {
1
}
}
SysCall::WaitForNotification => {
let ci = tc.current;
let mut retaddr = 0;
suspend = true;
if let Some(task) = tc.tasks[ci].as_mut() {
// is there already a pending notification?
for (i, block) in task.incoming_notifications.iter_mut().enumerate() {
if let Some(block) = block.take() {
// yes, there is
let addr = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.block_to_addr(block as usize)
};
if let Some(sender_task) = tc.tasks[i].as_mut() {
if sender_task.ackwait[ci] == 3 { // they are waiting for us to ack
sender_task.ackwait[ci] = 0;
} else { // they are not waiting for us to ack, so we need to preemptively ack them
sender_task.ackwait[ci] = 1;
}
}
retaddr = addr;
suspend = false;
break;
}
}
}
if suspend {
// no, set wait flag and suspend
if let Some(task) = tc.tasks[ci].as_mut() {
task.wait |= TaskWait::WaitForNotification as u32;
}
}
retaddr
}
SysCall::PendingNotifications => {
let ci = tc.current;
let mut pending = 0;
if let Some(task) = tc.tasks[ci].as_mut() {
for block in task.incoming_notifications.iter() {
if block.is_some() {
pending += 1;
}
}
}
pending
}
SysCall::WaitForNotifAck => {
let taskid = a1;
let ci = tc.current;
if let Some(task) = tc.tasks[ci].as_mut() {
let ack = task.ackwait[taskid];
if ack == 1 {
task.ackwait[taskid] = 0;
} else {
suspend = true;
task.ackwait[taskid] = 3;
}
}
0
}
},
suspend,
)
}
pub fn context_switch<'a>(tc: &'a mut TrafficControl, current: Task) -> Option<&'a Task> {
let ci = tc.current;
tc.tasks[ci] = Some(current);
for i in 0..tc.tasks.len() {
let want_exit = tc.tasks[i]
.as_ref()
.map(|task| task.want_exit)
.unwrap_or(false);
if want_exit {
tc.init_mem_if_not();
let sp = tc.tasks[i].as_ref().map(|v| v.sp).unwrap_or(0);
let ddi_start_block = tc.tasks[i].as_ref().map(|v| v.ddi_mem_start_block).unwrap_or(0);
let ddi_blocks_count = tc.tasks[i].as_ref().map(|v| v.ddi_mem_blocks_count).unwrap_or(0);
if sp != 0 {
let stackblock = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.addr_to_block(sp - BLOCK_SIZE)
};
unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.free_n_blocks(stackblock, 4)
};
unsafe {
tc.memory_manager.as_mut().unwrap_unchecked()
.free_n_blocks(ddi_start_block as usize, ddi_blocks_count as usize)
}
}
tc.tasks[i] = None;
// check if any tasks are waiting on this one to exit
for task in &mut tc.tasks {
if let Some(task) = task {
if task.wait & TaskWait::WaitForTaskExit as u32 != 0 && task.task_wait == i as u8 {
task.wait &= !(TaskWait::WaitForTaskExit as u32);
}
}
}
}
}
let mut next_task = tc.current + 1;
if next_task >= MAX_TASKS {
next_task = 0;
}
for _ in 0..(MAX_TASKS + 1) {
if let Some(task) = tc.tasks[next_task].as_ref() {
if task.wait == 0 && {
let mut waiting = false;
for v in &task.ackwait {
if *v == 3 {
waiting = true;
break;
}
}
!waiting
} {
// don't switch to a task that is waiting for something
tc.current = next_task;
return Some(task);
}
}
next_task += 1;
if next_task >= MAX_TASKS {
next_task = 0;
}
}
None
}
pub struct TrafficControl {
pub memory_manager: Option<MemoryManager>,
pub tasks: [Option<Task>; MAX_TASKS],
pub first_task_setup: bool,
pub current: usize,
pub inbuf: [u8; INBUF_LEN],
pub inbuf_read: u8,
pub inbuf_write: u8,
pub hung_system: bool,
}
pub struct Task {
#[cfg(feature = "arch_virt")]
pub epc: usize,
pub want_exit: bool,
/// THE ORIGINAL STACK POINTER THAT THE TASK STARTED WITH
pub sp: usize,
pub wait: u32,
pub incoming_notifications: [Option<usize>; MAX_TASKS],
pub ackwait: [u8; MAX_TASKS], // 3 = they have not yet received the notification, 1 = they have received the notification, 0 = we know they received the notification, or don't care
pub task_wait: u8,
pub ddi_mem_start_block: usize,
pub ddi_mem_blocks_count: usize,
#[cfg(feature = "arch_virt")]
pub trap_frame: arch::virt::trap::TrapFrame,
#[cfg(feature = "arch_ppc32")]
pub trap_frame: arch::ppc32::trap::TrapFrame,
}
impl TrafficControl {
pub const fn empty() -> Self {
TrafficControl {
memory_manager: None,
tasks: [const { None }; MAX_TASKS],
first_task_setup: false,
current: MAX_TASKS + 1,
inbuf: [0; INBUF_LEN],
inbuf_read: 0,
inbuf_write: 0,
hung_system: false,
}
}
pub fn init_mem_if_not(&mut self) {
if self.memory_manager.is_none() {
#[cfg(feature = "arch_virt")]
{
self.memory_manager = Some(MemoryManager::init())
}
#[cfg(feature = "arch_ppc32")]
{
use arch::ppc32::PPC_HEAP_START;
self.memory_manager = Some(MemoryManager::init(PPC_HEAP_START.load(Ordering::Relaxed), TOTAL_MEMORY));
}
}
}
pub fn write_inbuf(&mut self, c: u8) {
self.inbuf[self.inbuf_write as usize] = c;
self.inbuf_write += 1;
if self.inbuf_write >= INBUF_LEN as u8 {
self.inbuf_write = 0;
}
}
pub fn read_inbuf(&mut self) -> Option<u8> {
if self.inbuf_read == self.inbuf_write {
return None;
}
let c = self.inbuf[self.inbuf_read as usize];
self.inbuf_read += 1;
if self.inbuf_read >= INBUF_LEN as u8 {
self.inbuf_read = 0;
}
Some(c)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn program_default_exit() -> ! {
liblbos::syscalls::exit()
}

77
src/turntable.rs Normal file
View file

@ -0,0 +1,77 @@
use liblbos::fs::{DirectoryReader, FileReader, FileSystem, Record, RecordType};
use liblbos::ktask;
use crate::strprint::twodigit;
use crate::syscalls::{kinfo, read_inbuf};
use crate::trafficcontrol::{INBUF_LEN};
pub fn turntable_task() -> ! {
print(b"turntable task\n");
fn print(bytes: &[u8]) {
crate::syscalls::write_terminal(bytes);
}
let mut buf: [u8; INBUF_LEN] = [0; INBUF_LEN];
loop {
let read = read_inbuf(&mut buf);
if read > 0 {
for c in &buf[0..read] {
if *c == b'\r' {
let kinfo = kinfo();
print(b"\nkinfo\n");
print(b"task count ");
print(&twodigit(kinfo.current_process_count as u8));
print(b"\ntotal mem ");
print(&twodigit(kinfo.total_mem_blocks as u8));
print(b"\nfree mem ");
print(&twodigit(kinfo.free_mem_blocks as u8));
print(b"\n");
let mut fs = FileSystem::empty();
if ktask::ktask_fsopen(&mut fs) != 0 {
print(b"failed to open fs\n");
}
let mut dir = DirectoryReader::empty();
if ktask::ktask_fsopendir(&fs, &mut dir, b"/") != 0 {
print(b"failed to open dir\n");
}
let mut record = Record {
name: [0; 12],
record_type: 0,
target: 0,
total_size_bytes: 0,
};
while ktask::ktask_fsreaddir(&fs, &mut dir, &mut record) == 0 {
print(b"\n");
if record.record_type == 0 {
print(b"unexpected eod");
break;
}
print(b" - ");
print(&record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)]);
if record.record_type == RecordType::Directory as u8 {
print(b" (dir)");
} else {
print(b" (file)");
let mut reader = FileReader::empty();
let mut buf = [0u8; 16];
if ktask::ktask_fsopenfile(&fs, &record, &mut reader) != 0 {
print(b" failed to open file\n");
continue;
}
if ktask::ktask_fsreadfile(&fs, &mut reader, &mut buf) != 0 {
print(b" failed to read file\n");
continue;
}
print(b"\n (");
print(&buf);
print(b")\n");
}
}
} else {
print(&[*c]);
}
}
}
}
}

105
src/uart.rs Normal file
View file

@ -0,0 +1,105 @@
use core::fmt::Write;
pub trait Serial {
fn put(&self, c: u8);
fn putstr(&self, s: &str);
fn put_bytes(&self, s: &[u8]);
fn get(&self) -> Option<u8>;
}
pub struct UART {
offset: usize,
}
impl UART {
pub fn init(&self) {
let offset = self.offset as *mut u8;
unsafe {
// word length
let lcr = (1 << 0) | (1 << 1);
offset.add(3).write_volatile(lcr);
// enable fifo
offset.add(2).write_volatile(1 << 0);
// receiver buffer interrupts
offset.add(1).write_volatile(1 << 0);
// divisor boilerplate code, maybe check this later if we want custom baud rate
let divisor: u16 = 592;
let dlo: u8 = (divisor & 0xff) as u8;
let dhi: u8 = (divisor >> 8) as u8;
offset.add(3).write_volatile(lcr | 1 << 7);
offset.add(0).write_volatile(dlo);
offset.add(1).write_volatile(dhi);
offset.add(3).write_volatile(lcr);
}
}
pub fn new_and_init(offset: usize) -> UART {
let uart = UART { offset: offset };
uart.init();
uart
}
pub const fn new(offset: usize) -> UART {
let uart = UART { offset: offset };
uart
}
pub fn get(&self) -> Option<u8> {
unsafe {
if (self.offset as *mut u8).add(5).read_volatile() & 1 == 0 {
None
} else {
Some((self.offset as *mut u8).add(0).read_volatile())
}
}
}
pub fn put(&self, c: u8) {
unsafe {
(self.offset as *mut u8).add(0).write_volatile(c);
}
}
pub fn putstr(&self, s: &str) {
for c in s.bytes() {
self.put(c);
}
}
pub fn put_bytes(&self, s: &[u8]) {
for c in s {
self.put(*c);
}
}
}
impl Write for UART {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for c in s.bytes() {
self.put(c);
}
Ok(())
}
}
impl Serial for UART {
fn put(&self, c: u8) {
self.put(c);
}
fn putstr(&self, s: &str) {
self.putstr(s);
}
fn put_bytes(&self, s: &[u8]) {
self.put_bytes(s);
}
fn get(&self) -> Option<u8> {
self.get()
}
}

66
src/worm.rs Normal file
View file

@ -0,0 +1,66 @@
use core::cell::UnsafeCell;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, Ordering};
pub struct Worm<T> {
inner: UnsafeCell<T>,
active: AtomicBool,
}
unsafe impl<T> Sync for Worm<T> {}
pub struct WormWriter<'a, T: 'a>(&'a Worm<T>);
impl<'a, T: 'a> Drop for WormWriter<'a, T> {
fn drop(&mut self) {
self.0.active.store(false, Ordering::Relaxed);
}
}
impl<'a, T: 'a> Deref for WormWriter<'a, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.0.inner.get() }
}
}
impl<'a, T: 'a> DerefMut for WormWriter<'a, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.0.inner.get() }
}
}
impl<T> Worm<T> {
pub const fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
active: AtomicBool::new(false),
}
}
pub unsafe fn lock(&self) -> WormWriter<'_, T> {
if self.active.swap(true, Ordering::Relaxed) {
panic!("worm is locked");
}
WormWriter(self)
}
pub unsafe fn force_unlock(&self) {
self.active.store(false, Ordering::Relaxed);
}
pub fn is_locked(&self) -> bool {
self.active.load(Ordering::Relaxed)
}
}
impl<T> Deref for Worm<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
if self.active.load(Ordering::Relaxed) {
panic!("worm is locked");
}
unsafe { &*self.inner.get() }
}
}

2
turntable/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

14
turntable/Cargo.lock generated Normal file
View file

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "liblbos"
version = "0.1.0"
[[package]]
name = "turntable"
version = "0.1.0"
dependencies = [
"liblbos",
]

25
turntable/Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "turntable"
version = "0.1.0"
edition = "2024"
[dependencies]
liblbos = { path = "../liblbos" }
[profile.dev]
panic = "abort"
opt-level = "s"
debug-assertions = false
overflow-checks = false
[profile.release]
panic = "abort"
opt-level = "z"
debug-assertions = false
overflow-checks = false
lto = true
codegen-units = 1
[features]
default = ["arch_riscv32"]
arch_riscv32 = ["liblbos/arch_riscv32"]

16
turntable/build.rs Normal file
View file

@ -0,0 +1,16 @@
fn main() {
let arch = std::env::var("LBOS_ARCH").unwrap_or("riscv32".to_string());
println!("cargo:rerun-if-env-changed=LBOS_ARCH");
println!("cargo:rustc-cfg=feature=\"arch_{}\"", arch);
println!("cargo:rerun-if-changed=src/arch/{}/asm", arch);
// specify the linker.ld script
println!("cargo:rustc-link-arg=-Tsrc/arch/{arch}/asm/linker.ld");
// output relocation info
println!("cargo:rustc-link-arg=--emit-relocs");
// don't page align sections
println!("cargo:rustc-link-arg=-n");
}

View file

@ -0,0 +1,2 @@
#[cfg(feature = "arch_riscv32")]
mod riscv32;

View file

@ -0,0 +1,34 @@
OUTPUT_FORMAT( "elf32-littleriscv" )
OUTPUT_ARCH( "riscv" )
ENTRY( _start )
MEMORY
{
program (wxa) : ORIGIN = 0x0, LENGTH = 20480
}
PHDRS
{
prg PT_LOAD ;
}
SECTIONS {
.text : {
. = ALIGN(512);
*(.text .text.*)
} >program AT>program :prg
.rodata : {
*(.rodata .rodata.*)
} >program AT>program :prg
.data : {
*(.sdata .sdata.*) *(.data .data.*)
} >program AT>program :prg
.bss : {
*(.sbss .sbss.*)
*(.bss .bss.*)
} >program AT>program :prg
}

View file

@ -0,0 +1,10 @@
use crate::main;
#[unsafe(no_mangle)]
extern "C" fn _start() -> isize {
unsafe {
main();
}
0
}

202
turntable/src/main.rs Normal file
View file

@ -0,0 +1,202 @@
#![no_std]
#![no_main]
use crate::terminal::{print, println};
use liblbos::fs::{DirectoryReader, FileSystem};
static TEST: &[u8] = b"test";
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
liblbos::syscalls::write_terminal(b"abort\n");
loop {}
}
fn lsdir() {
let mut fs = FileSystem::empty();
if liblbos::ktask::ktask_fsopen(&mut fs) != 0 {
println("failed to open fs");
return;
}
let mut dir = DirectoryReader::empty();
if liblbos::ktask::ktask_fsopendir(&fs, &mut dir, b"/") != 0 {
println("failed to open dir");
return;
}
let mut record = liblbos::fs::Record {
name: [0; 12],
record_type: 0,
target: 0,
total_size_bytes: 0,
};
while liblbos::ktask::ktask_fsreaddir(&fs, &mut dir, &mut record) == 0 {
print("\n");
if record.record_type == 0 {
println("unexpected eod");
break;
}
print(" - ");
liblbos::syscalls::write_terminal(
&record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)],
);
if record.record_type == liblbos::fs::RecordType::Directory as u8 {
print(" (dir)");
} else {
print(" (file)");
}
}
print("\n");
}
fn attempt_run_file(name: &str) {
print("\n");
if name.len() > 11 {
println("filename too long");
return;
}
let mut fs = FileSystem::empty();
if liblbos::ktask::ktask_fsopen(&mut fs) != 0 {
println("failed to open fs");
return;
}
let mut dir = DirectoryReader::empty();
if liblbos::ktask::ktask_fsopendir(&fs, &mut dir, b"/") != 0 {
println("failed to open dir");
return;
}
let mut record = liblbos::fs::Record {
name: [0; 12],
record_type: 0,
target: 0,
total_size_bytes: 0,
};
fn namecmp(record: &liblbos::fs::Record, name: &[u8]) -> bool {
let record_name = &record.name[..record.name.iter().position(|&x| x == 0).unwrap_or(11)];
record_name == name
}
while !namecmp(&record, name.as_bytes()) {
if liblbos::ktask::ktask_fsreaddir(&fs, &mut dir, &mut record) != 0 {
println("file not found");
return;
}
if record.record_type == 0 {
println("unexpected eod");
return;
}
if record.record_type == liblbos::fs::RecordType::Directory as u8
&& namecmp(&record, name.as_bytes())
{
println("not a file");
return;
}
}
// found, load the file
let mut reader = liblbos::fs::FileReader::empty();
if liblbos::ktask::ktask_fsopenfile(&fs, &record, &mut reader) != 0 {
println("failed to open file");
return;
}
let size = record.total_size_bytes as usize;
let buf = liblbos::syscalls::alloc_blocks(size.div_ceil(512));
if liblbos::ktask::ktask_fsreadfile(&fs, &mut reader, unsafe { core::slice::from_raw_parts_mut(buf as *mut _, size) }) != 0 {
println("failed to read file");
}
let mut task_setup = liblbos::TaskSetup {
epc: 0,
ddi_first_addr: 0,
ddi_size: 0,
wait_for_task_exit: true,
};
if liblbos::ktask::ktask_loadddifile(buf, size, &mut task_setup as *mut _ as usize) != 0 {
println("failed to load ddi");
liblbos::syscalls::free_blocks(buf, size.div_ceil(512));
return;
}
liblbos::syscalls::free_blocks(buf, size.div_ceil(512));
let task_id = liblbos::syscalls::create_task(task_setup);
println("\ntask exited\n");
}
fn execute(cmd: &str) {
print("\n");
let mut split = cmd.split_ascii_whitespace();
if let Some(cmd) = split.next() {
match cmd {
"ls" => {
lsdir();
}
_ => {
attempt_run_file(cmd);
}
}
}
}
fn hex(mut n: u8) -> [u8; 2] {
let mut buf = [0u8; 2];
for i in (0..2).rev() {
let num = n & 0b1111;
n >>= 4;
if num < 10 {
buf[i] = b'0' + num;
} else {
buf[i] = b'a' + (num - 10);
}
}
buf
}
mod arch;
mod terminal;
#[unsafe(no_mangle)]
extern "C" fn main() {
print("\n\n");
print("turntable v");
const VERSION: &str = env!("CARGO_PKG_VERSION");
print(VERSION);
print("\n");
println("(c) 2025 Real Microsoft, LLC");
print("> ");
let mut cmdbuf = [0u8; 256];
let mut cmdbuf_len = 0;
let mut inbuf = [0u8; 8];
loop {
let read = liblbos::syscalls::read_inbuf(&mut inbuf);
if read > 0 {
for c in &inbuf[0..read] {
if cmdbuf_len >= cmdbuf.len() {
print("\x07");
} else {
// backspace
if *c == 0x7f && cmdbuf_len > 0 {
cmdbuf_len -= 1;
print("\x08 \x08");
} else {
cmdbuf[cmdbuf_len] = *c;
cmdbuf_len += 1;
if *c == b'\r' {
execute(unsafe {
core::str::from_utf8_unchecked(&cmdbuf[0..cmdbuf_len - 1])
});
cmdbuf_len = 0;
print("> ");
break;
} else {
liblbos::syscalls::write_terminal(&[*c]);
}
}
}
}
}
}
}

10
turntable/src/terminal.rs Normal file
View file

@ -0,0 +1,10 @@
pub fn print(s: &str) {
unsafe {
liblbos::syscalls::write_terminal(s.as_bytes());
}
}
pub fn println(s: &str) {
print(s);
print("\n");
}