commit 86dbf7482fe6b1e8e9ddacd588e2ef6a91af4d54 Author: wcjbr Date: Wed Jul 29 10:56:30 2026 +0800 vi and busybox diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..85d49dc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "x86_64-unknown-uefi" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0ce7a27 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "MicroPython.executeButton": [ + { + "text": "▶", + "tooltip": "运行", + "alignment": "left", + "command": "extension.executeFile", + "priority": 3.5 + } + ], + "MicroPython.syncButton": [ + { + "text": "$(sync)", + "tooltip": "同步", + "alignment": "left", + "command": "extension.execute", + "priority": 4 + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..12bf221 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ZeroOS" +version = "0.1.0" + +[[package]] +name = "zeroos-loader" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..80c0a74 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ZeroOS" +version = "0.1.0" +edition = "2024" + +[workspace] +members = ["boot"] + +[dependencies] + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/boot/Cargo.toml b/boot/Cargo.toml new file mode 100644 index 0000000..0638733 --- /dev/null +++ b/boot/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "zeroos-loader" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/boot/src/main.rs b/boot/src/main.rs new file mode 100644 index 0000000..44eded8 --- /dev/null +++ b/boot/src/main.rs @@ -0,0 +1,352 @@ +#![no_std] +#![no_main] + +use core::mem::size_of; +use core::panic::PanicInfo; +use core::ptr::{copy_nonoverlapping, null_mut}; + +type EfiHandle = *mut core::ffi::c_void; +type EfiStatus = usize; + +const EFI_SUCCESS: EfiStatus = 0; +const EFI_LOAD_ERROR: EfiStatus = 1; +const EFI_BUFFER_TOO_SMALL: EfiStatus = 5; + +const EFI_LOADED_IMAGE_PROTOCOL_GUID: EfiGuid = EfiGuid::new( + 0x5b1b31a1, + 0x9562, + 0x11d2, + [0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], +); + +const EFI_DEVICE_PATH_PROTOCOL_GUID: EfiGuid = EfiGuid::new( + 0x09576e91, + 0x6d3f, + 0x11d2, + [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], +); + +const MEDIA_DEVICE_PATH: u8 = 0x04; +const MEDIA_FILEPATH_DP: u8 = 0x04; +const END_DEVICE_PATH_TYPE: u8 = 0x7f; +const END_ENTIRE_DEVICE_PATH_SUBTYPE: u8 = 0xff; + +static ZEROOS_PATH: [u16; 23] = [ + '\\' as u16, + 'E' as u16, + 'F' as u16, + 'I' as u16, + '\\' as u16, + 'Z' as u16, + 'e' as u16, + 'r' as u16, + 'o' as u16, + 'O' as u16, + 'S' as u16, + '\\' as u16, + 'Z' as u16, + 'e' as u16, + 'r' as u16, + 'o' as u16, + 'O' as u16, + 'S' as u16, + '.' as u16, + 'E' as u16, + 'F' as u16, + 'I' as u16, + 0, +]; + +static LOADING: [u16; 22] = [ + 'L' as u16, + 'o' as u16, + 'a' as u16, + 'd' as u16, + 'i' as u16, + 'n' as u16, + 'g' as u16, + ' ' as u16, + 'Z' as u16, + 'e' as u16, + 'r' as u16, + 'o' as u16, + 'O' as u16, + 'S' as u16, + '.' as u16, + '.' as u16, + '.' as u16, + '\r' as u16, + '\n' as u16, + 0, + 0, + 0, +]; + +#[repr(C)] +#[derive(Clone, Copy)] +struct EfiGuid { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} + +impl EfiGuid { + const fn new(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> Self { + Self { + data1, + data2, + data3, + data4, + } + } +} + +#[repr(C)] +struct EfiTableHeader { + signature: u64, + revision: u32, + header_size: u32, + crc32: u32, + reserved: u32, +} + +#[repr(C)] +struct EfiSystemTable { + hdr: EfiTableHeader, + firmware_vendor: *mut u16, + firmware_revision: u32, + console_in_handle: EfiHandle, + con_in: *mut core::ffi::c_void, + console_out_handle: EfiHandle, + con_out: *mut EfiSimpleTextOutputProtocol, + standard_error_handle: EfiHandle, + std_err: *mut EfiSimpleTextOutputProtocol, + runtime_services: *mut core::ffi::c_void, + boot_services: *mut EfiBootServices, + number_of_table_entries: usize, + configuration_table: *mut core::ffi::c_void, +} + +#[repr(C)] +struct EfiSimpleTextOutputProtocol { + reset: usize, + output_string: + extern "efiapi" fn(this: *mut EfiSimpleTextOutputProtocol, string: *const u16) -> EfiStatus, +} + +#[repr(C)] +struct EfiBootServices { + hdr: EfiTableHeader, + raise_tpl: usize, + restore_tpl: usize, + allocate_pages: usize, + free_pages: usize, + get_memory_map: usize, + allocate_pool: usize, + free_pool: usize, + create_event: usize, + set_timer: usize, + wait_for_event: usize, + signal_event: usize, + close_event: usize, + check_event: usize, + install_protocol_interface: usize, + reinstall_protocol_interface: usize, + uninstall_protocol_interface: usize, + handle_protocol: extern "efiapi" fn( + handle: EfiHandle, + protocol: *const EfiGuid, + interface: *mut *mut core::ffi::c_void, + ) -> EfiStatus, + reserved: usize, + register_protocol_notify: usize, + locate_handle: usize, + locate_device_path: usize, + install_configuration_table: usize, + load_image: extern "efiapi" fn( + boot_policy: u8, + parent_image_handle: EfiHandle, + device_path: *const EfiDevicePathProtocol, + source_buffer: *mut core::ffi::c_void, + source_size: usize, + image_handle: *mut EfiHandle, + ) -> EfiStatus, + start_image: extern "efiapi" fn( + image_handle: EfiHandle, + exit_data_size: *mut usize, + exit_data: *mut *mut u16, + ) -> EfiStatus, +} + +#[repr(C)] +struct EfiLoadedImageProtocol { + revision: u32, + parent_handle: EfiHandle, + system_table: *mut EfiSystemTable, + device_handle: EfiHandle, + file_path: *mut EfiDevicePathProtocol, + reserved: *mut core::ffi::c_void, + load_options_size: u32, + load_options: *mut core::ffi::c_void, + image_base: *mut core::ffi::c_void, + image_size: u64, + image_code_type: u32, + image_data_type: u32, + unload: usize, +} + +#[repr(C, packed)] +struct EfiDevicePathProtocol { + ty: u8, + sub_type: u8, + length: [u8; 2], +} + +#[unsafe(no_mangle)] +extern "efiapi" fn efi_main( + image_handle: EfiHandle, + system_table: *mut EfiSystemTable, +) -> EfiStatus { + unsafe { + if system_table.is_null() || (*system_table).boot_services.is_null() { + return EFI_LOAD_ERROR; + } + + write(system_table, LOADING.as_ptr()); + + let boot_services = (*system_table).boot_services; + let mut loaded_image = null_mut(); + let status = ((*boot_services).handle_protocol)( + image_handle, + &EFI_LOADED_IMAGE_PROTOCOL_GUID, + &mut loaded_image, + ); + if status != EFI_SUCCESS { + return status; + } + + let loaded_image = loaded_image as *mut EfiLoadedImageProtocol; + let mut partition_device_path = null_mut(); + let status = ((*boot_services).handle_protocol)( + (*loaded_image).device_handle, + &EFI_DEVICE_PATH_PROTOCOL_GUID, + &mut partition_device_path, + ); + if status != EFI_SUCCESS { + return status; + } + + let mut device_path_buffer = [0u8; 1024]; + let device_path = match build_zeroos_device_path( + partition_device_path as *const EfiDevicePathProtocol, + &mut device_path_buffer, + ) { + Ok(path) => path, + Err(status) => return status, + }; + + let mut zeroos_image = null_mut(); + let status = ((*boot_services).load_image)( + 0, + image_handle, + device_path, + null_mut(), + 0, + &mut zeroos_image, + ); + if status != EFI_SUCCESS { + return status; + } + + ((*boot_services).start_image)(zeroos_image, null_mut(), null_mut()) + } +} + +unsafe fn build_zeroos_device_path<'a>( + partition_path: *const EfiDevicePathProtocol, + buffer: &'a mut [u8], +) -> Result<*const EfiDevicePathProtocol, EfiStatus> { + if partition_path.is_null() { + return Err(EFI_LOAD_ERROR); + } + + let partition_len = unsafe { device_path_len_without_end(partition_path)? }; + let file_path_node_len = 4 + ZEROOS_PATH.len() * size_of::(); + let total_len = partition_len + file_path_node_len + 4; + + if total_len > buffer.len() || file_path_node_len > u16::MAX as usize { + return Err(EFI_BUFFER_TOO_SMALL); + } + + unsafe { + copy_nonoverlapping( + partition_path as *const u8, + buffer.as_mut_ptr(), + partition_len, + ); + } + + let file_node = &mut buffer[partition_len..partition_len + file_path_node_len]; + file_node[0] = MEDIA_DEVICE_PATH; + file_node[1] = MEDIA_FILEPATH_DP; + file_node[2] = (file_path_node_len & 0xff) as u8; + file_node[3] = (file_path_node_len >> 8) as u8; + + unsafe { + copy_nonoverlapping( + ZEROOS_PATH.as_ptr() as *const u8, + file_node[4..].as_mut_ptr(), + ZEROOS_PATH.len() * size_of::(), + ); + } + + let end_node = &mut buffer[partition_len + file_path_node_len..total_len]; + end_node[0] = END_DEVICE_PATH_TYPE; + end_node[1] = END_ENTIRE_DEVICE_PATH_SUBTYPE; + end_node[2] = 4; + end_node[3] = 0; + + Ok(buffer.as_ptr() as *const EfiDevicePathProtocol) +} + +unsafe fn device_path_len_without_end( + mut node: *const EfiDevicePathProtocol, +) -> Result { + let mut len = 0; + + for _ in 0..256 { + let node_len = unsafe { device_path_node_len(node) }; + if node_len < 4 { + return Err(EFI_LOAD_ERROR); + } + + if unsafe { + (*node).ty == END_DEVICE_PATH_TYPE && (*node).sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE + } { + return Ok(len); + } + + len += node_len; + node = unsafe { (node as *const u8).add(node_len) as *const EfiDevicePathProtocol }; + } + + Err(EFI_LOAD_ERROR) +} + +unsafe fn device_path_node_len(node: *const EfiDevicePathProtocol) -> usize { + unsafe { ((*node).length[0] as usize) | ((*node).length[1] as usize) << 8 } +} + +unsafe fn write(system_table: *mut EfiSystemTable, text: *const u16) { + unsafe { + if !system_table.is_null() && !(*system_table).con_out.is_null() { + ((*(*system_table).con_out).output_string)((*system_table).con_out, text); + } + } +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} diff --git a/resources/hankaku.bin b/resources/hankaku.bin new file mode 100644 index 0000000..ea173f9 Binary files /dev/null and b/resources/hankaku.bin differ diff --git a/scripts/run-qemu.sh b/scripts/run-qemu.sh new file mode 100755 index 0000000..84eeca4 --- /dev/null +++ b/scripts/run-qemu.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +target_dir="${repo_dir}/target/x86_64-unknown-uefi/debug" +esp_dir="${repo_dir}/target/esp" +disk_image="${repo_dir}/target/zeroos.raw" +esp_image="${repo_dir}/target/zeroos-esp.fat" +root_image="${repo_dir}/target/zeroos-root.ext4" +root_dir="${repo_dir}/target/rootfs" +ovmf_vars="${repo_dir}/target/OVMF_VARS.4m.fd" + +ovmf_code="/usr/share/edk2/x64/OVMF_CODE.4m.fd" +ovmf_vars_template="/usr/share/edk2/x64/OVMF_VARS.4m.fd" +qemu_display="${QEMU_DISPLAY:-gtk}" +qemu_serial="${QEMU_SERIAL:-stdio}" +qemu_extra_args="${QEMU_EXTRA_ARGS:--no-reboot}" +qemu_accel="${QEMU_ACCEL:-auto}" +qemu_debug_log="${QEMU_DEBUG_LOG:-${repo_dir}/target/qemu-debug.log}" +zeroos_sh="${ZEROOS_SH:-/home/archzero/C++/busybox/busybox}" +rebuild_disk="${ZEROOS_REBUILD_DISK:-0}" +update_rootfs="${ZEROOS_UPDATE_ROOTFS:-0}" + +esp_start=2048 +esp_sectors=131072 +root_start=$((esp_start + esp_sectors)) +root_sectors=389120 +esp_size=$((esp_sectors * 512)) +root_size=$((root_sectors * 512)) +disk_size=$(((root_start + root_sectors + 2048) * 512)) + +if [[ ! -r "${ovmf_code}" || ! -r "${ovmf_vars_template}" ]]; then + echo "OVMF firmware not found under /usr/share/edk2/x64." >&2 + echo "Install the OVMF/edk2 package for your distribution." >&2 + exit 1 +fi + +cargo build --manifest-path "${repo_dir}/Cargo.toml" --target x86_64-unknown-uefi --workspace + +mkdir -p "${esp_dir}/EFI/BOOT" "${esp_dir}/EFI/ZeroOS" +cp "${target_dir}/zeroos-loader.efi" "${esp_dir}/EFI/BOOT/BOOTX64.EFI" +cp "${target_dir}/ZeroOS.efi" "${esp_dir}/EFI/ZeroOS/ZeroOS.EFI" + +truncate -s "${esp_size}" "${esp_image}" +mformat -i "${esp_image}" -F :: +mmd -i "${esp_image}" ::/EFI ::/EFI/BOOT ::/EFI/ZeroOS +mcopy -i "${esp_image}" "${esp_dir}/EFI/BOOT/BOOTX64.EFI" ::/EFI/BOOT/BOOTX64.EFI +mcopy -i "${esp_image}" "${esp_dir}/EFI/ZeroOS/ZeroOS.EFI" ::/EFI/ZeroOS/ZeroOS.EFI + +if [[ ! -f "${disk_image}" ]]; then + rebuild_disk=1 +fi + +if [[ "${rebuild_disk}" == "1" || "${update_rootfs}" == "1" ]]; then + if [[ ! -r "${zeroos_sh}" ]]; then + echo "sh source is not readable: ${zeroos_sh}" >&2 + echo "Set ZEROOS_SH=/path/to/static/busybox-or-sh." >&2 + exit 1 + fi + + rm -rf "${root_dir}" + mkdir -p "${root_dir}/bin" "${root_dir}/usr/bin" "${root_dir}/dev" "${root_dir}/etc" "${root_dir}/tmp" + cp "${zeroos_sh}" "${root_dir}/usr/bin/busybox" + while IFS= read -r applet; do + [[ -n "${applet}" ]] || continue + if [[ "${applet}" != "busybox" ]]; then + ln -f "${root_dir}/usr/bin/busybox" "${root_dir}/usr/bin/${applet}" + fi + ln -f "${root_dir}/usr/bin/busybox" "${root_dir}/bin/${applet}" + done < <("${zeroos_sh}" --list) + ln -f "${root_dir}/usr/bin/busybox" "${root_dir}/usr/bin/bash" + ln -f "${root_dir}/usr/bin/busybox" "${root_dir}/bin/bash" + + truncate -s "${root_size}" "${root_image}" + mkfs.ext4 -q -F -b 4096 -O '^64bit,^metadata_csum,^has_journal,^dir_index' -d "${root_dir}" "${root_image}" +fi + +if [[ "${rebuild_disk}" == "1" ]]; then + truncate -s "${disk_size}" "${disk_image}" + sgdisk --clear \ + --new=1:${esp_start}:$((esp_start + esp_sectors - 1)) \ + --typecode=1:ef00 \ + --change-name=1:ZeroOSESP \ + --new=2:${root_start}:$((root_start + root_sectors - 1)) \ + --typecode=2:8300 \ + --change-name=2:ZeroOSRoot \ + "${disk_image}" >/dev/null + dd if="${root_image}" of="${disk_image}" bs=512 seek="${root_start}" conv=notrunc status=none +elif [[ "${update_rootfs}" == "1" ]]; then + dd if="${root_image}" of="${disk_image}" bs=512 seek="${root_start}" conv=notrunc status=none +fi + +dd if="${esp_image}" of="${disk_image}" bs=512 seek="${esp_start}" conv=notrunc status=none + +if [[ ! -f "${ovmf_vars}" ]]; then + cp "${ovmf_vars_template}" "${ovmf_vars}" +fi + +extra_args=() +if [[ -n "${qemu_extra_args}" ]]; then + read -r -a extra_args <<< "${qemu_extra_args}" +fi + +debug_args=() +if [[ "${QEMU_DEBUG:-1}" != "0" ]]; then + debug_args=(-d int,cpu_reset -D "${qemu_debug_log}") + rm -f "${qemu_debug_log}" +fi + +accel_args=() +case "${qemu_accel}" in + auto) + if [[ -r /dev/kvm && -w /dev/kvm ]]; then + accel_args=(-enable-kvm -cpu host) + fi + ;; + kvm) + accel_args=(-enable-kvm -cpu host) + ;; + tcg|off|none) + ;; + *) + echo "invalid QEMU_ACCEL: ${qemu_accel}" >&2 + echo "Use QEMU_ACCEL=auto, kvm, or tcg." >&2 + exit 1 + ;; +esac + +qemu-system-x86_64 \ + -machine q35 \ + "${accel_args[@]}" \ + -m 256M \ + -drive "if=pflash,format=raw,readonly=on,file=${ovmf_code}" \ + -drive "if=pflash,format=raw,file=${ovmf_vars}" \ + -device "ich9-ahci,id=ahci0" \ + -drive "id=zeroosdisk,if=none,format=raw,file=${disk_image}" \ + -device "ide-hd,drive=zeroosdisk,bus=ahci0.0" \ + -serial "${qemu_serial}" \ + -display "${qemu_display}" \ + "${debug_args[@]}" \ + "${extra_args[@]}" +status=$? +echo "qemu exited with status ${status}" >&2 +if [[ -f "${qemu_debug_log}" ]]; then + echo "qemu debug log: ${qemu_debug_log}" >&2 +fi +exit "${status}" diff --git a/src/console.rs b/src/console.rs new file mode 100644 index 0000000..6bf350c --- /dev/null +++ b/src/console.rs @@ -0,0 +1,99 @@ +use crate::drivers::input::ps2; +use crate::io::{restore_flags, save_flags_and_disable_interrupts}; +use crate::tty::Tty; + +static mut TTY: *mut Tty = core::ptr::null_mut(); +static mut RAW_MODE: bool = false; + +pub unsafe fn init(tty: *mut Tty) { + unsafe { + TTY = tty; + } +} + +pub fn write(bytes: &[u8]) -> usize { + let rflags = save_flags_and_disable_interrupts(); + let written = write_unlocked(bytes); + restore_flags(rflags); + written +} + +pub fn write_panic(bytes: &[u8]) -> usize { + write_unlocked(bytes) +} + +pub fn set_raw_mode(enabled: bool) { + unsafe { + RAW_MODE = enabled; + } +} + +pub fn reset_terminal() { + set_raw_mode(false); + unsafe { + if !TTY.is_null() { + (*TTY).reset(); + } + } +} + +pub fn size() -> (usize, usize) { + unsafe { + if TTY.is_null() { + (80, 25) + } else { + ((*TTY).columns(), (*TTY).rows()) + } + } +} + +fn write_unlocked(bytes: &[u8]) -> usize { + unsafe { + if TTY.is_null() { + 0 + } else { + let tty = &mut *TTY; + for byte in bytes { + tty.put_char(*byte); + } + bytes.len() + } + } +} + +pub fn read(buffer: &mut [u8]) -> usize { + let mut read = 0; + while read < buffer.len() { + if let Some(ch) = ps2::read_char() { + if unsafe { RAW_MODE } { + buffer[read] = ch; + read += 1; + break; + } + match ch { + 0x08 => { + if read > 0 { + read -= 1; + } + } + b'\r' | b'\n' => { + buffer[read] = b'\n'; + read += 1; + break; + } + ch => { + buffer[read] = ch; + read += 1; + } + } + } else { + break; + } + } + + read +} + +pub fn has_input() -> bool { + ps2::has_char() +} diff --git a/src/drivers/bus/mod.rs b/src/drivers/bus/mod.rs new file mode 100644 index 0000000..7652d2c --- /dev/null +++ b/src/drivers/bus/mod.rs @@ -0,0 +1 @@ +pub mod pci; diff --git a/src/drivers/bus/pci.rs b/src/drivers/bus/pci.rs new file mode 100644 index 0000000..57881dc --- /dev/null +++ b/src/drivers/bus/pci.rs @@ -0,0 +1,73 @@ +use crate::io::{inl, outl}; + +const CONFIG_ADDRESS: u16 = 0xcf8; +const CONFIG_DATA: u16 = 0xcfc; +const INVALID_VENDOR_ID: u16 = 0xffff; + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct PciDevice { + pub bus: u8, + pub device: u8, + pub function: u8, + pub vendor_id: u16, + pub device_id: u16, + pub class_code: u8, + pub subclass: u8, + pub prog_if: u8, +} + +pub trait Visitor { + fn visit(&mut self, device: PciDevice); +} + +pub fn scan(visitor: &mut V) { + for bus in 0..=0 { + for device in 0..32 { + let header = read_config(bus, device, 0, 0x0c); + let multifunction = header & (1 << 23) != 0; + let functions = if multifunction { 8 } else { 1 }; + + for function in 0..functions { + let vendor_device = read_config(bus, device, function, 0x00); + let vendor_id = vendor_device as u16; + if vendor_id == INVALID_VENDOR_ID { + continue; + } + + let class = read_config(bus, device, function, 0x08); + visitor.visit(PciDevice { + bus, + device, + function, + vendor_id, + device_id: (vendor_device >> 16) as u16, + class_code: (class >> 24) as u8, + subclass: (class >> 16) as u8, + prog_if: (class >> 8) as u8, + }); + } + } + } +} + +pub fn read_bar(device: PciDevice, index: u8) -> u32 { + read_config( + device.bus, + device.device, + device.function, + 0x10 + (index as u8 * 4), + ) +} + +pub fn read_config(bus: u8, device: u8, function: u8, offset: u8) -> u32 { + let address = 0x8000_0000 + | ((bus as u32) << 16) + | ((device as u32) << 11) + | ((function as u32) << 8) + | ((offset as u32) & 0xfc); + unsafe { + outl(CONFIG_ADDRESS, address); + inl(CONFIG_DATA) + } +} diff --git a/src/drivers/input/mod.rs b/src/drivers/input/mod.rs new file mode 100644 index 0000000..4003e84 --- /dev/null +++ b/src/drivers/input/mod.rs @@ -0,0 +1 @@ +pub mod ps2; diff --git a/src/drivers/input/ps2.rs b/src/drivers/input/ps2.rs new file mode 100644 index 0000000..a3849c6 --- /dev/null +++ b/src/drivers/input/ps2.rs @@ -0,0 +1,328 @@ +use crate::drivers::platform::pic; +use crate::io::{disable_interrupts, enable_interrupts, inb}; + +const DATA_PORT: u16 = 0x60; +const STATUS_PORT: u16 = 0x64; +const OUTPUT_FULL: u8 = 1 << 0; +const BUFFER_SIZE: usize = 256; + +static mut KEYBOARD: Ps2Keyboard = Ps2Keyboard::new(); + +pub struct Ps2Keyboard { + buffer: [u8; BUFFER_SIZE], + read_index: usize, + write_index: usize, + shift: bool, + extended: bool, +} + +impl Ps2Keyboard { + const fn new() -> Self { + Self { + buffer: [0; BUFFER_SIZE], + read_index: 0, + write_index: 0, + shift: false, + extended: false, + } + } + + fn push(&mut self, byte: u8) { + let next = (self.write_index + 1) % BUFFER_SIZE; + if next != self.read_index { + self.buffer[self.write_index] = byte; + self.write_index = next; + } + } + + fn pop(&mut self) -> Option { + if self.read_index == self.write_index { + return None; + } + + let byte = self.buffer[self.read_index]; + self.read_index = (self.read_index + 1) % BUFFER_SIZE; + Some(byte) + } + + fn handle_scancode(&mut self, scancode: u8) { + if scancode == 0xe0 { + self.extended = true; + return; + } + + if self.extended { + self.extended = false; + if scancode & 0x80 != 0 { + return; + } + match scancode { + 0x48 => self.push_escape_sequence(b"\x1b[A"), + 0x50 => self.push_escape_sequence(b"\x1b[B"), + 0x4b => self.push_escape_sequence(b"\x1b[D"), + 0x4d => self.push_escape_sequence(b"\x1b[C"), + 0x47 => self.push_escape_sequence(b"\x1b[H"), + 0x4f => self.push_escape_sequence(b"\x1b[F"), + 0x53 => self.push_escape_sequence(b"\x1b[3~"), + _ => {} + } + return; + } + + let released = scancode & 0x80 != 0; + let code = scancode & 0x7f; + + match code { + 0x2a | 0x36 => { + self.shift = !released; + return; + } + _ if released => return, + _ => {} + } + + if let Some(ascii) = scancode_set1_to_ascii(code, self.shift) { + self.push(ascii); + } + } + + fn push_escape_sequence(&mut self, bytes: &[u8]) { + for byte in bytes { + self.push(*byte); + } + } +} + +pub unsafe fn init() { + unsafe { + drain_output_buffer(); + pic::unmask_irq(pic::KEYBOARD_IRQ); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_ps2_keyboard_interrupt() { + unsafe { + if inb(STATUS_PORT) & OUTPUT_FULL != 0 { + let scancode = inb(DATA_PORT); + (*(&raw mut KEYBOARD)).handle_scancode(scancode); + } + + pic::end_of_interrupt(pic::KEYBOARD_IRQ); + } +} + +pub fn read_char() -> Option { + disable_interrupts(); + let ch = unsafe { (*(&raw mut KEYBOARD)).pop() }; + enable_interrupts(); + ch +} + +pub fn has_char() -> bool { + disable_interrupts(); + let ready = unsafe { + let keyboard = &*(&raw const KEYBOARD); + keyboard.read_index != keyboard.write_index + }; + enable_interrupts(); + ready +} + +unsafe fn drain_output_buffer() { + unsafe { + while inb(STATUS_PORT) & OUTPUT_FULL != 0 { + let _ = inb(DATA_PORT); + } + } +} + +fn scancode_set1_to_ascii(code: u8, shift: bool) -> Option { + let byte = match code { + 0x01 => 0x1b, + 0x02 => { + if shift { + b'!' + } else { + b'1' + } + } + 0x03 => { + if shift { + b'@' + } else { + b'2' + } + } + 0x04 => { + if shift { + b'#' + } else { + b'3' + } + } + 0x05 => { + if shift { + b'$' + } else { + b'4' + } + } + 0x06 => { + if shift { + b'%' + } else { + b'5' + } + } + 0x07 => { + if shift { + b'^' + } else { + b'6' + } + } + 0x08 => { + if shift { + b'&' + } else { + b'7' + } + } + 0x09 => { + if shift { + b'*' + } else { + b'8' + } + } + 0x0a => { + if shift { + b'(' + } else { + b'9' + } + } + 0x0b => { + if shift { + b')' + } else { + b'0' + } + } + 0x0c => { + if shift { + b'_' + } else { + b'-' + } + } + 0x0d => { + if shift { + b'+' + } else { + b'=' + } + } + 0x0e => 0x08, + 0x0f => b'\t', + 0x10 => letter(b'q', shift), + 0x11 => letter(b'w', shift), + 0x12 => letter(b'e', shift), + 0x13 => letter(b'r', shift), + 0x14 => letter(b't', shift), + 0x15 => letter(b'y', shift), + 0x16 => letter(b'u', shift), + 0x17 => letter(b'i', shift), + 0x18 => letter(b'o', shift), + 0x19 => letter(b'p', shift), + 0x1a => { + if shift { + b'{' + } else { + b'[' + } + } + 0x1b => { + if shift { + b'}' + } else { + b']' + } + } + 0x1c => b'\r', + 0x1e => letter(b'a', shift), + 0x1f => letter(b's', shift), + 0x20 => letter(b'd', shift), + 0x21 => letter(b'f', shift), + 0x22 => letter(b'g', shift), + 0x23 => letter(b'h', shift), + 0x24 => letter(b'j', shift), + 0x25 => letter(b'k', shift), + 0x26 => letter(b'l', shift), + 0x27 => { + if shift { + b':' + } else { + b';' + } + } + 0x28 => { + if shift { + b'"' + } else { + b'\'' + } + } + 0x29 => { + if shift { + b'~' + } else { + b'`' + } + } + 0x2b => { + if shift { + b'|' + } else { + b'\\' + } + } + 0x2c => letter(b'z', shift), + 0x2d => letter(b'x', shift), + 0x2e => letter(b'c', shift), + 0x2f => letter(b'v', shift), + 0x30 => letter(b'b', shift), + 0x31 => letter(b'n', shift), + 0x32 => letter(b'm', shift), + 0x33 => { + if shift { + b'<' + } else { + b',' + } + } + 0x34 => { + if shift { + b'>' + } else { + b'.' + } + } + 0x35 => { + if shift { + b'?' + } else { + b'/' + } + } + 0x39 => b' ', + _ => return None, + }; + + Some(byte) +} + +const fn letter(lower: u8, shift: bool) -> u8 { + if shift { lower - 32 } else { lower } +} diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs new file mode 100644 index 0000000..15ce53d --- /dev/null +++ b/src/drivers/mod.rs @@ -0,0 +1,4 @@ +pub mod bus; +pub mod input; +pub mod platform; +pub mod storage; diff --git a/src/drivers/platform/mod.rs b/src/drivers/platform/mod.rs new file mode 100644 index 0000000..226bce6 --- /dev/null +++ b/src/drivers/platform/mod.rs @@ -0,0 +1,2 @@ +pub mod pic; +pub mod timer; diff --git a/src/drivers/platform/pic.rs b/src/drivers/platform/pic.rs new file mode 100644 index 0000000..9e5102e --- /dev/null +++ b/src/drivers/platform/pic.rs @@ -0,0 +1,79 @@ +use crate::io::{disable_interrupts, inb, outb}; + +const PIC1_COMMAND: u16 = 0x20; +const PIC1_DATA: u16 = 0x21; +const PIC2_COMMAND: u16 = 0xa0; +const PIC2_DATA: u16 = 0xa1; + +const ICW1_INIT: u8 = 0x10; +const ICW1_ICW4: u8 = 0x01; +const ICW4_8086: u8 = 0x01; +const PIC_EOI: u8 = 0x20; + +pub const PIC1_OFFSET: u8 = 0x20; +pub const PIC2_OFFSET: u8 = 0x28; +pub const TIMER_IRQ: u8 = 0; +pub const KEYBOARD_IRQ: u8 = 1; + +pub unsafe fn remap_and_mask_all() { + unsafe { + disable_interrupts(); + + let mask1 = inb(PIC1_DATA); + let mask2 = inb(PIC2_DATA); + outb(PIC1_DATA, 0xff); + outb(PIC2_DATA, 0xff); + io_wait(); + + outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + + outb(PIC1_DATA, PIC1_OFFSET); + io_wait(); + outb(PIC2_DATA, PIC2_OFFSET); + io_wait(); + + outb(PIC1_DATA, 4); + io_wait(); + outb(PIC2_DATA, 2); + io_wait(); + + outb(PIC1_DATA, ICW4_8086); + io_wait(); + outb(PIC2_DATA, ICW4_8086); + io_wait(); + + outb(PIC1_DATA, mask1 | 0xff); + outb(PIC2_DATA, mask2 | 0xff); + } +} + +pub unsafe fn unmask_irq(irq: u8) { + let (port, bit) = if irq < 8 { + (PIC1_DATA, irq) + } else { + (PIC2_DATA, irq - 8) + }; + + unsafe { + let mask = inb(port) & !(1 << bit); + outb(port, mask); + } +} + +pub unsafe fn end_of_interrupt(irq: u8) { + unsafe { + if irq >= 8 { + outb(PIC2_COMMAND, PIC_EOI); + } + outb(PIC1_COMMAND, PIC_EOI); + } +} + +unsafe fn io_wait() { + unsafe { + core::arch::asm!("pause", options(nomem, nostack, preserves_flags)); + } +} diff --git a/src/drivers/platform/timer.rs b/src/drivers/platform/timer.rs new file mode 100644 index 0000000..1aab59e --- /dev/null +++ b/src/drivers/platform/timer.rs @@ -0,0 +1,46 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +use crate::{drivers::platform::pic, io::outb}; + +const PIT_CHANNEL0: u16 = 0x40; +const PIT_COMMAND: u16 = 0x43; +const PIT_FREQUENCY: u32 = 1_193_182; +pub const HZ: u64 = 100; + +static TICKS: AtomicU64 = AtomicU64::new(0); +static WALL_CLOCK_BOOT_SEC: AtomicU64 = AtomicU64::new(0); + +pub unsafe fn init() { + let divisor = (PIT_FREQUENCY / HZ as u32) as u16; + unsafe { + outb(PIT_COMMAND, 0x36); + outb(PIT_CHANNEL0, divisor as u8); + outb(PIT_CHANNEL0, (divisor >> 8) as u8); + pic::unmask_irq(pic::TIMER_IRQ); + } +} + +pub fn tick() { + TICKS.fetch_add(1, Ordering::Relaxed); +} + +pub fn ticks() -> u64 { + TICKS.load(Ordering::Relaxed) +} + +pub fn monotonic_time() -> (u64, u64) { + let ticks = ticks(); + let sec = ticks / HZ; + let nsec = (ticks % HZ) * (1_000_000_000 / HZ); + (sec, nsec) +} + +pub fn set_wall_clock_boot_time(unix_seconds: u64) { + WALL_CLOCK_BOOT_SEC.store(unix_seconds, Ordering::Relaxed); +} + +pub fn realtime() -> (u64, u64) { + let (uptime_sec, nsec) = monotonic_time(); + let boot_sec = WALL_CLOCK_BOOT_SEC.load(Ordering::Relaxed); + (boot_sec.saturating_add(uptime_sec), nsec) +} diff --git a/src/drivers/storage/ahci.rs b/src/drivers/storage/ahci.rs new file mode 100644 index 0000000..b56104e --- /dev/null +++ b/src/drivers/storage/ahci.rs @@ -0,0 +1,493 @@ +use crate::drivers::{ + bus::pci::{self, PciDevice, Visitor}, + storage::block::{BlockDevice, BlockError}, +}; +use crate::memory::PhysicalMemoryManager; +use core::ptr::{addr_of, addr_of_mut}; + +const AHCI_CLASS_MASS_STORAGE: u8 = 0x01; +const AHCI_SUBCLASS_SATA: u8 = 0x06; +const AHCI_PROG_IF: u8 = 0x01; +const AHCI_BAR_INDEX: u8 = 5; +const MAX_AHCI_CONTROLLERS: usize = 4; +const MAX_AHCI_DISKS: usize = 8; + +const SATA_SIG_ATA: u32 = 0x0000_0101; +const SATA_SIG_ATAPI: u32 = 0xeb14_0101; +const HBA_PORT_DET_PRESENT: u32 = 3; +const HBA_PORT_IPM_ACTIVE: u32 = 1; +const HBA_PX_CMD_ST: u32 = 1 << 0; +const HBA_PX_CMD_FRE: u32 = 1 << 4; +const HBA_PX_CMD_FR: u32 = 1 << 14; +const HBA_PX_CMD_CR: u32 = 1 << 15; +const HBA_PX_IS_TFES: u32 = 1 << 30; +const ATA_CMD_READ_DMA_EXT: u8 = 0x25; +const ATA_CMD_WRITE_DMA_EXT: u8 = 0x35; +const FIS_TYPE_REG_H2D: u8 = 0x27; + +#[repr(C)] +pub struct HbaMemory { + cap: u32, + ghc: u32, + is: u32, + pi: u32, + vs: u32, + ccc_ctl: u32, + ccc_pts: u32, + em_loc: u32, + em_ctl: u32, + cap2: u32, + bohc: u32, + reserved: [u8; 0xa0 - 0x2c], + vendor: [u8; 0x100 - 0xa0], + ports: [HbaPort; 32], +} + +#[repr(C)] +pub struct HbaPort { + clb: u32, + clbu: u32, + fb: u32, + fbu: u32, + is: u32, + ie: u32, + cmd: u32, + reserved0: u32, + tfd: u32, + sig: u32, + ssts: u32, + sctl: u32, + serr: u32, + sact: u32, + ci: u32, + sntf: u32, + fbs: u32, + reserved1: [u32; 11], + vendor: [u32; 4], +} + +#[repr(C)] +struct HbaCommandHeader { + flags: u16, + prdtl: u16, + prdbc: u32, + ctba: u32, + ctbau: u32, + reserved: [u32; 4], +} + +#[repr(C)] +struct HbaPrdtEntry { + dba: u32, + dbau: u32, + reserved: u32, + dbc_i: u32, +} + +#[repr(C)] +struct HbaCommandTable { + cfis: [u8; 64], + acmd: [u8; 16], + reserved: [u8; 48], + prdt: [HbaPrdtEntry; 1], +} + +#[repr(C, align(4096))] +struct AlignedPage([u8; 4096]); + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct AhciController { + pub pci: PciDevice, + pub abar: u64, + pub ports_implemented: u32, +} + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct AhciDisk { + slot: usize, + controller: usize, + port: usize, + signature: u32, +} + +impl AhciDisk { + pub const fn empty() -> Self { + Self { + slot: usize::MAX, + controller: usize::MAX, + port: usize::MAX, + signature: 0, + } + } + + #[allow(dead_code)] + pub fn is_present(self) -> bool { + self.controller != usize::MAX + } + + #[allow(dead_code)] + pub fn port(self) -> usize { + self.port + } + + #[allow(dead_code)] + pub fn is_ata(self) -> bool { + self.signature == SATA_SIG_ATA + } +} + +impl BlockDevice for AhciDisk { + fn read_sector(&mut self, lba: u64, buffer: &mut [u8]) -> Result<(), BlockError> { + if buffer.len() < 512 || self.slot >= MAX_AHCI_DISKS || !self.is_ata() { + return Err(BlockError::Invalid); + } + + unsafe { + let controller = CONTROLLERS[self.controller]; + let hba = controller.abar as *mut HbaMemory; + let port = addr_of_mut!((*hba).ports[self.port]); + read_dma_ext(self.slot, port, lba)?; + let read_buffer = READ_BUFFERS[self.slot].0.as_ptr(); + core::ptr::copy_nonoverlapping(read_buffer, buffer.as_mut_ptr(), 512); + } + Ok(()) + } + + fn write_sector(&mut self, lba: u64, buffer: &[u8]) -> Result<(), BlockError> { + if buffer.len() < 512 || self.slot >= MAX_AHCI_DISKS || !self.is_ata() { + return Err(BlockError::Invalid); + } + + unsafe { + core::ptr::copy_nonoverlapping( + buffer.as_ptr(), + READ_BUFFERS[self.slot].0.as_mut_ptr(), + 512, + ); + let controller = CONTROLLERS[self.controller]; + let hba = controller.abar as *mut HbaMemory; + let port = addr_of_mut!((*hba).ports[self.port]); + write_dma_ext(self.slot, port, lba)?; + } + Ok(()) + } +} + +struct AhciProbe { + controllers: [AhciController; MAX_AHCI_CONTROLLERS], + controller_count: usize, + disks: [AhciDisk; MAX_AHCI_DISKS], + disk_count: usize, +} + +impl AhciProbe { + const fn new() -> Self { + Self { + controllers: [AhciController { + pci: PciDevice { + bus: 0, + device: 0, + function: 0, + vendor_id: 0, + device_id: 0, + class_code: 0, + subclass: 0, + prog_if: 0, + }, + abar: 0, + ports_implemented: 0, + }; MAX_AHCI_CONTROLLERS], + controller_count: 0, + disks: [AhciDisk::empty(); MAX_AHCI_DISKS], + disk_count: 0, + } + } +} + +impl Visitor for AhciProbe { + fn visit(&mut self, device: PciDevice) { + if device.class_code != AHCI_CLASS_MASS_STORAGE + || device.subclass != AHCI_SUBCLASS_SATA + || device.prog_if != AHCI_PROG_IF + || self.controller_count >= MAX_AHCI_CONTROLLERS + { + return; + } + + let bar5 = pci::read_bar(device, AHCI_BAR_INDEX); + let abar = (bar5 & 0xffff_fff0) as u64; + if abar == 0 { + return; + } + + let hba = abar as *mut HbaMemory; + let pi = unsafe { core::ptr::read_volatile(&(*hba).pi) }; + let controller_index = self.controller_count; + self.controllers[controller_index] = AhciController { + pci: device, + abar, + ports_implemented: pi, + }; + self.controller_count += 1; + + for port in 0..32 { + if pi & (1 << port) == 0 || self.disk_count >= MAX_AHCI_DISKS { + continue; + } + + let port_ref = unsafe { addr_of!((*hba).ports[port]) }; + if !port_has_device(port_ref) { + continue; + } + + let signature = unsafe { core::ptr::read_volatile(addr_of!((*port_ref).sig)) }; + if signature != SATA_SIG_ATA && signature != SATA_SIG_ATAPI { + continue; + } + + self.disks[self.disk_count] = AhciDisk { + slot: self.disk_count, + controller: controller_index, + port, + signature, + }; + self.disk_count += 1; + } + } +} + +static mut CONTROLLERS: [AhciController; MAX_AHCI_CONTROLLERS] = [AhciController { + pci: PciDevice { + bus: 0, + device: 0, + function: 0, + vendor_id: 0, + device_id: 0, + class_code: 0, + subclass: 0, + prog_if: 0, + }, + abar: 0, + ports_implemented: 0, +}; MAX_AHCI_CONTROLLERS]; +static mut CONTROLLER_COUNT: usize = 0; +static mut DISKS: [AhciDisk; MAX_AHCI_DISKS] = [AhciDisk::empty(); MAX_AHCI_DISKS]; +static mut DISK_COUNT: usize = 0; +static mut COMMAND_LISTS: [AlignedPage; MAX_AHCI_DISKS] = + [const { AlignedPage([0; 4096]) }; MAX_AHCI_DISKS]; +static mut RECEIVED_FIS: [AlignedPage; MAX_AHCI_DISKS] = + [const { AlignedPage([0; 4096]) }; MAX_AHCI_DISKS]; +static mut COMMAND_TABLES: [AlignedPage; MAX_AHCI_DISKS] = + [const { AlignedPage([0; 4096]) }; MAX_AHCI_DISKS]; +static mut READ_BUFFERS: [AlignedPage; MAX_AHCI_DISKS] = + [const { AlignedPage([0; 4096]) }; MAX_AHCI_DISKS]; + +pub unsafe fn init(_memory: &mut PhysicalMemoryManager) -> usize { + let mut probe = AhciProbe::new(); + pci::scan(&mut probe); + + unsafe { + CONTROLLER_COUNT = probe.controller_count; + DISK_COUNT = probe.disk_count; + for index in 0..probe.controller_count { + CONTROLLERS[index] = probe.controllers[index]; + } + for index in 0..probe.disk_count { + DISKS[index] = probe.disks[index]; + configure_disk(index); + } + DISK_COUNT + } +} + +#[allow(dead_code)] +pub fn disk_count() -> usize { + unsafe { DISK_COUNT } +} + +pub fn first_disk() -> Option { + unsafe { + if DISK_COUNT == 0 { + None + } else { + Some(DISKS[0]) + } + } +} + +fn port_has_device(port: *const HbaPort) -> bool { + let ssts = unsafe { core::ptr::read_volatile(addr_of!((*port).ssts)) }; + let det = ssts & 0x0f; + let ipm = (ssts >> 8) & 0x0f; + det == HBA_PORT_DET_PRESENT && ipm == HBA_PORT_IPM_ACTIVE +} + +unsafe fn configure_disk(index: usize) { + unsafe { + let disk = DISKS[index]; + let controller = CONTROLLERS[disk.controller]; + let hba = controller.abar as *mut HbaMemory; + let port = addr_of_mut!((*hba).ports[disk.port]); + + stop_command_engine(port); + core::ptr::write_bytes(COMMAND_LISTS[index].0.as_mut_ptr(), 0, 4096); + core::ptr::write_bytes(RECEIVED_FIS[index].0.as_mut_ptr(), 0, 4096); + core::ptr::write_bytes(COMMAND_TABLES[index].0.as_mut_ptr(), 0, 4096); + core::ptr::write_volatile( + addr_of_mut!((*port).clb), + COMMAND_LISTS[index].0.as_ptr() as u32, + ); + core::ptr::write_volatile(addr_of_mut!((*port).clbu), 0); + core::ptr::write_volatile( + addr_of_mut!((*port).fb), + RECEIVED_FIS[index].0.as_ptr() as u32, + ); + core::ptr::write_volatile(addr_of_mut!((*port).fbu), 0); + core::ptr::write_volatile(addr_of_mut!((*port).serr), u32::MAX); + core::ptr::write_volatile(addr_of_mut!((*port).is), u32::MAX); + start_command_engine(port); + } +} + +unsafe fn read_dma_ext(slot: usize, port: *mut HbaPort, lba: u64) -> Result<(), BlockError> { + unsafe { + while core::ptr::read_volatile(addr_of!((*port).tfd)) & 0x88 != 0 {} + + let command_list = COMMAND_LISTS[slot].0.as_mut_ptr() as *mut HbaCommandHeader; + let command_table = COMMAND_TABLES[slot].0.as_mut_ptr() as *mut HbaCommandTable; + core::ptr::write_bytes( + command_table.cast::(), + 0, + core::mem::size_of::(), + ); + + (*command_list).flags = 5; + (*command_list).prdtl = 1; + (*command_list).prdbc = 0; + (*command_list).ctba = command_table as u32; + (*command_list).ctbau = 0; + + (*command_table).prdt[0].dba = READ_BUFFERS[slot].0.as_mut_ptr() as u32; + (*command_table).prdt[0].dbau = 0; + (*command_table).prdt[0].reserved = 0; + (*command_table).prdt[0].dbc_i = (512 - 1) | (1 << 31); + + let cfis = &mut (*command_table).cfis; + cfis[0] = FIS_TYPE_REG_H2D; + cfis[1] = 1 << 7; + cfis[2] = ATA_CMD_READ_DMA_EXT; + cfis[4] = lba as u8; + cfis[5] = (lba >> 8) as u8; + cfis[6] = (lba >> 16) as u8; + cfis[7] = 1 << 6; + cfis[8] = (lba >> 24) as u8; + cfis[9] = (lba >> 32) as u8; + cfis[10] = (lba >> 40) as u8; + cfis[12] = 1; + cfis[13] = 0; + + core::ptr::write_volatile(addr_of_mut!((*port).is), u32::MAX); + core::ptr::write_volatile(addr_of_mut!((*port).ci), 1); + + let mut timeout = 10_000_000usize; + while core::ptr::read_volatile(addr_of!((*port).ci)) & 1 != 0 { + if core::ptr::read_volatile(addr_of!((*port).is)) & HBA_PX_IS_TFES != 0 { + return Err(BlockError::Io); + } + timeout = timeout.saturating_sub(1); + if timeout == 0 { + return Err(BlockError::Io); + } + } + + if core::ptr::read_volatile(addr_of!((*port).is)) & HBA_PX_IS_TFES != 0 { + Err(BlockError::Io) + } else { + Ok(()) + } + } +} + +unsafe fn write_dma_ext(slot: usize, port: *mut HbaPort, lba: u64) -> Result<(), BlockError> { + unsafe { + while core::ptr::read_volatile(addr_of!((*port).tfd)) & 0x88 != 0 {} + + let command_list = COMMAND_LISTS[slot].0.as_mut_ptr() as *mut HbaCommandHeader; + let command_table = COMMAND_TABLES[slot].0.as_mut_ptr() as *mut HbaCommandTable; + core::ptr::write_bytes( + command_table.cast::(), + 0, + core::mem::size_of::(), + ); + + (*command_list).flags = 5 | (1 << 6); + (*command_list).prdtl = 1; + (*command_list).prdbc = 0; + (*command_list).ctba = command_table as u32; + (*command_list).ctbau = 0; + + (*command_table).prdt[0].dba = READ_BUFFERS[slot].0.as_mut_ptr() as u32; + (*command_table).prdt[0].dbau = 0; + (*command_table).prdt[0].reserved = 0; + (*command_table).prdt[0].dbc_i = (512 - 1) | (1 << 31); + + let cfis = &mut (*command_table).cfis; + cfis[0] = FIS_TYPE_REG_H2D; + cfis[1] = 1 << 7; + cfis[2] = ATA_CMD_WRITE_DMA_EXT; + cfis[4] = lba as u8; + cfis[5] = (lba >> 8) as u8; + cfis[6] = (lba >> 16) as u8; + cfis[7] = 1 << 6; + cfis[8] = (lba >> 24) as u8; + cfis[9] = (lba >> 32) as u8; + cfis[10] = (lba >> 40) as u8; + cfis[12] = 1; + cfis[13] = 0; + + core::ptr::write_volatile(addr_of_mut!((*port).is), u32::MAX); + core::ptr::write_volatile(addr_of_mut!((*port).ci), 1); + + let mut timeout = 10_000_000usize; + while core::ptr::read_volatile(addr_of!((*port).ci)) & 1 != 0 { + if core::ptr::read_volatile(addr_of!((*port).is)) & HBA_PX_IS_TFES != 0 { + return Err(BlockError::Io); + } + timeout = timeout.saturating_sub(1); + if timeout == 0 { + return Err(BlockError::Io); + } + } + + if core::ptr::read_volatile(addr_of!((*port).is)) & HBA_PX_IS_TFES != 0 { + Err(BlockError::Io) + } else { + Ok(()) + } + } +} + +unsafe fn stop_command_engine(port: *mut HbaPort) { + unsafe { + let mut cmd = core::ptr::read_volatile(addr_of!((*port).cmd)); + cmd &= !HBA_PX_CMD_ST; + core::ptr::write_volatile(addr_of_mut!((*port).cmd), cmd); + while core::ptr::read_volatile(addr_of!((*port).cmd)) & HBA_PX_CMD_CR != 0 {} + cmd = core::ptr::read_volatile(addr_of!((*port).cmd)); + cmd &= !HBA_PX_CMD_FRE; + core::ptr::write_volatile(addr_of_mut!((*port).cmd), cmd); + while core::ptr::read_volatile(addr_of!((*port).cmd)) & HBA_PX_CMD_FR != 0 {} + } +} + +unsafe fn start_command_engine(port: *mut HbaPort) { + unsafe { + let mut cmd = core::ptr::read_volatile(addr_of!((*port).cmd)); + cmd |= HBA_PX_CMD_FRE; + core::ptr::write_volatile(addr_of_mut!((*port).cmd), cmd); + cmd |= HBA_PX_CMD_ST; + core::ptr::write_volatile(addr_of_mut!((*port).cmd), cmd); + } +} diff --git a/src/drivers/storage/block.rs b/src/drivers/storage/block.rs new file mode 100644 index 0000000..a7d126a --- /dev/null +++ b/src/drivers/storage/block.rs @@ -0,0 +1,67 @@ +#[derive(Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum BlockError { + NoDevice, + Io, + Unsupported, + Invalid, +} + +pub trait BlockDevice { + fn sector_size(&self) -> usize { + 512 + } + + fn read_sector(&mut self, lba: u64, buffer: &mut [u8]) -> Result<(), BlockError>; + + fn write_sector(&mut self, _lba: u64, _buffer: &[u8]) -> Result<(), BlockError> { + Err(BlockError::Unsupported) + } + + fn read_at(&mut self, offset: u64, buffer: &mut [u8]) -> Result<(), BlockError> { + let sector_size = self.sector_size(); + if sector_size == 0 { + return Err(BlockError::Invalid); + } + + let mut done = 0; + let mut scratch = [0u8; 512]; + while done < buffer.len() { + let absolute = offset + done as u64; + let lba = absolute / sector_size as u64; + let sector_offset = (absolute % sector_size as u64) as usize; + self.read_sector(lba, &mut scratch)?; + + let chunk = (sector_size - sector_offset).min(buffer.len() - done); + buffer[done..done + chunk] + .copy_from_slice(&scratch[sector_offset..sector_offset + chunk]); + done += chunk; + } + + Ok(()) + } + + fn write_at(&mut self, offset: u64, buffer: &[u8]) -> Result<(), BlockError> { + let sector_size = self.sector_size(); + if sector_size == 0 { + return Err(BlockError::Invalid); + } + + let mut done = 0; + let mut scratch = [0u8; 512]; + while done < buffer.len() { + let absolute = offset + done as u64; + let lba = absolute / sector_size as u64; + let sector_offset = (absolute % sector_size as u64) as usize; + self.read_sector(lba, &mut scratch)?; + + let chunk = (sector_size - sector_offset).min(buffer.len() - done); + scratch[sector_offset..sector_offset + chunk] + .copy_from_slice(&buffer[done..done + chunk]); + self.write_sector(lba, &scratch)?; + done += chunk; + } + + Ok(()) + } +} diff --git a/src/drivers/storage/mod.rs b/src/drivers/storage/mod.rs new file mode 100644 index 0000000..ee5a664 --- /dev/null +++ b/src/drivers/storage/mod.rs @@ -0,0 +1,3 @@ +pub mod ahci; +pub mod block; +pub mod partition; diff --git a/src/drivers/storage/partition.rs b/src/drivers/storage/partition.rs new file mode 100644 index 0000000..82edbfc --- /dev/null +++ b/src/drivers/storage/partition.rs @@ -0,0 +1,102 @@ +use crate::drivers::storage::block::{BlockDevice, BlockError}; + +const GPT_HEADER_LBA: u64 = 1; +const GPT_SIGNATURE: &[u8; 8] = b"EFI PART"; +const GPT_ENTRY_SIZE_MIN: usize = 128; +const LINUX_FILESYSTEM_GUID: [u8; 16] = [ + 0xaf, 0x3d, 0xc6, 0x0f, 0x83, 0x84, 0x72, 0x47, 0x8e, 0x79, 0x3d, 0x69, 0xd8, 0x47, 0x7d, 0xe4, +]; + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct Partition { + pub first_lba: u64, + pub last_lba: u64, +} + +#[derive(Clone, Copy)] +pub struct PartitionBlockDevice { + inner: D, + first_lba: u64, +} + +impl PartitionBlockDevice { + pub const fn new(inner: D, partition: Partition) -> Self { + Self { + inner, + first_lba: partition.first_lba, + } + } +} + +impl BlockDevice for PartitionBlockDevice { + fn sector_size(&self) -> usize { + self.inner.sector_size() + } + + fn read_sector(&mut self, lba: u64, buffer: &mut [u8]) -> Result<(), BlockError> { + self.inner.read_sector(self.first_lba + lba, buffer) + } + + fn write_sector(&mut self, lba: u64, buffer: &[u8]) -> Result<(), BlockError> { + self.inner.write_sector(self.first_lba + lba, buffer) + } +} + +pub fn find_linux_partition(device: &mut D) -> Result { + let mut sector = [0u8; 512]; + device.read_sector(GPT_HEADER_LBA, &mut sector)?; + if §or[0..8] != GPT_SIGNATURE { + return Err(BlockError::Unsupported); + } + + let entries_lba = le_u64(§or, 0x48); + let entry_count = le_u32(§or, 0x50) as usize; + let entry_size = le_u32(§or, 0x54) as usize; + if entry_size < GPT_ENTRY_SIZE_MIN || entry_size > 512 { + return Err(BlockError::Unsupported); + } + + let entries_per_sector = 512 / entry_size; + for index in 0..entry_count { + let sector_lba = entries_lba + (index / entries_per_sector) as u64; + let sector_offset = (index % entries_per_sector) * entry_size; + device.read_sector(sector_lba, &mut sector)?; + + let entry = §or[sector_offset..sector_offset + entry_size]; + if entry[0..16] == LINUX_FILESYSTEM_GUID { + let first_lba = le_u64(entry, 0x20); + let last_lba = le_u64(entry, 0x28); + if first_lba != 0 && last_lba >= first_lba { + return Ok(Partition { + first_lba, + last_lba, + }); + } + } + } + + Err(BlockError::NoDevice) +} + +fn le_u32(buffer: &[u8], offset: usize) -> u32 { + u32::from_le_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + ]) +} + +fn le_u64(buffer: &[u8], offset: usize) -> u64 { + u64::from_le_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + buffer[offset + 4], + buffer[offset + 5], + buffer[offset + 6], + buffer[offset + 7], + ]) +} diff --git a/src/efi.rs b/src/efi.rs new file mode 100644 index 0000000..9a34ac1 --- /dev/null +++ b/src/efi.rs @@ -0,0 +1,402 @@ +pub type EfiHandle = *mut core::ffi::c_void; +pub type EfiEvent = *mut core::ffi::c_void; +pub type EfiStatus = usize; + +pub const EFI_SUCCESS: EfiStatus = 0; +pub const EFI_NOT_FOUND: EfiStatus = 14; + +const EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID: EfiGuid = EfiGuid::new( + 0x9042a9de, + 0x23dc, + 0x4a38, + [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a], +); + +pub const EFI_LOADED_IMAGE_PROTOCOL_GUID: EfiGuid = EfiGuid::new( + 0x5b1b31a1, + 0x9562, + 0x11d2, + [0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], +); + +pub const EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID: EfiGuid = EfiGuid::new( + 0x0964e5b22, + 0x6459, + 0x11d2, + [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], +); + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EfiGuid { + data1: u32, + data2: u16, + data3: u16, + data4: [u8; 8], +} + +impl EfiGuid { + pub const fn new(data1: u32, data2: u16, data3: u16, data4: [u8; 8]) -> Self { + Self { + data1, + data2, + data3, + data4, + } + } +} + +#[repr(C)] +pub struct EfiTableHeader { + signature: u64, + revision: u32, + header_size: u32, + crc32: u32, + reserved: u32, +} + +#[repr(C)] +pub struct EfiSystemTable { + hdr: EfiTableHeader, + firmware_vendor: *mut u16, + firmware_revision: u32, + console_in_handle: EfiHandle, + pub con_in: *mut EfiSimpleTextInputProtocol, + console_out_handle: EfiHandle, + con_out: *mut core::ffi::c_void, + standard_error_handle: EfiHandle, + std_err: *mut core::ffi::c_void, + pub runtime_services: *mut EfiRuntimeServices, + pub boot_services: *mut EfiBootServices, + number_of_table_entries: usize, + configuration_table: *mut core::ffi::c_void, +} + +#[repr(C)] +pub struct EfiInputKey { + pub scan_code: u16, + pub unicode_char: u16, +} + +#[repr(C)] +pub struct EfiSimpleTextInputProtocol { + reset: usize, + pub read_key_stroke: extern "efiapi" fn( + this: *mut EfiSimpleTextInputProtocol, + key: *mut EfiInputKey, + ) -> EfiStatus, + pub wait_for_key: EfiEvent, +} + +#[repr(C)] +pub struct EfiRuntimeServices { + hdr: EfiTableHeader, + pub get_time: + extern "efiapi" fn(time: *mut EfiTime, capabilities: *mut EfiTimeCapabilities) -> EfiStatus, + set_time: usize, + get_wakeup_time: usize, + set_wakeup_time: usize, + set_virtual_address_map: usize, + convert_pointer: usize, + get_variable: usize, + get_next_variable_name: usize, + set_variable: usize, + get_next_high_mono_count: usize, + reset_system: usize, + update_capsule: usize, + query_capsule_capabilities: usize, + query_variable_info: usize, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EfiTime { + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, + pub pad1: u8, + pub nanosecond: u32, + pub timezone: i16, + pub daylight: u8, + pub pad2: u8, +} + +#[repr(C)] +pub struct EfiTimeCapabilities { + resolution: u32, + accuracy: u32, + sets_to_zero: bool, +} + +#[repr(C)] +pub struct EfiBootServices { + hdr: EfiTableHeader, + raise_tpl: usize, + restore_tpl: usize, + allocate_pages: usize, + free_pages: usize, + pub get_memory_map: extern "efiapi" fn( + memory_map_size: *mut usize, + memory_map: *mut EfiMemoryDescriptor, + map_key: *mut usize, + descriptor_size: *mut usize, + descriptor_version: *mut u32, + ) -> EfiStatus, + allocate_pool: usize, + free_pool: usize, + create_event: usize, + set_timer: usize, + pub wait_for_event: extern "efiapi" fn( + number_of_events: usize, + event: *mut EfiEvent, + index: *mut usize, + ) -> EfiStatus, + signal_event: usize, + close_event: usize, + check_event: usize, + install_protocol_interface: usize, + reinstall_protocol_interface: usize, + uninstall_protocol_interface: usize, + pub handle_protocol: extern "efiapi" fn( + handle: EfiHandle, + protocol: *const EfiGuid, + interface: *mut *mut core::ffi::c_void, + ) -> EfiStatus, + reserved: usize, + register_protocol_notify: usize, + locate_handle: usize, + locate_device_path: usize, + install_configuration_table: usize, + load_image: usize, + start_image: usize, + exit: usize, + unload_image: usize, + pub exit_boot_services: + extern "efiapi" fn(image_handle: EfiHandle, map_key: usize) -> EfiStatus, + get_next_monotonic_count: usize, + stall: usize, + set_watchdog_timer: usize, + connect_controller: usize, + disconnect_controller: usize, + open_protocol: usize, + close_protocol: usize, + open_protocol_information: usize, + protocols_per_handle: usize, + locate_handle_buffer: usize, + pub locate_protocol: extern "efiapi" fn( + protocol: *const EfiGuid, + registration: *mut core::ffi::c_void, + interface: *mut *mut core::ffi::c_void, + ) -> EfiStatus, +} + +#[repr(C)] +pub struct EfiLoadedImageProtocol { + revision: u32, + parent_handle: EfiHandle, + system_table: *mut EfiSystemTable, + pub device_handle: EfiHandle, + file_path: *mut core::ffi::c_void, + reserved: *mut core::ffi::c_void, + load_options_size: u32, + load_options: *mut core::ffi::c_void, + image_base: *mut core::ffi::c_void, + image_size: u64, + image_code_type: u32, + image_data_type: u32, + unload: usize, +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct EfiMemoryDescriptor { + pub ty: u32, + pub physical_start: u64, + pub virtual_start: u64, + pub number_of_pages: u64, + pub attribute: u64, +} + +pub const EFI_CONVENTIONAL_MEMORY: u32 = 7; + +#[derive(Clone, Copy)] +pub struct MemoryMap { + pub ptr: *const EfiMemoryDescriptor, + pub byte_len: usize, + pub key: usize, + pub descriptor_size: usize, +} + +#[repr(C)] +pub struct EfiGraphicsOutputProtocol { + query_mode: usize, + set_mode: usize, + blt: usize, + pub mode: *mut EfiGraphicsOutputProtocolMode, +} + +#[repr(C)] +pub struct EfiGraphicsOutputProtocolMode { + max_mode: u32, + mode: u32, + pub info: *mut EfiGraphicsOutputModeInformation, + size_of_info: usize, + pub frame_buffer_base: u64, + frame_buffer_size: usize, +} + +#[repr(C)] +pub struct EfiGraphicsOutputModeInformation { + version: u32, + pub horizontal_resolution: u32, + pub vertical_resolution: u32, + pub pixel_format: EfiGraphicsPixelFormat, + pixel_information: EfiPixelBitmask, + pub pixels_per_scan_line: u32, +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum EfiGraphicsPixelFormat { + RedGreenBlueReserved8BitPerColor = 0, + BlueGreenRedReserved8BitPerColor = 1, + BitMask = 2, + BltOnly = 3, + FormatMax = 4, +} + +#[repr(C)] +struct EfiPixelBitmask { + red_mask: u32, + green_mask: u32, + blue_mask: u32, + reserved_mask: u32, +} + +pub unsafe fn boot_services(system_table: *mut EfiSystemTable) -> Option<*mut EfiBootServices> { + if system_table.is_null() || unsafe { (*system_table).boot_services.is_null() } { + None + } else { + Some(unsafe { (*system_table).boot_services }) + } +} + +pub unsafe fn read_unix_time(system_table: *mut EfiSystemTable) -> Option { + if system_table.is_null() || unsafe { (*system_table).runtime_services.is_null() } { + return None; + } + + let mut time = EfiTime { + year: 0, + month: 0, + day: 0, + hour: 0, + minute: 0, + second: 0, + pad1: 0, + nanosecond: 0, + timezone: 0, + daylight: 0, + pad2: 0, + }; + let status = + unsafe { ((*(*system_table).runtime_services).get_time)(&mut time, core::ptr::null_mut()) }; + if status != EFI_SUCCESS { + return None; + } + efi_time_to_unix_seconds(time) +} + +fn efi_time_to_unix_seconds(time: EfiTime) -> Option { + if time.year < 1970 || time.month == 0 || time.month > 12 || time.day == 0 || time.day > 31 { + return None; + } + if time.hour > 23 || time.minute > 59 || time.second > 59 { + return None; + } + + let mut days = 0u64; + let mut year = 1970u16; + while year < time.year { + days += if is_leap_year(year) { 366 } else { 365 }; + year += 1; + } + + let mut month = 1u8; + while month < time.month { + days += days_in_month(time.year, month) as u64; + month += 1; + } + + days += (time.day - 1) as u64; + Some(days * 86_400 + time.hour as u64 * 3_600 + time.minute as u64 * 60 + time.second as u64) +} + +fn is_leap_year(year: u16) -> bool { + (year.is_multiple_of(4) && !year.is_multiple_of(100)) || year.is_multiple_of(400) +} + +fn days_in_month(year: u16, month: u8) -> u8 { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 if is_leap_year(year) => 29, + 2 => 28, + _ => 0, + } +} + +pub unsafe fn locate_gop( + boot_services: *mut EfiBootServices, +) -> Option<*mut EfiGraphicsOutputProtocol> { + let mut gop = core::ptr::null_mut(); + let status = unsafe { + ((*boot_services).locate_protocol)( + &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, + core::ptr::null_mut(), + &mut gop, + ) + }; + + if status != EFI_SUCCESS || gop.is_null() { + None + } else { + Some(gop as *mut EfiGraphicsOutputProtocol) + } +} + +pub unsafe fn get_memory_map( + boot_services: *mut EfiBootServices, + buffer: *mut u8, + buffer_len: usize, +) -> Option { + let mut memory_map_size = buffer_len; + let mut map_key = 0; + let mut descriptor_size = 0; + let mut descriptor_version = 0; + + let status = unsafe { + ((*boot_services).get_memory_map)( + &mut memory_map_size, + buffer as *mut EfiMemoryDescriptor, + &mut map_key, + &mut descriptor_size, + &mut descriptor_version, + ) + }; + + if status != EFI_SUCCESS || descriptor_size < core::mem::size_of::() { + None + } else { + Some(MemoryMap { + ptr: buffer as *const EfiMemoryDescriptor, + byte_len: memory_map_size, + key: map_key, + descriptor_size, + }) + } +} diff --git a/src/elf.rs b/src/elf.rs new file mode 100644 index 0000000..56d3da7 --- /dev/null +++ b/src/elf.rs @@ -0,0 +1,259 @@ +use core::ptr::{copy_nonoverlapping, write_bytes}; + +use crate::fs::uefi::LoadedFile; +use crate::memory::PhysicalMemoryManager; + +const EI_CLASS: usize = 4; +const EI_DATA: usize = 5; +const ELFCLASS64: u8 = 2; +const ELFDATA2LSB: u8 = 1; +const ET_EXEC: u16 = 2; +const ET_DYN: u16 = 3; +const EM_X86_64: u16 = 62; +const PT_LOAD: u32 = 1; +const SHT_RELA: u32 = 4; +const R_X86_64_RELATIVE: u32 = 8; +const R_X86_64_IRELATIVE: u32 = 37; +const USER_DYN_LOAD_BIAS: u64 = 0x4000_0000; +const MAX_USER_LOAD_END: u64 = 0x0000_8000_0000_0000; + +pub struct UserProgram { + pub entry: u64, + pub phdr: u64, + pub phent: u64, + pub phnum: u64, +} + +#[repr(C)] +struct Elf64Header { + ident: [u8; 16], + ty: u16, + machine: u16, + version: u32, + entry: u64, + phoff: u64, + shoff: u64, + flags: u32, + ehsize: u16, + phentsize: u16, + phnum: u16, + shentsize: u16, + shnum: u16, + shstrndx: u16, +} + +#[repr(C)] +struct Elf64ProgramHeader { + ty: u32, + flags: u32, + offset: u64, + vaddr: u64, + paddr: u64, + filesz: u64, + memsz: u64, + align: u64, +} + +#[repr(C)] +struct Elf64SectionHeader { + name: u32, + ty: u32, + flags: u64, + addr: u64, + offset: u64, + size: u64, + link: u32, + info: u32, + addralign: u64, + entsize: u64, +} + +#[repr(C)] +struct Elf64Rela { + offset: u64, + info: u64, + addend: i64, +} + +pub unsafe fn load_user_elf( + image: &LoadedFile, + _memory: &mut PhysicalMemoryManager, +) -> Option { + let bytes = unsafe { core::slice::from_raw_parts(image.ptr, image.len) }; + let header = parse_header(bytes)?; + let load_bias = if header.ty == ET_DYN { + USER_DYN_LOAD_BIAS + } else { + 0 + }; + + for index in 0..header.phnum as usize { + let ph = program_header(bytes, header, index)?; + if ph.ty != PT_LOAD { + continue; + } + + if ph.filesz > ph.memsz { + return None; + } + + let file_start = usize::try_from(ph.offset).ok()?; + let file_size = usize::try_from(ph.filesz).ok()?; + let file_end = file_start.checked_add(file_size)?; + if file_end > bytes.len() { + return None; + } + + let load_start = load_bias.checked_add(ph.vaddr)?; + let load_end = load_start.checked_add(ph.memsz)?; + if load_start < 0x1000 || load_end > MAX_USER_LOAD_END { + return None; + } + + unsafe { + copy_nonoverlapping( + bytes.as_ptr().add(file_start), + load_start as *mut u8, + file_size, + ); + + if ph.memsz > ph.filesz { + write_bytes( + (load_start + ph.filesz) as *mut u8, + 0, + usize::try_from(ph.memsz - ph.filesz).ok()?, + ); + } + } + } + + unsafe { + apply_relocations(bytes, header, load_bias)?; + } + + Some(UserProgram { + entry: load_bias.checked_add(header.entry)?, + phdr: program_header_address(bytes, header, load_bias)?, + phent: header.phentsize as u64, + phnum: header.phnum as u64, + }) +} + +fn parse_header(bytes: &[u8]) -> Option<&Elf64Header> { + if bytes.len() < core::mem::size_of::() { + return None; + } + + let header = unsafe { &*(bytes.as_ptr() as *const Elf64Header) }; + if &header.ident[0..4] != b"\x7fELF" { + return None; + } + if header.ident[EI_CLASS] != ELFCLASS64 || header.ident[EI_DATA] != ELFDATA2LSB { + return None; + } + if header.machine != EM_X86_64 || (header.ty != ET_EXEC && header.ty != ET_DYN) { + return None; + } + if header.phentsize as usize != core::mem::size_of::() { + return None; + } + + Some(header) +} + +unsafe fn apply_relocations(bytes: &[u8], header: &Elf64Header, load_bias: u64) -> Option<()> { + if header.shoff == 0 || header.shentsize as usize != core::mem::size_of::() + { + return Some(()); + } + + for index in 0..header.shnum as usize { + let section = section_header(bytes, header, index)?; + if section.ty != SHT_RELA { + continue; + } + if section.entsize as usize != core::mem::size_of::() { + continue; + } + + let offset = usize::try_from(section.offset).ok()?; + let size = usize::try_from(section.size).ok()?; + let end = offset.checked_add(size)?; + if end > bytes.len() { + return None; + } + + let count = size / core::mem::size_of::(); + for rela_index in 0..count { + let rela = unsafe { + &*(bytes + .as_ptr() + .add(offset + rela_index * core::mem::size_of::()) + as *const Elf64Rela) + }; + let ty = (rela.info & 0xffff_ffff) as u32; + let target = load_bias.checked_add(rela.offset)? as *mut u64; + match ty { + R_X86_64_RELATIVE => unsafe { + target.write(load_bias.wrapping_add(rela.addend as u64)); + }, + R_X86_64_IRELATIVE => unsafe { + let resolver_address = load_bias.wrapping_add(rela.addend as u64); + let resolver: extern "C" fn() -> u64 = + core::mem::transmute(resolver_address as usize); + target.write(resolver()); + }, + _ => {} + } + } + } + + Some(()) +} + +fn section_header<'a>( + bytes: &'a [u8], + header: &Elf64Header, + index: usize, +) -> Option<&'a Elf64SectionHeader> { + let shoff = usize::try_from(header.shoff).ok()?; + let offset = shoff.checked_add(index.checked_mul(header.shentsize as usize)?)?; + let end = offset.checked_add(core::mem::size_of::())?; + if end > bytes.len() { + return None; + } + + Some(unsafe { &*(bytes.as_ptr().add(offset) as *const Elf64SectionHeader) }) +} + +fn program_header<'a>( + bytes: &'a [u8], + header: &Elf64Header, + index: usize, +) -> Option<&'a Elf64ProgramHeader> { + let phoff = usize::try_from(header.phoff).ok()?; + let offset = phoff.checked_add(index.checked_mul(header.phentsize as usize)?)?; + let end = offset.checked_add(core::mem::size_of::())?; + if end > bytes.len() { + return None; + } + + Some(unsafe { &*(bytes.as_ptr().add(offset) as *const Elf64ProgramHeader) }) +} + +fn program_header_address(bytes: &[u8], header: &Elf64Header, load_bias: u64) -> Option { + let phoff = header.phoff; + for index in 0..header.phnum as usize { + let ph = program_header(bytes, header, index)?; + if ph.ty != PT_LOAD { + continue; + } + if phoff >= ph.offset && phoff < ph.offset.checked_add(ph.filesz)? { + return load_bias + .checked_add(ph.vaddr)? + .checked_add(phoff.checked_sub(ph.offset)?); + } + } + + load_bias.checked_add(phoff) +} diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 0000000..838dac1 --- /dev/null +++ b/src/font.rs @@ -0,0 +1,3 @@ +pub const GLYPH_WIDTH: usize = 8; +pub const GLYPH_HEIGHT: usize = 16; +pub const HANKAKU: &[u8; 4096] = include_bytes!("../resources/hankaku.bin"); diff --git a/src/framebuffer.rs b/src/framebuffer.rs new file mode 100644 index 0000000..5f4815d --- /dev/null +++ b/src/framebuffer.rs @@ -0,0 +1,135 @@ +use crate::efi::{EfiGraphicsOutputProtocol, EfiGraphicsPixelFormat}; + +#[derive(Clone, Copy)] +pub struct Rgb { + pub red: u8, + pub green: u8, + pub blue: u8, +} + +pub struct Framebuffer { + base: *mut u8, + width: usize, + height: usize, + stride: usize, + pixel_format: EfiGraphicsPixelFormat, +} + +impl Framebuffer { + pub unsafe fn from_gop(gop: *mut EfiGraphicsOutputProtocol) -> Option { + let mode = unsafe { (*gop).mode }; + if mode.is_null() { + return None; + } + + let info = unsafe { (*mode).info }; + if info.is_null() { + return None; + } + + let pixel_format = unsafe { (*info).pixel_format }; + if pixel_format != EfiGraphicsPixelFormat::RedGreenBlueReserved8BitPerColor + && pixel_format != EfiGraphicsPixelFormat::BlueGreenRedReserved8BitPerColor + { + return None; + } + + Some(Self { + base: unsafe { (*mode).frame_buffer_base as *mut u8 }, + width: unsafe { (*info).horizontal_resolution as usize }, + height: unsafe { (*info).vertical_resolution as usize }, + stride: unsafe { (*info).pixels_per_scan_line as usize }, + pixel_format, + }) + } + + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn clear(&mut self, color: Rgb) { + self.fill_rect(0, 0, self.width, self.height, color); + } + + pub fn fill_rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: Rgb) { + let end_x = x.saturating_add(width).min(self.width); + let end_y = y.saturating_add(height).min(self.height); + + for py in y..end_y { + for px in x..end_x { + self.put_pixel(px, py, color); + } + } + } + + pub fn scroll_region_up(&mut self, top: usize, bottom: usize, pixels: usize, fill: Rgb) { + if pixels == 0 { + return; + } + + let top = top.min(self.height); + let bottom = bottom.min(self.height); + if top >= bottom { + return; + } + + if pixels >= bottom - top { + self.fill_rect(0, top, self.width, bottom - top, fill); + return; + } + + for y in top..bottom { + for x in 0..self.width { + if y + pixels < bottom { + self.copy_pixel(x, y + pixels, x, y); + } else { + self.put_pixel(x, y, fill); + } + } + } + } + + pub fn put_pixel(&mut self, x: usize, y: usize, color: Rgb) { + if x >= self.width || y >= self.height { + return; + } + + let offset = (y * self.stride + x) * 4; + unsafe { + let pixel = self.base.add(offset); + match self.pixel_format { + EfiGraphicsPixelFormat::RedGreenBlueReserved8BitPerColor => { + pixel.write_volatile(color.red); + pixel.add(1).write_volatile(color.green); + pixel.add(2).write_volatile(color.blue); + pixel.add(3).write_volatile(0); + } + EfiGraphicsPixelFormat::BlueGreenRedReserved8BitPerColor => { + pixel.write_volatile(color.blue); + pixel.add(1).write_volatile(color.green); + pixel.add(2).write_volatile(color.red); + pixel.add(3).write_volatile(0); + } + _ => {} + } + } + } + + fn copy_pixel(&mut self, source_x: usize, source_y: usize, target_x: usize, target_y: usize) { + let source_offset = (source_y * self.stride + source_x) * 4; + let target_offset = (target_y * self.stride + target_x) * 4; + + unsafe { + let source = self.base.add(source_offset); + let target = self.base.add(target_offset); + target.write_volatile(source.read_volatile()); + target.add(1).write_volatile(source.add(1).read_volatile()); + target.add(2).write_volatile(source.add(2).read_volatile()); + target.add(3).write_volatile(source.add(3).read_volatile()); + } + } +} diff --git a/src/fs/ext4.rs b/src/fs/ext4.rs new file mode 100644 index 0000000..00f1811 --- /dev/null +++ b/src/fs/ext4.rs @@ -0,0 +1,921 @@ +use crate::drivers::storage::{ + ahci::AhciDisk, + block::{BlockDevice, BlockError}, + partition::PartitionBlockDevice, +}; +use crate::fs::uefi::LoadedFile; +use crate::memory::{PAGE_SIZE, PhysicalMemoryManager}; + +const EXT4_SUPERBLOCK_OFFSET: u64 = 1024; +const EXT4_SUPER_MAGIC: u16 = 0xef53; +const EXT4_ROOT_INO: u32 = 2; +const EXT4_EXTENTS_FL: u32 = 0x0008_0000; +const EXT4_EXTENT_MAGIC: u16 = 0xf30a; +const EXT4_N_BLOCKS_OFFSET: usize = 40; +const EXT4_NAME_LEN: usize = 255; +const MAX_BLOCK_SIZE: usize = 4096; +const MAX_INODE_SIZE: usize = 256; +const EXT4_FT_REG_FILE: u8 = 1; +const EXT4_FT_DIR: u8 = 2; +const EXT4_S_IFREG: u16 = 0o100000; +const EXT4_S_IFDIR: u16 = 0o040000; + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct Ext4Superblock { + pub inodes_count: u32, + pub blocks_count_lo: u32, + pub first_data_block: u32, + pub log_block_size: u32, + pub blocks_per_group: u32, + pub inodes_per_group: u32, + pub free_blocks_count: u32, + pub free_inodes_count: u32, + pub first_ino: u32, + pub magic: u16, + pub inode_size: u16, + pub desc_size: u16, + pub feature_incompat: u32, +} + +#[derive(Clone, Copy)] +#[allow(dead_code)] +pub struct Ext4FileSystem { + pub block_size: u32, + pub first_data_block: u32, + pub inode_size: u16, + pub blocks_count: u32, + pub inodes_count: u32, + pub blocks_per_group: u32, + pub inodes_per_group: u32, + pub first_ino: u32, + pub desc_size: u16, + pub feature_incompat: u32, +} + +#[derive(Clone, Copy)] +#[allow(dead_code)] +struct Ext4Inode { + mode: u16, + links: u16, + size: u64, + flags: u32, + block: [u8; 60], +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Ext4Error { + Block(BlockError), + BadMagic, + NotFound, + Unsupported, + NoMemory, + Invalid, + NoSpace, +} + +impl From for Ext4Error { + fn from(value: BlockError) -> Self { + Self::Block(value) + } +} + +pub fn mount(device: &mut D) -> Result { + let superblock = read_superblock(device)?; + if superblock.magic != EXT4_SUPER_MAGIC { + return Err(Ext4Error::BadMagic); + } + + let block_size = 1024u32 + .checked_shl(superblock.log_block_size) + .ok_or(Ext4Error::Unsupported)?; + if !(1024..=MAX_BLOCK_SIZE as u32).contains(&block_size) { + return Err(Ext4Error::Unsupported); + } + + Ok(Ext4FileSystem { + block_size, + first_data_block: superblock.first_data_block, + inode_size: if superblock.inode_size == 0 { + 128 + } else { + superblock.inode_size + }, + blocks_count: superblock.blocks_count_lo, + inodes_count: superblock.inodes_count, + blocks_per_group: superblock.blocks_per_group, + inodes_per_group: superblock.inodes_per_group, + first_ino: if superblock.first_ino == 0 { + 11 + } else { + superblock.first_ino + }, + desc_size: superblock.desc_size.max(32), + feature_incompat: superblock.feature_incompat, + }) +} + +static mut ROOT_DEVICE: Option> = None; +static mut ROOT_FS: Option = None; + +pub unsafe fn register_root(device: PartitionBlockDevice, fs: Ext4FileSystem) { + unsafe { + ROOT_DEVICE = Some(device); + ROOT_FS = Some(fs); + } +} + +pub fn create_file(path: &[u8]) -> Result<(), Ext4Error> { + unsafe { + let Some(mut device) = ROOT_DEVICE else { + return Err(Ext4Error::Unsupported); + }; + let Some(fs) = ROOT_FS else { + return Err(Ext4Error::Unsupported); + }; + let result = create_node(&mut device, &fs, path, false); + ROOT_DEVICE = Some(device); + result + } +} + +pub fn create_dir(path: &[u8]) -> Result<(), Ext4Error> { + unsafe { + let Some(mut device) = ROOT_DEVICE else { + return Err(Ext4Error::Unsupported); + }; + let Some(fs) = ROOT_FS else { + return Err(Ext4Error::Unsupported); + }; + let result = create_node(&mut device, &fs, path, true); + ROOT_DEVICE = Some(device); + result + } +} + +pub fn load_path( + device: &mut D, + fs: &Ext4FileSystem, + memory: &mut PhysicalMemoryManager, + path: &[u8], +) -> Result { + let inode_no = lookup_path(device, fs, path)?; + let inode = read_inode(device, fs, inode_no)?; + let len = usize::try_from(inode.size).map_err(|_| Ext4Error::Unsupported)?; + let pages = len.div_ceil(PAGE_SIZE).max(1); + let Some(buffer) = memory.alloc_pages(pages) else { + return Err(Ext4Error::NoMemory); + }; + + let target = unsafe { core::slice::from_raw_parts_mut(buffer, pages * PAGE_SIZE) }; + target[..len].fill(0); + if let Err(error) = read_inode_data(device, fs, &inode, &mut target[..len]) { + unsafe { + memory.free_pages(buffer, pages); + } + return Err(error); + } + + Ok(LoadedFile { + ptr: buffer as *const u8, + len, + }) +} + +pub fn mount_vfs_tree( + device: &mut D, + fs: &Ext4FileSystem, +) -> Result<(), Ext4Error> { + let mut root = [0u8; 256]; + root[0] = b'/'; + mount_vfs_dir(device, fs, EXT4_ROOT_INO, &root, 0) +} + +fn lookup_path( + device: &mut D, + fs: &Ext4FileSystem, + path: &[u8], +) -> Result { + let mut current = EXT4_ROOT_INO; + let mut index = 0; + + while index < path.len() { + while index < path.len() && path[index] == b'/' { + index += 1; + } + if index >= path.len() { + break; + } + + let start = index; + while index < path.len() && path[index] != b'/' && path[index] != 0 { + index += 1; + } + let name = &path[start..index]; + if name.is_empty() || name.len() > EXT4_NAME_LEN { + return Err(Ext4Error::NotFound); + } + + let dir = read_inode(device, fs, current)?; + current = find_dir_entry(device, fs, &dir, name)?; + } + + Ok(current) +} + +fn find_dir_entry( + device: &mut D, + fs: &Ext4FileSystem, + dir: &Ext4Inode, + name: &[u8], +) -> Result { + let mut block = [0u8; MAX_BLOCK_SIZE]; + let block_size = fs.block_size as usize; + let mut logical_block = 0u32; + let total_blocks = dir.size.div_ceil(fs.block_size as u64) as u32; + + while logical_block < total_blocks { + read_file_block(device, fs, dir, logical_block, &mut block[..block_size])?; + let mut offset = 0; + while offset + 8 <= block_size { + let inode = le_u32(&block, offset); + let rec_len = le_u16(&block, offset + 4) as usize; + let name_len = block[offset + 6] as usize; + if rec_len < 8 || offset + rec_len > block_size { + break; + } + if inode != 0 + && name_len == name.len() + && &block[offset + 8..offset + 8 + name_len] == name + { + return Ok(inode); + } + offset += rec_len; + } + logical_block += 1; + } + + Err(Ext4Error::NotFound) +} + +fn mount_vfs_dir( + device: &mut D, + fs: &Ext4FileSystem, + ino: u32, + path: &[u8; 256], + depth: usize, +) -> Result<(), Ext4Error> { + if depth > 8 { + return Ok(()); + } + let dir = read_inode(device, fs, ino)?; + let mut block = [0u8; MAX_BLOCK_SIZE]; + let block_size = fs.block_size as usize; + let mut logical_block = 0u32; + let total_blocks = dir.size.div_ceil(fs.block_size as u64) as u32; + + while logical_block < total_blocks { + read_file_block(device, fs, &dir, logical_block, &mut block[..block_size])?; + let mut offset = 0; + while offset + 8 <= block_size { + let child_ino = le_u32(&block, offset); + let rec_len = le_u16(&block, offset + 4) as usize; + let name_len = block[offset + 6] as usize; + let file_type = block[offset + 7]; + if rec_len < 8 || offset + rec_len > block_size { + break; + } + if child_ino != 0 + && name_len > 0 + && offset + 8 + name_len <= block_size + && !is_dot_dirent(&block[offset + 8..offset + 8 + name_len]) + { + let mut child_path = [0u8; 256]; + if join_path( + path, + &block[offset + 8..offset + 8 + name_len], + &mut child_path, + ) { + match file_type { + EXT4_FT_DIR => { + let _ = crate::fs::vfs::mount_ext4_node( + &child_path, + crate::fs::vfs::NodeKind::Directory, + ); + mount_vfs_dir(device, fs, child_ino, &child_path, depth + 1)?; + } + EXT4_FT_REG_FILE => { + let _ = crate::fs::vfs::mount_ext4_node( + &child_path, + crate::fs::vfs::NodeKind::Regular, + ); + } + _ => {} + } + } + } + offset += rec_len; + } + logical_block += 1; + } + + Ok(()) +} + +fn is_dot_dirent(name: &[u8]) -> bool { + name == b"." || name == b".." +} + +fn join_path(parent: &[u8; 256], name: &[u8], output: &mut [u8; 256]) -> bool { + let parent_len = nul_len(parent); + if parent_len == 0 || name.is_empty() { + return false; + } + let mut index = 0; + while index < parent_len && index < output.len() - 1 { + output[index] = parent[index]; + index += 1; + } + if !(index == 1 && output[0] == b'/') { + if index >= output.len() - 1 { + return false; + } + output[index] = b'/'; + index += 1; + } + if index + name.len() >= output.len() { + return false; + } + output[index..index + name.len()].copy_from_slice(name); + true +} + +fn read_inode_data( + device: &mut D, + fs: &Ext4FileSystem, + inode: &Ext4Inode, + output: &mut [u8], +) -> Result<(), Ext4Error> { + let block_size = fs.block_size as usize; + let mut block = [0u8; MAX_BLOCK_SIZE]; + let mut done = 0; + let mut logical_block = 0u32; + + while done < output.len() { + read_file_block(device, fs, inode, logical_block, &mut block[..block_size])?; + let chunk = block_size.min(output.len() - done); + output[done..done + chunk].copy_from_slice(&block[..chunk]); + done += chunk; + logical_block += 1; + } + + Ok(()) +} + +fn read_file_block( + device: &mut D, + fs: &Ext4FileSystem, + inode: &Ext4Inode, + logical_block: u32, + output: &mut [u8], +) -> Result<(), Ext4Error> { + if inode.flags & EXT4_EXTENTS_FL == 0 { + return Err(Ext4Error::Unsupported); + } + let physical = match extent_lookup(device, fs, &inode.block, logical_block) { + Ok(physical) => physical, + Err(Ext4Error::NotFound) => { + output.fill(0); + return Ok(()); + } + Err(error) => return Err(error), + }; + read_block(device, fs, physical, output) +} + +fn extent_lookup( + device: &mut D, + fs: &Ext4FileSystem, + root: &[u8; 60], + logical_block: u32, +) -> Result { + let magic = le_u16(root, 0); + let entries = le_u16(root, 2) as usize; + let depth = le_u16(root, 6); + if magic != EXT4_EXTENT_MAGIC { + return Err(Ext4Error::Unsupported); + } + + if depth == 0 { + return extent_leaf_lookup(root, entries, logical_block); + } + if depth != 1 { + return Err(Ext4Error::Unsupported); + } + + let mut leaf_block = None; + for index in 0..entries { + let offset = 12 + index * 12; + if offset + 12 > root.len() { + break; + } + let first = le_u32(root, offset); + if first <= logical_block { + let lo = le_u32(root, offset + 4) as u64; + let hi = le_u16(root, offset + 8) as u64; + leaf_block = Some((hi << 32) | lo); + } + } + + let Some(leaf_block) = leaf_block else { + return Err(Ext4Error::NotFound); + }; + let mut block = [0u8; MAX_BLOCK_SIZE]; + read_block(device, fs, leaf_block, &mut block[..fs.block_size as usize])?; + if le_u16(&block, 0) != EXT4_EXTENT_MAGIC { + return Err(Ext4Error::Unsupported); + } + extent_leaf_lookup(&block[..60], le_u16(&block, 2) as usize, logical_block) +} + +fn extent_leaf_lookup( + extents: &[u8], + entries: usize, + logical_block: u32, +) -> Result { + for index in 0..entries { + let offset = 12 + index * 12; + if offset + 12 > extents.len() { + break; + } + let start = le_u32(extents, offset); + let len = le_u16(extents, offset + 4) as u32 & 0x7fff; + let physical_hi = le_u16(extents, offset + 6) as u64; + let physical_lo = le_u32(extents, offset + 8) as u64; + if logical_block >= start && logical_block < start + len { + return Ok(((physical_hi << 32) | physical_lo) + (logical_block - start) as u64); + } + } + + Err(Ext4Error::NotFound) +} + +fn read_inode( + device: &mut D, + fs: &Ext4FileSystem, + inode_no: u32, +) -> Result { + if fs.inode_size as usize > MAX_INODE_SIZE { + return Err(Ext4Error::Unsupported); + } + + let group = (inode_no - 1) / fs.inodes_per_group; + let index = (inode_no - 1) % fs.inodes_per_group; + let inode_table = read_inode_table_block(device, fs, group)?; + let inode_offset = inode_table + .checked_mul(fs.block_size as u64) + .and_then(|base| base.checked_add(index as u64 * fs.inode_size as u64)) + .ok_or(Ext4Error::Unsupported)?; + + let mut buffer = [0u8; MAX_INODE_SIZE]; + device.read_at(inode_offset, &mut buffer[..fs.inode_size as usize])?; + + let mut block = [0u8; 60]; + block.copy_from_slice(&buffer[EXT4_N_BLOCKS_OFFSET..EXT4_N_BLOCKS_OFFSET + 60]); + let size_lo = le_u32(&buffer, 4) as u64; + let size_hi = le_u32(&buffer, 108) as u64; + + Ok(Ext4Inode { + mode: le_u16(&buffer, 0), + links: le_u16(&buffer, 26), + size: size_lo | (size_hi << 32), + flags: le_u32(&buffer, 32), + block, + }) +} + +fn read_inode_table_block( + device: &mut D, + fs: &Ext4FileSystem, + group: u32, +) -> Result { + let descriptor_table_block = if fs.block_size == 1024 { 2 } else { 1 }; + let descriptor_offset = + descriptor_table_block as u64 * fs.block_size as u64 + group as u64 * fs.desc_size as u64; + let mut descriptor = [0u8; 64]; + device.read_at(descriptor_offset, &mut descriptor[..fs.desc_size as usize])?; + let lo = le_u32(&descriptor, 8) as u64; + let hi = if fs.desc_size as usize >= 64 { + le_u32(&descriptor, 40) as u64 + } else { + 0 + }; + Ok((hi << 32) | lo) +} + +fn read_superblock(device: &mut D) -> Result { + let mut buffer = [0u8; 1024]; + device.read_at(EXT4_SUPERBLOCK_OFFSET, &mut buffer)?; + Ok(parse_superblock(&buffer)) +} + +fn read_block( + device: &mut D, + fs: &Ext4FileSystem, + block: u64, + output: &mut [u8], +) -> Result<(), Ext4Error> { + let offset = block + .checked_mul(fs.block_size as u64) + .ok_or(Ext4Error::Unsupported)?; + device.read_at(offset, output)?; + Ok(()) +} + +fn parse_superblock(buffer: &[u8; 1024]) -> Ext4Superblock { + Ext4Superblock { + inodes_count: le_u32(buffer, 0x00), + blocks_count_lo: le_u32(buffer, 0x04), + first_data_block: le_u32(buffer, 0x14), + log_block_size: le_u32(buffer, 0x18), + blocks_per_group: le_u32(buffer, 0x20), + inodes_per_group: le_u32(buffer, 0x28), + free_blocks_count: le_u32(buffer, 0x0c), + free_inodes_count: le_u32(buffer, 0x10), + first_ino: le_u32(buffer, 0x54), + magic: le_u16(buffer, 0x38), + inode_size: le_u16(buffer, 0x58), + feature_incompat: le_u32(buffer, 0x60), + desc_size: le_u16(buffer, 0xfe), + } +} + +fn create_node( + device: &mut D, + fs: &Ext4FileSystem, + path: &[u8], + is_dir: bool, +) -> Result<(), Ext4Error> { + let (parent_path, name) = split_parent(path)?; + if name.is_empty() || name.len() > EXT4_NAME_LEN { + return Err(Ext4Error::Invalid); + } + + if lookup_path(device, fs, path).is_ok() { + return Ok(()); + } + + let parent_ino = lookup_path(device, fs, parent_path)?; + let mut parent_inode = read_inode(device, fs, parent_ino)?; + if parent_inode.mode & EXT4_S_IFDIR == 0 { + return Err(Ext4Error::Invalid); + } + + let inode_no = alloc_inode(device, fs)?; + let mut inode = new_inode(is_dir); + if is_dir { + let block = alloc_block(device, fs)?; + inode.size = fs.block_size as u64; + inode.flags = EXT4_EXTENTS_FL; + inode.block = make_single_extent(block); + write_dir_block(device, fs, block, inode_no, parent_ino)?; + inode.links = 2; + } else { + inode.links = 1; + } + write_inode(device, fs, inode_no, &inode)?; + + append_dir_entry( + device, + fs, + parent_ino, + &mut parent_inode, + inode_no, + name, + is_dir, + )?; + if is_dir { + parent_inode.links += 1; + write_inode(device, fs, parent_ino, &parent_inode)?; + } + + Ok(()) +} + +fn new_inode(is_dir: bool) -> Ext4Inode { + let mode = if is_dir { + EXT4_S_IFDIR | 0o755 + } else { + EXT4_S_IFREG | 0o644 + }; + Ext4Inode { + mode, + links: if is_dir { 2 } else { 1 }, + size: 0, + flags: EXT4_EXTENTS_FL, + block: [0; 60], + } +} + +fn make_single_extent(block: u64) -> [u8; 60] { + let mut block_bytes = [0u8; 60]; + write_u16(&mut block_bytes, 0, EXT4_EXTENT_MAGIC); + write_u16(&mut block_bytes, 2, 1); + write_u16(&mut block_bytes, 6, 0); + write_u32(&mut block_bytes, 12, 0); + write_u16(&mut block_bytes, 16, 1); + write_u16(&mut block_bytes, 18, (block >> 32) as u16); + write_u32(&mut block_bytes, 20, block as u32); + block_bytes +} + +fn write_dir_block( + device: &mut D, + fs: &Ext4FileSystem, + block: u64, + self_ino: u32, + parent_ino: u32, +) -> Result<(), Ext4Error> { + let mut data = [0u8; MAX_BLOCK_SIZE]; + let block_size = fs.block_size as usize; + write_dirent_entry(&mut data, 0, self_ino, EXT4_FT_DIR, b".", 12); + write_dirent_entry( + &mut data, + 12, + parent_ino, + EXT4_FT_DIR, + b"..", + block_size - 12, + ); + device.write_at(block * fs.block_size as u64, &data[..block_size])?; + Ok(()) +} + +fn append_dir_entry( + device: &mut D, + fs: &Ext4FileSystem, + _dir_ino: u32, + dir: &mut Ext4Inode, + child_ino: u32, + name: &[u8], + is_dir: bool, +) -> Result<(), Ext4Error> { + let block_size = fs.block_size as usize; + let mut logical = 0u32; + let total_blocks = dir.size.div_ceil(fs.block_size as u64) as u32; + let mut block = [0u8; MAX_BLOCK_SIZE]; + while logical < total_blocks { + read_file_block(device, fs, dir, logical, &mut block[..block_size])?; + let physical = extent_lookup(device, fs, &dir.block, logical)?; + let mut offset = 0usize; + let mut previous_offset = None; + while offset + 8 <= block_size { + let inode = le_u32(&block, offset); + let rec_len = le_u16(&block, offset + 4) as usize; + let name_len = block[offset + 6] as usize; + if rec_len < 8 || offset + rec_len > block_size { + break; + } + if inode != 0 { + previous_offset = Some(offset); + let ideal = align_up(8 + name_len, 4); + if rec_len.saturating_sub(ideal) >= align_up(8 + name.len(), 4) { + write_u16(&mut block, offset + 4, ideal as u16); + let insert = offset + ideal; + write_dirent_entry( + &mut block, + insert, + child_ino, + if is_dir { + EXT4_FT_DIR + } else { + EXT4_FT_REG_FILE + }, + name, + rec_len - ideal, + ); + device.write_at(physical * fs.block_size as u64, &block[..block_size])?; + return Ok(()); + } + } + offset += rec_len; + } + if let Some(prev) = previous_offset { + let name_len = block[prev + 6] as usize; + let ideal = align_up(8 + name_len, 4); + let rec_len = le_u16(&block, prev + 4) as usize; + let new_rec = align_up(8 + name.len(), 4); + if rec_len > ideal + new_rec { + write_u16(&mut block, prev + 4, ideal as u16); + let insert = prev + ideal; + write_dirent_entry( + &mut block, + insert, + child_ino, + if is_dir { + EXT4_FT_DIR + } else { + EXT4_FT_REG_FILE + }, + name, + rec_len - ideal, + ); + device.write_at(physical * fs.block_size as u64, &block[..block_size])?; + return Ok(()); + } + } + logical += 1; + } + Err(Ext4Error::NoSpace) +} + +fn write_dirent_entry( + block: &mut [u8], + offset: usize, + ino: u32, + file_type: u8, + name: &[u8], + rec_len: usize, +) { + let rec_len = rec_len.max(align_up(8 + name.len(), 4)); + write_u32(block, offset, ino); + write_u16(block, offset + 4, rec_len as u16); + block[offset + 6] = name.len() as u8; + block[offset + 7] = file_type; + block[offset + 8..offset + 8 + name.len()].copy_from_slice(name); +} + +fn alloc_inode(device: &mut D, fs: &Ext4FileSystem) -> Result { + let groups = fs.blocks_count.div_ceil(fs.blocks_per_group); + for group in 0..groups { + let inode_bitmap = group_desc_field(device, fs, group, 4)?; + let mut bitmap = [0u8; MAX_BLOCK_SIZE]; + device.read_at( + inode_bitmap * fs.block_size as u64, + &mut bitmap[..fs.block_size as usize], + )?; + let start_inode = group * fs.inodes_per_group + 1; + let end_inode = (start_inode + fs.inodes_per_group - 1).min(fs.inodes_count); + for inode_no in start_inode..=end_inode { + if inode_no < fs.first_ino { + continue; + } + let bit = (inode_no - start_inode) as usize; + if bitmap[bit / 8] & (1 << (bit % 8)) == 0 { + bitmap[bit / 8] |= 1 << (bit % 8); + device.write_at( + inode_bitmap * fs.block_size as u64, + &bitmap[..fs.block_size as usize], + )?; + decrement_free_counters(device, fs, group, true, false)?; + return Ok(inode_no); + } + } + } + Err(Ext4Error::NoSpace) +} + +fn alloc_block(device: &mut D, fs: &Ext4FileSystem) -> Result { + let groups = fs.blocks_count.div_ceil(fs.blocks_per_group); + for group in 0..groups { + let block_bitmap = group_desc_field(device, fs, group, 0)?; + let mut bitmap = [0u8; MAX_BLOCK_SIZE]; + device.read_at( + block_bitmap * fs.block_size as u64, + &mut bitmap[..fs.block_size as usize], + )?; + let start_block = group * fs.blocks_per_group + fs.first_data_block; + let end_block = (start_block + fs.blocks_per_group - 1).min(fs.blocks_count - 1); + for block_no in start_block..=end_block { + let bit = (block_no - start_block) as usize; + if bitmap[bit / 8] & (1 << (bit % 8)) == 0 { + bitmap[bit / 8] |= 1 << (bit % 8); + device.write_at( + block_bitmap * fs.block_size as u64, + &bitmap[..fs.block_size as usize], + )?; + decrement_free_counters(device, fs, group, false, true)?; + return Ok(block_no as u64); + } + } + } + Err(Ext4Error::NoSpace) +} + +fn group_desc_field( + device: &mut D, + fs: &Ext4FileSystem, + group: u32, + offset: usize, +) -> Result { + let descriptor_table_block = if fs.block_size == 1024 { 2 } else { 1 }; + let descriptor_offset = descriptor_table_block as u64 * fs.block_size as u64 + + group as u64 * fs.desc_size as u64 + + offset as u64; + let mut buffer = [0u8; 8]; + device.read_at(descriptor_offset, &mut buffer[..4])?; + Ok(le_u32(&buffer, 0) as u64) +} + +fn decrement_free_counters( + device: &mut D, + fs: &Ext4FileSystem, + group: u32, + inode: bool, + block: bool, +) -> Result<(), Ext4Error> { + let mut sb = [0u8; 1024]; + device.read_at(EXT4_SUPERBLOCK_OFFSET, &mut sb)?; + if inode { + let free = le_u32(&sb, 0x10).saturating_sub(1); + write_u32(&mut sb, 0x10, free); + } + if block { + let free = le_u32(&sb, 0x0c).saturating_sub(1); + write_u32(&mut sb, 0x0c, free); + } + device.write_at(EXT4_SUPERBLOCK_OFFSET, &sb)?; + let descriptor_table_block = if fs.block_size == 1024 { 2 } else { 1 }; + let descriptor_offset = + descriptor_table_block as u64 * fs.block_size as u64 + group as u64 * fs.desc_size as u64; + let mut descriptor = [0u8; 64]; + device.read_at(descriptor_offset, &mut descriptor[..fs.desc_size as usize])?; + if block { + let free = le_u16(&descriptor, 12).saturating_sub(1); + write_u16(&mut descriptor, 12, free); + } + if inode { + let free = le_u16(&descriptor, 14).saturating_sub(1); + write_u16(&mut descriptor, 14, free); + } + device.write_at(descriptor_offset, &descriptor[..fs.desc_size as usize])?; + Ok(()) +} + +fn write_inode( + device: &mut D, + fs: &Ext4FileSystem, + inode_no: u32, + inode: &Ext4Inode, +) -> Result<(), Ext4Error> { + let group = (inode_no - 1) / fs.inodes_per_group; + let index = (inode_no - 1) % fs.inodes_per_group; + let inode_table = read_inode_table_block(device, fs, group)?; + let inode_offset = inode_table + .checked_mul(fs.block_size as u64) + .and_then(|base| base.checked_add(index as u64 * fs.inode_size as u64)) + .ok_or(Ext4Error::Unsupported)?; + let mut buffer = [0u8; MAX_INODE_SIZE]; + device.read_at(inode_offset, &mut buffer[..fs.inode_size as usize])?; + write_u16(&mut buffer, 0, inode.mode); + write_u16(&mut buffer, 26, inode.links); + write_u32(&mut buffer, 4, inode.size as u32); + write_u32(&mut buffer, 108, (inode.size >> 32) as u32); + write_u32(&mut buffer, 32, inode.flags); + buffer[EXT4_N_BLOCKS_OFFSET..EXT4_N_BLOCKS_OFFSET + 60].copy_from_slice(&inode.block); + device.write_at(inode_offset, &buffer[..fs.inode_size as usize])?; + Ok(()) +} + +fn split_parent(path: &[u8]) -> Result<(&[u8], &[u8]), Ext4Error> { + let len = nul_len(path); + let path = &path[..len]; + let Some(last) = path.iter().rposition(|byte| *byte == b'/') else { + return Err(Ext4Error::Invalid); + }; + if last == 0 { + return Ok((&path[..1], &path[1..])); + } + Ok((&path[..last], &path[last + 1..])) +} + +fn nul_len(path: &[u8]) -> usize { + path.iter() + .position(|byte| *byte == 0) + .unwrap_or(path.len()) +} + +fn align_up(value: usize, align: usize) -> usize { + (value + align - 1) & !(align - 1) +} + +fn write_u16(buffer: &mut [u8], offset: usize, value: u16) { + buffer[offset..offset + 2].copy_from_slice(&value.to_le_bytes()); +} + +fn write_u32(buffer: &mut [u8], offset: usize, value: u32) { + buffer[offset..offset + 4].copy_from_slice(&value.to_le_bytes()); +} + +fn le_u16(buffer: &[u8], offset: usize) -> u16 { + u16::from_le_bytes([buffer[offset], buffer[offset + 1]]) +} + +fn le_u32(buffer: &[u8], offset: usize) -> u32 { + u32::from_le_bytes([ + buffer[offset], + buffer[offset + 1], + buffer[offset + 2], + buffer[offset + 3], + ]) +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs new file mode 100644 index 0000000..2e52be2 --- /dev/null +++ b/src/fs/mod.rs @@ -0,0 +1,3 @@ +pub mod ext4; +pub mod uefi; +pub mod vfs; diff --git a/src/fs/uefi.rs b/src/fs/uefi.rs new file mode 100644 index 0000000..ce453ad --- /dev/null +++ b/src/fs/uefi.rs @@ -0,0 +1,179 @@ +use core::ffi::c_void; + +use crate::efi::{ + EFI_LOADED_IMAGE_PROTOCOL_GUID, EFI_NOT_FOUND, EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, + EFI_SUCCESS, EfiBootServices, EfiGuid, EfiHandle, EfiLoadedImageProtocol, EfiStatus, +}; +use crate::memory::{PAGE_SIZE, PhysicalMemoryManager}; + +pub const BASH_PATH: &[u16] = &[ + '\\' as u16, + 'b' as u16, + 'i' as u16, + 'n' as u16, + '\\' as u16, + 'b' as u16, + 'a' as u16, + 's' as u16, + 'h' as u16, + 0, +]; + +pub const SH_PATH: &[u16] = &[ + '\\' as u16, + 'b' as u16, + 'i' as u16, + 'n' as u16, + '\\' as u16, + 's' as u16, + 'h' as u16, + 0, +]; + +const EFI_FILE_MODE_READ: u64 = 1; +const EFI_FILE_INFO_GUID: EfiGuid = EfiGuid::new( + 0x09576e92, + 0x6d3f, + 0x11d2, + [0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b], +); + +pub struct LoadedFile { + pub ptr: *const u8, + pub len: usize, +} + +#[repr(C)] +struct EfiSimpleFileSystemProtocol { + revision: u64, + open_volume: extern "efiapi" fn( + this: *mut EfiSimpleFileSystemProtocol, + root: *mut *mut EfiFileProtocol, + ) -> EfiStatus, +} + +#[repr(C)] +struct EfiFileProtocol { + revision: u64, + open: extern "efiapi" fn( + this: *mut EfiFileProtocol, + new_handle: *mut *mut EfiFileProtocol, + file_name: *const u16, + open_mode: u64, + attributes: u64, + ) -> EfiStatus, + close: extern "efiapi" fn(this: *mut EfiFileProtocol) -> EfiStatus, + delete: usize, + read: extern "efiapi" fn( + this: *mut EfiFileProtocol, + buffer_size: *mut usize, + buffer: *mut c_void, + ) -> EfiStatus, + write: usize, + get_position: usize, + set_position: usize, + get_info: extern "efiapi" fn( + this: *mut EfiFileProtocol, + information_type: *const EfiGuid, + buffer_size: *mut usize, + buffer: *mut c_void, + ) -> EfiStatus, +} + +#[repr(C)] +struct EfiFileInfoPrefix { + size: u64, + file_size: u64, + physical_size: u64, +} + +pub unsafe fn load_file( + image_handle: EfiHandle, + boot_services: *mut EfiBootServices, + memory: &mut PhysicalMemoryManager, + path: &[u16], +) -> Option { + let mut loaded_image = core::ptr::null_mut(); + let status = unsafe { + ((*boot_services).handle_protocol)( + image_handle, + &EFI_LOADED_IMAGE_PROTOCOL_GUID, + &mut loaded_image, + ) + }; + if status != EFI_SUCCESS || loaded_image.is_null() { + return None; + } + + let loaded_image = loaded_image as *mut EfiLoadedImageProtocol; + let mut fs = core::ptr::null_mut(); + let status = unsafe { + ((*boot_services).handle_protocol)( + (*loaded_image).device_handle, + &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, + &mut fs, + ) + }; + if status != EFI_SUCCESS || fs.is_null() { + return None; + } + + let fs = fs as *mut EfiSimpleFileSystemProtocol; + let mut root = core::ptr::null_mut(); + let status = unsafe { ((*fs).open_volume)(fs, &mut root) }; + if status != EFI_SUCCESS || root.is_null() { + return None; + } + + let mut file = core::ptr::null_mut(); + let status = unsafe { ((*root).open)(root, &mut file, path.as_ptr(), EFI_FILE_MODE_READ, 0) }; + unsafe { + ((*root).close)(root); + } + if status == EFI_NOT_FOUND || status != EFI_SUCCESS || file.is_null() { + return None; + } + + let size = unsafe { file_size(file)? }; + let pages = size.div_ceil(PAGE_SIZE).max(1); + let buffer = memory.alloc_pages(pages)?; + let mut read_len = size; + let status = unsafe { ((*file).read)(file, &mut read_len, buffer.cast::()) }; + unsafe { + ((*file).close)(file); + } + + if status != EFI_SUCCESS || read_len == 0 { + unsafe { + memory.free_pages(buffer, pages); + } + return None; + } + + Some(LoadedFile { + ptr: buffer as *const u8, + len: read_len, + }) +} + +unsafe fn file_size(file: *mut EfiFileProtocol) -> Option { + let mut info_buffer = [0u8; 1024]; + let mut info_size = info_buffer.len(); + let status = unsafe { + ((*file).get_info)( + file, + &EFI_FILE_INFO_GUID, + &mut info_size, + info_buffer.as_mut_ptr().cast::(), + ) + }; + if status != EFI_SUCCESS { + return None; + } + if info_size < core::mem::size_of::() { + return None; + } + + let info = unsafe { &*(info_buffer.as_ptr() as *const EfiFileInfoPrefix) }; + usize::try_from(info.file_size).ok() +} diff --git a/src/fs/vfs.rs b/src/fs/vfs.rs new file mode 100644 index 0000000..28f3731 --- /dev/null +++ b/src/fs/vfs.rs @@ -0,0 +1,1070 @@ +const MAX_VFS_NODES: usize = 1024; +const MAX_FDS: usize = 512; +const MAX_AUTO_VFS_FDS: usize = 256; +const MAX_PATH: usize = 256; + +pub const AT_FDCWD: isize = -100; + +const S_IFDIR: u32 = 0o040000; +const S_IFCHR: u32 = 0o020000; +const S_IFREG: u32 = 0o100000; + +const EBADF: isize = 9; +const EFAULT: isize = 14; +const EINVAL: isize = 22; +const ENOENT: isize = 2; +const ENOTDIR: isize = 20; +const EMFILE: isize = 24; +const EIO: isize = 5; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Color { + Red, + Black, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum NodeKind { + Directory, + Regular, + CharDevice, +} + +#[derive(Clone, Copy)] +struct VfsNode { + path: [u8; MAX_PATH], + kind: NodeKind, + data: *const u8, + len: usize, + parent: Option, + left: Option, + right: Option, + color: Color, +} + +impl VfsNode { + const fn empty() -> Self { + Self { + path: [0; MAX_PATH], + kind: NodeKind::Regular, + data: core::ptr::null(), + len: 0, + parent: None, + left: None, + right: None, + color: Color::Black, + } + } +} + +#[derive(Clone, Copy)] +struct FileDescriptor { + node: Option, + offset: usize, +} + +impl FileDescriptor { + const fn empty() -> Self { + Self { + node: None, + offset: 0, + } + } +} + +pub struct Vfs { + root: Option, + nodes: [VfsNode; MAX_VFS_NODES], + len: usize, + fds: [FileDescriptor; MAX_FDS], + cwd: [u8; MAX_PATH], +} + +impl Vfs { + const fn new() -> Self { + Self { + root: None, + nodes: [VfsNode::empty(); MAX_VFS_NODES], + len: 0, + fds: [FileDescriptor::empty(); MAX_FDS], + cwd: [0; MAX_PATH], + } + } + + fn reset(&mut self) { + self.root = None; + self.len = 0; + self.fds.fill(FileDescriptor::empty()); + self.cwd = [0; MAX_PATH]; + self.cwd[0] = b'/'; + } + + fn insert(&mut self, path: &[u8], kind: NodeKind, data: *const u8, len: usize) -> Option<()> { + if self.len >= MAX_VFS_NODES { + return None; + } + + let mut parent = None; + let mut cursor = self.root; + while let Some(index) = cursor { + parent = cursor; + match compare_path(path, &self.nodes[index].path) { + core::cmp::Ordering::Less => cursor = self.nodes[index].left, + core::cmp::Ordering::Greater => cursor = self.nodes[index].right, + core::cmp::Ordering::Equal => { + self.nodes[index].kind = kind; + self.nodes[index].data = data; + self.nodes[index].len = len; + return Some(()); + } + } + } + + let index = self.len; + self.len += 1; + let mut stored_path = [0u8; MAX_PATH]; + let path_len = nul_len(path).min(MAX_PATH - 1); + stored_path[..path_len].copy_from_slice(&path[..path_len]); + + self.nodes[index] = VfsNode { + path: stored_path, + kind, + data, + len, + parent, + left: None, + right: None, + color: Color::Red, + }; + + if let Some(parent) = parent { + if compare_path(path, &self.nodes[parent].path).is_lt() { + self.nodes[parent].left = Some(index); + } else { + self.nodes[parent].right = Some(index); + } + } else { + self.root = Some(index); + } + + self.insert_fixup(index); + Some(()) + } + + fn find(&self, path: &[u8]) -> Option { + let mut cursor = self.root; + while let Some(index) = cursor { + match compare_path(path, &self.nodes[index].path) { + core::cmp::Ordering::Less => cursor = self.nodes[index].left, + core::cmp::Ordering::Greater => cursor = self.nodes[index].right, + core::cmp::Ordering::Equal => return Some(index), + } + } + + None + } + + fn resolve_executable_path(&self, path: &[u8]) -> Option { + let node = self.find(path); + if let Some(index) = node { + let node = self.nodes[index]; + if !(node.kind == NodeKind::Regular + && node.len == 0 + && node.data.is_null() + && is_busybox_applet_path(path)) + { + return Some(index); + } + } + self.busybox_fallback(path).or(node) + } + + fn open_path(&mut self, path: &[u8]) -> isize { + let Some(node) = self.resolve_executable_path(path) else { + return -ENOENT; + }; + + for fd in 3..MAX_AUTO_VFS_FDS { + if self.fds[fd].node.is_none() { + self.fds[fd] = FileDescriptor { + node: Some(node), + offset: 0, + }; + return fd as isize; + } + } + + -EMFILE + } + + fn fd_dir_path(&self, fd: usize) -> Result<[u8; MAX_PATH], isize> { + if fd >= MAX_FDS { + return Err(-EBADF); + } + let Some(node) = self.fds[fd].node else { + return Err(-EBADF); + }; + if self.nodes[node].kind != NodeKind::Directory { + return Err(-ENOTDIR); + } + Ok(self.nodes[node].path) + } + + fn read_fd(&mut self, fd: usize, buffer: *mut u8, len: usize) -> isize { + if fd >= MAX_FDS || buffer.is_null() { + return -EBADF; + } + let Some(node_index) = self.fds[fd].node else { + return -EBADF; + }; + let node = self.nodes[node_index]; + if node.kind == NodeKind::CharDevice { + let buffer = unsafe { core::slice::from_raw_parts_mut(buffer, len) }; + return crate::console::read(buffer) as isize; + } + if node.kind != NodeKind::Regular { + return -EINVAL; + } + + let offset = self.fds[fd].offset.min(node.len); + let available = node.len - offset; + let count = len.min(available); + if count > 0 { + unsafe { + core::ptr::copy_nonoverlapping(node.data.add(offset), buffer, count); + } + } + self.fds[fd].offset = offset + count; + count as isize + } + + fn write_fd(&mut self, fd: usize, buffer: *const u8, len: usize) -> isize { + if fd >= MAX_FDS || buffer.is_null() { + return -EBADF; + } + let Some(node_index) = self.fds[fd].node else { + return -EBADF; + }; + if self.nodes[node_index].kind != NodeKind::CharDevice { + return -EINVAL; + } + + let buffer = unsafe { core::slice::from_raw_parts(buffer, len) }; + crate::console::write(buffer) as isize + } + + fn close_fd(&mut self, fd: usize) -> isize { + if fd < 3 || fd >= MAX_FDS || self.fds[fd].node.is_none() { + return -EBADF; + } + self.fds[fd] = FileDescriptor::empty(); + 0 + } + + fn dup_fd(&mut self, oldfd: usize, min_newfd: usize) -> isize { + if oldfd >= MAX_FDS || self.fds[oldfd].node.is_none() { + return -EBADF; + } + let start = min_newfd.max(3); + let end = if start < MAX_AUTO_VFS_FDS { + MAX_AUTO_VFS_FDS + } else { + MAX_FDS + }; + for fd in start..end { + if self.fds[fd].node.is_none() { + self.fds[fd] = self.fds[oldfd]; + return fd as isize; + } + } + -EMFILE + } + + fn dup2_fd(&mut self, oldfd: usize, newfd: usize) -> isize { + if oldfd >= MAX_FDS || newfd >= MAX_FDS || self.fds[oldfd].node.is_none() { + return -EBADF; + } + if oldfd == newfd { + return newfd as isize; + } + self.fds[newfd] = self.fds[oldfd]; + newfd as isize + } + + fn lseek_fd(&mut self, fd: usize, offset: isize, whence: usize) -> isize { + if fd >= MAX_FDS { + return -EBADF; + } + let Some(node_index) = self.fds[fd].node else { + return -EBADF; + }; + let len = self.nodes[node_index].len as isize; + let current = self.fds[fd].offset as isize; + let next = match whence { + 0 => offset, + 1 => current.saturating_add(offset), + 2 => len.saturating_add(offset), + _ => return -EINVAL, + }; + if next < 0 { + return -EINVAL; + } + self.fds[fd].offset = next as usize; + next + } + + fn stat_fd(&self, fd: usize, stat: *mut u8) -> isize { + if fd >= MAX_FDS || stat.is_null() { + return -EBADF; + } + let Some(node_index) = self.fds[fd].node else { + return -EBADF; + }; + write_stat(stat, self.nodes[node_index]) + } + + fn is_tty_fd(&self, fd: usize) -> bool { + if fd >= MAX_FDS { + return false; + } + let Some(node_index) = self.fds[fd].node else { + return false; + }; + self.nodes[node_index].kind == NodeKind::CharDevice + } + + fn getdents64_fd(&mut self, fd: usize, buffer: *mut u8, len: usize) -> isize { + if fd >= MAX_FDS || buffer.is_null() { + return -EBADF; + } + let Some(dir_index) = self.fds[fd].node else { + return -EBADF; + }; + let dir = self.nodes[dir_index]; + if dir.kind != NodeKind::Directory { + return -ENOTDIR; + } + + let mut written = 0usize; + let mut cursor = self.fds[fd].offset; + if cursor == 0 { + if !write_dirent(buffer, len, &mut written, 1, NodeKind::Directory, b".") { + return 0; + } + cursor = 1; + } + if cursor == 1 { + if !write_dirent(buffer, len, &mut written, 2, NodeKind::Directory, b"..") { + self.fds[fd].offset = 1; + return written as isize; + } + cursor = 2; + } + while cursor < self.len { + let node = self.nodes[cursor]; + cursor += 1; + let Some(name) = child_name(&dir.path, &node.path) else { + continue; + }; + + if !write_dirent(buffer, len, &mut written, cursor as u64, node.kind, name) { + cursor -= 1; + break; + } + } + + self.fds[fd].offset = cursor; + written as isize + } + + fn stat_path(&self, path: &[u8], stat: *mut u8) -> isize { + if stat.is_null() { + return -EFAULT; + } + let Some(node_index) = self.resolve_executable_path(path) else { + return -ENOENT; + }; + write_stat(stat, self.nodes[node_index]) + } + + fn chdir_path(&mut self, path: &[u8]) -> isize { + let Some(node_index) = self.find(path) else { + return -ENOENT; + }; + if self.nodes[node_index].kind != NodeKind::Directory { + return -ENOTDIR; + } + self.cwd = [0; MAX_PATH]; + let path_len = nul_len(path).min(MAX_PATH - 1); + self.cwd[..path_len].copy_from_slice(&path[..path_len]); + if path_len == 0 { + self.cwd[0] = b'/'; + } + 0 + } + + fn create_path(&mut self, path: &[u8], kind: NodeKind) -> isize { + if self.find(path).is_some() { + return 0; + } + if self.insert(path, kind, core::ptr::null(), 0).is_some() { + 0 + } else { + -EMFILE + } + } + + fn getcwd(&self, buffer: *mut u8, len: usize) -> isize { + if buffer.is_null() { + return -EFAULT; + } + let cwd_len = nul_len(&self.cwd); + if len <= cwd_len { + return -EINVAL; + } + unsafe { + core::ptr::copy_nonoverlapping(self.cwd.as_ptr(), buffer, cwd_len); + buffer.add(cwd_len).write(0); + } + (cwd_len + 1) as isize + } + + fn busybox_fallback(&self, path: &[u8]) -> Option { + if is_busybox_applet_path(path) { + self.find(b"/usr/bin/busybox") + .or_else(|| self.find(b"/bin/busybox")) + } else { + None + } + } + + fn insert_fixup(&mut self, mut node: usize) { + while self.color(self.nodes[node].parent) == Color::Red { + let parent = self.nodes[node].parent.unwrap(); + let grandparent = self.nodes[parent].parent.unwrap(); + + if self.nodes[grandparent].left == Some(parent) { + let uncle = self.nodes[grandparent].right; + if self.color(uncle) == Color::Red { + self.nodes[parent].color = Color::Black; + if let Some(uncle) = uncle { + self.nodes[uncle].color = Color::Black; + } + self.nodes[grandparent].color = Color::Red; + node = grandparent; + } else { + if self.nodes[parent].right == Some(node) { + node = parent; + self.rotate_left(node); + } + let parent = self.nodes[node].parent.unwrap(); + let grandparent = self.nodes[parent].parent.unwrap(); + self.nodes[parent].color = Color::Black; + self.nodes[grandparent].color = Color::Red; + self.rotate_right(grandparent); + } + } else { + let uncle = self.nodes[grandparent].left; + if self.color(uncle) == Color::Red { + self.nodes[parent].color = Color::Black; + if let Some(uncle) = uncle { + self.nodes[uncle].color = Color::Black; + } + self.nodes[grandparent].color = Color::Red; + node = grandparent; + } else { + if self.nodes[parent].left == Some(node) { + node = parent; + self.rotate_right(node); + } + let parent = self.nodes[node].parent.unwrap(); + let grandparent = self.nodes[parent].parent.unwrap(); + self.nodes[parent].color = Color::Black; + self.nodes[grandparent].color = Color::Red; + self.rotate_left(grandparent); + } + } + } + + if let Some(root) = self.root { + self.nodes[root].color = Color::Black; + } + } + + fn rotate_left(&mut self, node: usize) { + let Some(right) = self.nodes[node].right else { + return; + }; + self.nodes[node].right = self.nodes[right].left; + if let Some(right_left) = self.nodes[right].left { + self.nodes[right_left].parent = Some(node); + } + + self.nodes[right].parent = self.nodes[node].parent; + if let Some(parent) = self.nodes[node].parent { + if self.nodes[parent].left == Some(node) { + self.nodes[parent].left = Some(right); + } else { + self.nodes[parent].right = Some(right); + } + } else { + self.root = Some(right); + } + + self.nodes[right].left = Some(node); + self.nodes[node].parent = Some(right); + } + + fn rotate_right(&mut self, node: usize) { + let Some(left) = self.nodes[node].left else { + return; + }; + self.nodes[node].left = self.nodes[left].right; + if let Some(left_right) = self.nodes[left].right { + self.nodes[left_right].parent = Some(node); + } + + self.nodes[left].parent = self.nodes[node].parent; + if let Some(parent) = self.nodes[node].parent { + if self.nodes[parent].right == Some(node) { + self.nodes[parent].right = Some(left); + } else { + self.nodes[parent].left = Some(left); + } + } else { + self.root = Some(left); + } + + self.nodes[left].right = Some(node); + self.nodes[node].parent = Some(left); + } + + fn color(&self, node: Option) -> Color { + node.map(|index| self.nodes[index].color) + .unwrap_or(Color::Black) + } +} + +static mut VFS: Vfs = Vfs::new(); + +pub unsafe fn init() { + unsafe { + let vfs = vfs_mut(); + vfs.reset(); + let _ = vfs.insert(b"/", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/bin", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/dev", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/dev/tty", NodeKind::CharDevice, core::ptr::null(), 0); + let _ = vfs.insert(b"/etc", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/tmp", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/usr", NodeKind::Directory, core::ptr::null(), 0); + let _ = vfs.insert(b"/usr/bin", NodeKind::Directory, core::ptr::null(), 0); + if let Some(tty) = vfs.find(b"/dev/tty") { + vfs.fds[0] = FileDescriptor { + node: Some(tty), + offset: 0, + }; + vfs.fds[1] = FileDescriptor { + node: Some(tty), + offset: 0, + }; + vfs.fds[2] = FileDescriptor { + node: Some(tty), + offset: 0, + }; + } + } +} + +pub unsafe fn mount_static_file(path: &'static [u8], data: *const u8, len: usize) -> Option<()> { + unsafe { vfs_mut().insert(path, NodeKind::Regular, data, len) } +} + +pub fn mount_ext4_node(path: &[u8], kind: NodeKind) -> Option<()> { + unsafe { vfs_mut().insert(path, kind, core::ptr::null(), 0) } +} + +#[allow(dead_code)] +pub fn open_user_path(path: *const u8) -> isize { + open_user_path_with_flags(path, 0) +} + +pub fn open_user_path_with_flags(path: *const u8, flags: usize) -> isize { + let Some(path) = (unsafe { read_user_path(path) }) else { + return -EFAULT; + }; + if flags & 0o100 != 0 && unsafe { vfs_ref().find(&path).is_none() } { + let result = create_regular_path(&path); + if result < 0 { + return result; + } + } + unsafe { vfs_mut().open_path(&path) } +} + +pub fn openat_user_path(dirfd: isize, path: *const u8, flags: usize) -> isize { + if unsafe { path_starts_with_slash(path) } || dirfd == AT_FDCWD { + return open_user_path_with_flags(path, flags); + } + + let base = match unsafe { vfs_ref().fd_dir_path(dirfd as usize) } { + Ok(path) => path, + Err(error) => return error, + }; + let Some(path) = (unsafe { read_user_path_relative(&base, path) }) else { + return -EFAULT; + }; + if flags & 0o100 != 0 && unsafe { vfs_ref().find(&path).is_none() } { + let result = create_regular_path(&path); + if result < 0 { + return result; + } + } + unsafe { vfs_mut().open_path(&path) } +} + +pub fn read(fd: usize, buffer: *mut u8, len: usize) -> isize { + unsafe { vfs_mut().read_fd(fd, buffer, len) } +} + +pub fn write(fd: usize, buffer: *const u8, len: usize) -> isize { + unsafe { vfs_mut().write_fd(fd, buffer, len) } +} + +pub fn close(fd: usize) -> isize { + unsafe { vfs_mut().close_fd(fd) } +} + +pub fn dup(fd: usize) -> isize { + unsafe { vfs_mut().dup_fd(fd, 3) } +} + +pub fn dup_min(fd: usize, min_newfd: usize) -> isize { + unsafe { vfs_mut().dup_fd(fd, min_newfd) } +} + +pub fn dup2(oldfd: usize, newfd: usize) -> isize { + unsafe { vfs_mut().dup2_fd(oldfd, newfd) } +} + +pub fn lseek(fd: usize, offset: isize, whence: usize) -> isize { + unsafe { vfs_mut().lseek_fd(fd, offset, whence) } +} + +pub fn fstat(fd: usize, stat: *mut u8) -> isize { + unsafe { vfs_ref().stat_fd(fd, stat) } +} + +pub fn is_tty(fd: usize) -> bool { + unsafe { vfs_ref().is_tty_fd(fd) } +} + +pub fn poll(fd: usize, events: i16) -> Option { + if !unsafe { vfs_ref().is_tty_fd(fd) } { + return None; + } + let mut revents = 0; + if events & 0x0001 != 0 && crate::console::has_input() { + revents |= 0x0001; + } + if events & 0x0004 != 0 { + revents |= 0x0004; + } + Some(revents) +} + +pub fn getdents64(fd: usize, buffer: *mut u8, len: usize) -> isize { + unsafe { vfs_mut().getdents64_fd(fd, buffer, len) } +} + +pub fn stat_user_path(path: *const u8, stat: *mut u8) -> isize { + let Some(path) = (unsafe { read_user_path(path) }) else { + return -EFAULT; + }; + unsafe { vfs_ref().stat_path(&path, stat) } +} + +pub fn statat_user_path(dirfd: isize, path: *const u8, stat: *mut u8) -> isize { + if unsafe { path_starts_with_slash(path) } || dirfd == AT_FDCWD { + return stat_user_path(path, stat); + } + + let base = match unsafe { vfs_ref().fd_dir_path(dirfd as usize) } { + Ok(path) => path, + Err(_) => return stat_user_path(path, stat), + }; + let Some(path) = (unsafe { read_user_path_relative(&base, path) }) else { + return -EFAULT; + }; + unsafe { vfs_ref().stat_path(&path, stat) } +} + +pub fn access_user_path(path: *const u8) -> isize { + let Some(path) = (unsafe { read_user_path(path) }) else { + return -EFAULT; + }; + unsafe { + if vfs_ref().resolve_executable_path(&path).is_some() { + 0 + } else { + -ENOENT + } + } +} + +pub fn chdir_user_path(path: *const u8) -> isize { + let Some(path) = (unsafe { read_user_path(path) }) else { + return -EFAULT; + }; + unsafe { vfs_mut().chdir_path(&path) } +} + +pub fn mkdir_user_path(path: *const u8) -> isize { + let Some(path) = (unsafe { read_user_path(path) }) else { + return -EFAULT; + }; + create_directory_path(&path) +} + +pub fn mkdirat_user_path(dirfd: isize, path: *const u8) -> isize { + if unsafe { path_starts_with_slash(path) } || dirfd == AT_FDCWD { + return mkdir_user_path(path); + } + + let base = match unsafe { vfs_ref().fd_dir_path(dirfd as usize) } { + Ok(path) => path, + Err(error) => return error, + }; + let Some(path) = (unsafe { read_user_path_relative(&base, path) }) else { + return -EFAULT; + }; + create_directory_path(&path) +} + +pub fn touch_user_path(dirfd: isize, path: *const u8) -> isize { + let resolved = if unsafe { path_starts_with_slash(path) } || dirfd == AT_FDCWD { + unsafe { read_user_path(path) } + } else { + let base = match unsafe { vfs_ref().fd_dir_path(dirfd as usize) } { + Ok(path) => path, + Err(error) => return error, + }; + unsafe { read_user_path_relative(&base, path) } + }; + let Some(path) = resolved else { + return -EFAULT; + }; + create_regular_path(&path) +} + +fn create_directory_path(path: &[u8]) -> isize { + if unsafe { vfs_ref().find(path).is_some() } { + return 0; + } + match crate::fs::ext4::create_dir(path) { + Ok(()) => unsafe { vfs_mut().create_path(path, NodeKind::Directory) }, + Err(_) => -EIO, + } +} + +fn create_regular_path(path: &[u8]) -> isize { + if unsafe { vfs_ref().find(path).is_some() } { + return 0; + } + match crate::fs::ext4::create_file(path) { + Ok(()) => unsafe { vfs_mut().create_path(path, NodeKind::Regular) }, + Err(_) => -EIO, + } +} + +pub fn getcwd(buffer: *mut u8, len: usize) -> isize { + unsafe { vfs_ref().getcwd(buffer, len) } +} + +pub fn file_data_user_path(path: *const u8) -> Option<(*const u8, usize)> { + let path = unsafe { read_user_path(path) }?; + unsafe { + let index = vfs_ref().resolve_executable_path(&path)?; + let node = &vfs_ref().nodes[index]; + if node.kind != NodeKind::Regular { + return None; + } + Some((node.data, node.len)) + } +} + +unsafe fn vfs_mut() -> &'static mut Vfs { + unsafe { &mut *(&raw mut VFS) } +} + +unsafe fn vfs_ref() -> &'static Vfs { + unsafe { &*(&raw const VFS) } +} + +unsafe fn read_user_path(path: *const u8) -> Option<[u8; MAX_PATH]> { + if path.is_null() { + return None; + } + + let first = unsafe { path.read() }; + + if first == 0 { + return None; + } + if first != b'/' { + return unsafe { read_user_path_relative(&vfs_ref().cwd, path) }; + } + unsafe { normalize_user_path(path, None) } +} + +unsafe fn read_user_path_relative(base: &[u8], path: *const u8) -> Option<[u8; MAX_PATH]> { + if path.is_null() { + return None; + } + if unsafe { path_starts_with_slash(path) } { + return unsafe { normalize_user_path(path, None) }; + } + + unsafe { normalize_user_path(path, Some(base)) } +} + +unsafe fn normalize_user_path(path: *const u8, base: Option<&[u8]>) -> Option<[u8; MAX_PATH]> { + let mut buffer = [0u8; MAX_PATH]; + let mut output_index = 0usize; + + if let Some(base) = base { + let base_len = nul_len(base); + if base_len == 0 { + return None; + } + for byte in &base[..base_len] { + if output_index >= MAX_PATH - 1 { + return None; + } + buffer[output_index] = *byte; + output_index += 1; + } + } else { + buffer[output_index] = b'/'; + output_index += 1; + } + + let mut input_index = 0; + loop { + while unsafe { path.add(input_index).read() } == b'/' { + input_index += 1; + } + + let component_start = input_index; + while { + let byte = unsafe { path.add(input_index).read() }; + byte != 0 && byte != b'/' + } { + input_index += 1; + } + let component_len = input_index - component_start; + + if component_len == 0 { + if output_index == 0 { + buffer[0] = b'/'; + } + return Some(buffer); + } + + if component_len == 1 && unsafe { path.add(component_start).read() } == b'.' { + continue; + } + + if component_len == 2 + && unsafe { path.add(component_start).read() } == b'.' + && unsafe { path.add(component_start + 1).read() } == b'.' + { + pop_path_component(&mut buffer, &mut output_index); + continue; + } + + if output_index == 0 { + buffer[output_index] = b'/'; + output_index += 1; + } + if !(output_index == 1 && buffer[0] == b'/') { + if output_index >= MAX_PATH - 1 { + return None; + } + buffer[output_index] = b'/'; + output_index += 1; + } + if output_index + component_len >= MAX_PATH { + return None; + } + for index in 0..component_len { + buffer[output_index + index] = unsafe { path.add(component_start + index).read() }; + } + output_index += component_len; + } +} + +fn pop_path_component(buffer: &mut [u8; MAX_PATH], output_index: &mut usize) { + if *output_index <= 1 { + buffer[0] = b'/'; + for byte in &mut buffer[1..] { + *byte = 0; + } + *output_index = 1; + return; + } + + let mut index = *output_index; + while index > 1 && buffer[index - 1] == b'/' { + index -= 1; + } + while index > 1 && buffer[index - 1] != b'/' { + index -= 1; + } + if index <= 1 { + buffer[0] = b'/'; + index = 1; + } else { + index -= 1; + } + for byte in &mut buffer[index..*output_index] { + *byte = 0; + } + *output_index = index; +} + +unsafe fn path_starts_with_slash(path: *const u8) -> bool { + if path.is_null() { + return false; + } + unsafe { path.read() == b'/' } +} + +fn compare_path(left: &[u8], right: &[u8]) -> core::cmp::Ordering { + let mut index = 0; + loop { + let left_byte = byte_at(left, index); + let right_byte = byte_at(right, index); + match left_byte.cmp(&right_byte) { + core::cmp::Ordering::Equal => { + if left_byte == 0 { + return core::cmp::Ordering::Equal; + } + } + ordering => return ordering, + } + index += 1; + } +} + +fn byte_at(path: &[u8], index: usize) -> u8 { + path.get(index).copied().unwrap_or(0) +} + +fn is_busybox_applet_path(path: &[u8]) -> bool { + let path_len = nul_len(path); + if path_len == 0 { + return false; + } + + let name = if path_len > b"/bin/".len() && &path[..b"/bin/".len()] == b"/bin/" { + &path[b"/bin/".len()..path_len] + } else if path_len > b"/usr/bin/".len() && &path[..b"/usr/bin/".len()] == b"/usr/bin/" { + &path[b"/usr/bin/".len()..path_len] + } else { + return false; + }; + + !name.is_empty() && !name.contains(&b'/') +} + +fn align_up(value: usize, align: usize) -> usize { + (value + align - 1) & !(align - 1) +} + +fn dirent_type(kind: NodeKind) -> u8 { + match kind { + NodeKind::Directory => 4, + NodeKind::Regular => 8, + NodeKind::CharDevice => 2, + } +} + +fn write_dirent( + buffer: *mut u8, + buffer_len: usize, + written: &mut usize, + ino: u64, + kind: NodeKind, + name: &[u8], +) -> bool { + let reclen = align_up(19 + name.len() + 1, 8); + if reclen > u16::MAX as usize || *written + reclen > buffer_len { + return false; + } + + unsafe { + let entry = buffer.add(*written); + (entry as *mut u64).write(ino); + (entry.add(8) as *mut i64).write(ino as i64); + (entry.add(16) as *mut u16).write(reclen as u16); + entry.add(18).write(dirent_type(kind)); + core::ptr::copy_nonoverlapping(name.as_ptr(), entry.add(19), name.len()); + entry.add(19 + name.len()).write(0); + if reclen > 20 + name.len() { + core::ptr::write_bytes(entry.add(20 + name.len()), 0, reclen - 20 - name.len()); + } + } + + *written += reclen; + true +} + +fn child_name<'a>(parent: &[u8], child: &'a [u8]) -> Option<&'a [u8]> { + if compare_path(parent, child).is_eq() { + return None; + } + let parent_len = nul_len(parent); + let child_len = nul_len(child); + if parent_len == 1 && parent[0] == b'/' { + let name = &child[1..child_len]; + if !name.is_empty() && !name.contains(&b'/') { + return Some(name); + } + return None; + } + if child_len <= parent_len + 1 + || &child[..parent_len] != &parent[..parent_len] + || child[parent_len] != b'/' + { + return None; + } + let name = &child[parent_len + 1..child_len]; + if !name.is_empty() && !name.contains(&b'/') { + Some(name) + } else { + None + } +} + +fn nul_len(path: &[u8]) -> usize { + path.iter() + .position(|byte| *byte == 0) + .unwrap_or(path.len()) +} + +fn write_stat(stat: *mut u8, node: VfsNode) -> isize { + unsafe { + core::ptr::write_bytes(stat, 0, 144); + let mode = match node.kind { + NodeKind::Directory => S_IFDIR | 0o755, + NodeKind::Regular => S_IFREG | 0o644, + NodeKind::CharDevice => S_IFCHR | 0o600, + }; + (stat.add(24) as *mut u32).write(mode); + (stat.add(48) as *mut u64).write(node.len as u64); + (stat.add(56) as *mut u64).write(4096); + (stat.add(64) as *mut u64).write(node.len.div_ceil(512) as u64); + } + 0 +} diff --git a/src/gdt.rs b/src/gdt.rs new file mode 100644 index 0000000..274ebad --- /dev/null +++ b/src/gdt.rs @@ -0,0 +1,127 @@ +use core::arch::asm; + +pub const KERNEL_CODE_SELECTOR: u16 = 0x08; +pub const KERNEL_DATA_SELECTOR: u16 = 0x10; +pub const SYSCALL_USER_SELECTOR_BASE: u16 = 0x18; +pub const USER_DATA_SELECTOR: u16 = 0x20 | 3; +pub const USER_CODE_SELECTOR: u16 = 0x28 | 3; +const TSS_SELECTOR: u16 = 0x30; +const KERNEL_STACK_SIZE: usize = 16 * 4096; +const DOUBLE_FAULT_STACK_SIZE: usize = 16 * 4096; + +#[repr(C, align(8))] +#[derive(Clone, Copy)] +struct Gdt { + entries: [u64; 8], +} + +#[repr(C, packed)] +struct GdtPointer { + limit: u16, + base: u64, +} + +static GDT: Gdt = Gdt { + entries: [ + 0, + // 64-bit kernel code: present, ring 0, executable/readable, long mode. + 0x00af_9a00_0000_ffff, + // Kernel data: present, ring 0, writable. + 0x00cf_9200_0000_ffff, + // Sysret compatibility slot. In long mode this is unused, but STAR expects it. + 0x00cf_f200_0000_ffff, + // User data: present, ring 3, writable. + 0x00cf_f200_0000_ffff, + // 64-bit user code: present, ring 3, executable/readable, long mode. + 0x00af_fa00_0000_ffff, + 0, + 0, + ], +}; + +#[repr(C, packed)] +struct TaskStateSegment { + reserved0: u32, + rsp: [u64; 3], + reserved1: u64, + ist: [u64; 7], + reserved2: u64, + reserved3: u16, + io_map_base: u16, +} + +static mut TSS: TaskStateSegment = TaskStateSegment { + reserved0: 0, + rsp: [0; 3], + reserved1: 0, + ist: [0; 7], + reserved2: 0, + reserved3: 0, + io_map_base: core::mem::size_of::() as u16, +}; + +#[repr(C, align(16))] +struct KernelStack([u8; KERNEL_STACK_SIZE]); + +#[repr(C, align(16))] +struct DoubleFaultStack([u8; DOUBLE_FAULT_STACK_SIZE]); + +static mut KERNEL_STACK: KernelStack = KernelStack([0; KERNEL_STACK_SIZE]); +static mut DOUBLE_FAULT_STACK: DoubleFaultStack = DoubleFaultStack([0; DOUBLE_FAULT_STACK_SIZE]); +static mut GDT_STORAGE: Gdt = GDT; + +pub unsafe fn init() { + unsafe { + let stack_base = (&raw const KERNEL_STACK).cast::() as u64; + TSS.rsp[0] = stack_base + KERNEL_STACK_SIZE as u64; + + let double_fault_stack_base = (&raw const DOUBLE_FAULT_STACK).cast::() as u64; + TSS.ist[0] = double_fault_stack_base + DOUBLE_FAULT_STACK_SIZE as u64; + + install_tss_descriptor(); + } + + let gdt_ptr = GdtPointer { + limit: (core::mem::size_of::() - 1) as u16, + base: (&raw const GDT_STORAGE).cast::() as u64, + }; + + unsafe { + asm!( + "lgdt [{gdt_ptr}]", + "push {code}", + "lea rax, [rip + 2f]", + "push rax", + "retfq", + "2:", + "mov ax, {data:x}", + "mov ds, ax", + "mov es, ax", + "mov ss, ax", + "ltr {tss:x}", + gdt_ptr = in(reg) &gdt_ptr, + code = in(reg) KERNEL_CODE_SELECTOR as u64, + data = in(reg) KERNEL_DATA_SELECTOR, + tss = in(reg) TSS_SELECTOR, + out("rax") _, + options(preserves_flags), + ); + } +} + +unsafe fn install_tss_descriptor() { + let base = (&raw const TSS).cast::() as u64; + let limit = (core::mem::size_of::() - 1) as u64; + + let low = (limit & 0xffff) + | ((base & 0x00ff_ffff) << 16) + | (0x89 << 40) + | ((limit & 0x000f_0000) << 32) + | ((base & 0xff00_0000) << 32); + let high = base >> 32; + + unsafe { + GDT_STORAGE.entries[6] = low; + GDT_STORAGE.entries[7] = high; + } +} diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..41e9ba9 --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,718 @@ +use core::arch::{asm, global_asm}; + +use crate::{console, memory::PhysicalMemoryManager, syscall::TrapFrame, task}; + +const IDT_ENTRY_COUNT: usize = 256; +const INTERRUPT_GATE: u8 = 0x8e; +const USER_INTERRUPT_GATE: u8 = 0xee; +const DOUBLE_FAULT_IST: u8 = 1; + +global_asm!( + r#" + .macro EXC_NOERR vector + .global zeroos_exception_\vector +zeroos_exception_\vector: + push 0 + push \vector + jmp zeroos_exception_common + .endm + + .macro EXC_ERR vector + .global zeroos_exception_\vector +zeroos_exception_\vector: + push \vector + jmp zeroos_exception_common + .endm + + EXC_NOERR 0 + EXC_NOERR 1 + EXC_NOERR 2 + EXC_NOERR 3 + EXC_NOERR 4 + EXC_NOERR 5 + EXC_NOERR 6 + EXC_NOERR 7 + EXC_ERR 8 + EXC_NOERR 9 + EXC_ERR 10 + EXC_ERR 11 + EXC_ERR 12 + EXC_ERR 13 + EXC_ERR 14 + EXC_NOERR 15 + EXC_NOERR 16 + EXC_ERR 17 + EXC_NOERR 18 + EXC_NOERR 19 + EXC_NOERR 20 + EXC_ERR 21 + EXC_NOERR 22 + EXC_NOERR 23 + EXC_NOERR 24 + EXC_NOERR 25 + EXC_NOERR 26 + EXC_NOERR 27 + EXC_NOERR 28 + EXC_ERR 29 + EXC_ERR 30 + EXC_NOERR 31 + +zeroos_exception_common: + cld + push rax + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + mov rcx, rsp + sub rsp, 40 + call zeroos_exception_handler + add rsp, 40 + test rax, rax + jnz zeroos_exception_return + mov rcx, [rsp + 120] + mov rdx, [rsp + 128] + mov r8, [rsp + 136] + mov r9, [rsp + 144] + mov rax, [rsp + 152] + mov rbx, [rsp + 160] + sub rsp, 56 + mov [rsp + 32], rax + mov [rsp + 40], rbx + call zeroos_exception_panic + add rsp, 56 +1: + cli + hlt + jmp 1b + +zeroos_exception_return: + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + add rsp, 16 + iretq + + .global zeroos_isr_ignore +zeroos_isr_ignore: + iretq + + .macro IRQ_IGNORE irq + .global zeroos_irq_ignore_\irq +zeroos_irq_ignore_\irq: + cld + push rax + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + mov rcx, \irq + sub rsp, 40 + call zeroos_irq_ignore_handler + add rsp, 40 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + iretq + .endm + + IRQ_IGNORE 0 + IRQ_IGNORE 2 + IRQ_IGNORE 3 + IRQ_IGNORE 4 + IRQ_IGNORE 5 + IRQ_IGNORE 6 + IRQ_IGNORE 8 + IRQ_IGNORE 9 + IRQ_IGNORE 10 + IRQ_IGNORE 11 + IRQ_IGNORE 12 + IRQ_IGNORE 13 + IRQ_IGNORE 14 + + .global zeroos_irq0_timer +zeroos_irq0_timer: + cld + push rax + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + sub rsp, 40 + call zeroos_timer_interrupt + add rsp, 40 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + iretq + + .global zeroos_irq1_keyboard +zeroos_irq1_keyboard: + cld + push rax + push rcx + push rdx + push rbx + push rbp + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + sub rsp, 40 + call zeroos_ps2_keyboard_interrupt + add rsp, 40 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + iretq + + .global zeroos_int80_syscall +zeroos_int80_syscall: + cld + push rbx + push rbp + push rcx + push rdx + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + sub rsp, 72 + mov [rsp + 32], r10 + mov [rsp + 40], r8 + mov [rsp + 48], r9 + mov r9, rdx + mov r8, rsi + mov rdx, rdi + mov rcx, rax + call zeroos_int80_syscall_dispatch + add rsp, 72 + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + pop rdx + pop rcx + pop rbp + pop rbx + iretq +"# +); + +unsafe extern "C" { + fn zeroos_exception_0(); + fn zeroos_exception_1(); + fn zeroos_exception_2(); + fn zeroos_exception_3(); + fn zeroos_exception_4(); + fn zeroos_exception_5(); + fn zeroos_exception_6(); + fn zeroos_exception_7(); + fn zeroos_exception_8(); + fn zeroos_exception_9(); + fn zeroos_exception_10(); + fn zeroos_exception_11(); + fn zeroos_exception_12(); + fn zeroos_exception_13(); + fn zeroos_exception_14(); + fn zeroos_exception_15(); + fn zeroos_exception_16(); + fn zeroos_exception_17(); + fn zeroos_exception_18(); + fn zeroos_exception_19(); + fn zeroos_exception_20(); + fn zeroos_exception_21(); + fn zeroos_exception_22(); + fn zeroos_exception_23(); + fn zeroos_exception_24(); + fn zeroos_exception_25(); + fn zeroos_exception_26(); + fn zeroos_exception_27(); + fn zeroos_exception_28(); + fn zeroos_exception_29(); + fn zeroos_exception_30(); + fn zeroos_exception_31(); + fn zeroos_isr_ignore(); + fn zeroos_irq_ignore_0(); + fn zeroos_irq_ignore_2(); + fn zeroos_irq_ignore_3(); + fn zeroos_irq_ignore_4(); + fn zeroos_irq_ignore_5(); + fn zeroos_irq_ignore_6(); + fn zeroos_irq_ignore_8(); + fn zeroos_irq_ignore_9(); + fn zeroos_irq_ignore_10(); + fn zeroos_irq_ignore_11(); + fn zeroos_irq_ignore_12(); + fn zeroos_irq_ignore_13(); + fn zeroos_irq_ignore_14(); + fn zeroos_irq0_timer(); + fn zeroos_irq1_keyboard(); + fn zeroos_int80_syscall(); +} + +#[repr(C, packed)] +#[derive(Clone, Copy)] +struct IdtEntry { + offset_low: u16, + selector: u16, + ist: u8, + options: u8, + offset_mid: u16, + offset_high: u32, + reserved: u32, +} + +impl IdtEntry { + const MISSING: Self = Self { + offset_low: 0, + selector: 0, + ist: 0, + options: 0, + offset_mid: 0, + offset_high: 0, + reserved: 0, + }; + + fn set(&mut self, handler: unsafe extern "C" fn(), selector: u16) { + self.set_with_options(handler, selector, INTERRUPT_GATE); + } + + fn set_user(&mut self, handler: unsafe extern "C" fn(), selector: u16) { + self.set_with_options(handler, selector, USER_INTERRUPT_GATE); + } + + fn set_with_options(&mut self, handler: unsafe extern "C" fn(), selector: u16, options: u8) { + self.set_with_ist(handler, selector, options, 0); + } + + fn set_with_ist( + &mut self, + handler: unsafe extern "C" fn(), + selector: u16, + options: u8, + ist: u8, + ) { + let addr = handler as usize as u64; + self.offset_low = addr as u16; + self.selector = selector; + self.ist = ist & 0x7; + self.options = options; + self.offset_mid = (addr >> 16) as u16; + self.offset_high = (addr >> 32) as u32; + self.reserved = 0; + } +} + +#[repr(C, packed)] +struct IdtPointer { + limit: u16, + base: u64, +} + +#[repr(C)] +pub struct ExceptionFrame { + r15: usize, + r14: usize, + r13: usize, + r12: usize, + r11: usize, + r10: usize, + r9: usize, + r8: usize, + rdi: usize, + rsi: usize, + rbp: usize, + rbx: usize, + rdx: usize, + rcx: usize, + rax: usize, + vector: usize, + error: usize, + rip: usize, + cs: usize, + rflags: usize, + rsp: usize, + ss: usize, +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_exception_handler(frame: *mut ExceptionFrame) -> usize { + if frame.is_null() { + return 0; + } + + let frame = unsafe { &mut *frame }; + if frame.cs & 3 != 3 { + return 0; + } + + let recoverable_vfork_child = task::is_current_vfork_child(); + if !recoverable_vfork_child { + console::write(b"user exception "); + write_hex(frame.vector as u64); + console::write(b" rip="); + write_hex(frame.rip as u64); + console::write(b" rsp="); + write_hex(frame.rsp as u64); + console::write(b" rax="); + write_hex(frame.rax as u64); + console::write(b" bytes="); + if crate::syscall::is_user_mapped(frame.rip, 8) { + unsafe { + let rip = frame.rip as *const u8; + for index in 0..8 { + write_hex_byte(rip.add(index).read()); + } + } + } else { + console::write(b""); + } + console::write(b", killing task\n"); + } + + let mut trap = TrapFrame { + rax: frame.rax, + rcx: frame.rcx, + rdx: frame.rdx, + rbx: frame.rbx, + rbp: frame.rbp, + rsi: frame.rsi, + rdi: frame.rdi, + r8: frame.r8, + r9: frame.r9, + r10: frame.r10, + r11: frame.r11, + r12: frame.r12, + r13: frame.r13, + r14: frame.r14, + r15: frame.r15, + rip: frame.rip, + rflags: frame.rflags, + rsp: frame.rsp, + }; + + if !task::exit_current(128 + frame.vector as isize, &mut trap) { + loop { + unsafe { + asm!("sti; hlt", options(nomem, nostack, preserves_flags)); + } + } + } + crate::syscall::restore_user_heap(); + + frame.rax = trap.rax; + frame.rcx = trap.rcx; + frame.rdx = trap.rdx; + frame.rbx = trap.rbx; + frame.rbp = trap.rbp; + frame.rsi = trap.rsi; + frame.rdi = trap.rdi; + frame.r8 = trap.r8; + frame.r9 = trap.r9; + frame.r10 = trap.r10; + frame.r11 = trap.r11; + frame.r12 = trap.r12; + frame.r13 = trap.r13; + frame.r14 = trap.r14; + frame.r15 = trap.r15; + frame.rip = trap.rip; + frame.rflags = trap.rflags; + frame.rsp = trap.rsp; + 1 +} + +pub unsafe fn init(memory: &mut PhysicalMemoryManager) -> Option<()> { + let page = memory.alloc_page()?; + let idt = page as *mut [IdtEntry; IDT_ENTRY_COUNT]; + unsafe { + (*idt).fill(IdtEntry::MISSING); + } + + let selector = current_code_segment(); + for vector in 0..IDT_ENTRY_COUNT { + unsafe { + (*idt)[vector].set(zeroos_isr_ignore, selector); + } + } + + let exceptions: [unsafe extern "C" fn(); 32] = [ + zeroos_exception_0, + zeroos_exception_1, + zeroos_exception_2, + zeroos_exception_3, + zeroos_exception_4, + zeroos_exception_5, + zeroos_exception_6, + zeroos_exception_7, + zeroos_exception_8, + zeroos_exception_9, + zeroos_exception_10, + zeroos_exception_11, + zeroos_exception_12, + zeroos_exception_13, + zeroos_exception_14, + zeroos_exception_15, + zeroos_exception_16, + zeroos_exception_17, + zeroos_exception_18, + zeroos_exception_19, + zeroos_exception_20, + zeroos_exception_21, + zeroos_exception_22, + zeroos_exception_23, + zeroos_exception_24, + zeroos_exception_25, + zeroos_exception_26, + zeroos_exception_27, + zeroos_exception_28, + zeroos_exception_29, + zeroos_exception_30, + zeroos_exception_31, + ]; + + for (vector, handler) in exceptions.iter().enumerate() { + unsafe { + (*idt)[vector].set(*handler, selector); + } + } + + unsafe { + // Vector 15 is reserved on x86. In practice, unexpected legacy/spurious + // interrupts can show up here while bringing interrupt controllers up. + (*idt)[0x0f].set(zeroos_isr_ignore, selector); + (*idt)[8].set_with_ist( + zeroos_exception_8, + selector, + INTERRUPT_GATE, + DOUBLE_FAULT_IST, + ); + // Remapped PIC spurious IRQ7/IRQ15. + (*idt)[0x27].set(zeroos_isr_ignore, selector); + (*idt)[0x2f].set(zeroos_isr_ignore, selector); + } + + let irq_ignore_handlers: [(usize, unsafe extern "C" fn()); 13] = [ + (0x20, zeroos_irq_ignore_0), + (0x22, zeroos_irq_ignore_2), + (0x23, zeroos_irq_ignore_3), + (0x24, zeroos_irq_ignore_4), + (0x25, zeroos_irq_ignore_5), + (0x26, zeroos_irq_ignore_6), + (0x28, zeroos_irq_ignore_8), + (0x29, zeroos_irq_ignore_9), + (0x2a, zeroos_irq_ignore_10), + (0x2b, zeroos_irq_ignore_11), + (0x2c, zeroos_irq_ignore_12), + (0x2d, zeroos_irq_ignore_13), + (0x2e, zeroos_irq_ignore_14), + ]; + + unsafe { + for (vector, handler) in irq_ignore_handlers { + (*idt)[vector].set(handler, selector); + } + (*idt)[0x27].set(zeroos_isr_ignore, selector); + (*idt)[0x2f].set(zeroos_isr_ignore, selector); + (*idt)[0x20].set(zeroos_irq0_timer, selector); + (*idt)[0x21].set(zeroos_irq1_keyboard, selector); + (*idt)[0x80].set_user(zeroos_int80_syscall, selector); + } + + let idt_ptr = IdtPointer { + limit: (core::mem::size_of::() * IDT_ENTRY_COUNT - 1) as u16, + base: idt as u64, + }; + + unsafe { + asm!("lidt [{}]", in(reg) &idt_ptr, options(readonly, nostack, preserves_flags)); + } + + Some(()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_irq_ignore_handler(irq: u64) { + unsafe { + crate::drivers::platform::pic::end_of_interrupt(irq as u8); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_timer_interrupt() { + crate::drivers::platform::timer::tick(); + unsafe { + crate::drivers::platform::pic::end_of_interrupt(crate::drivers::platform::pic::TIMER_IRQ); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_exception_panic( + vector: u64, + error_code: u64, + rip: u64, + cs: u64, + rflags: u64, + rsp: u64, +) -> ! { + console::write_panic(b"\r\nKernel panic - not syncing: CPU exception\r\n"); + console::write_panic(b"vector: "); + write_hex(vector); + console::write_panic(b" error: "); + write_hex(error_code); + console::write_panic(b"\r\nrip: "); + write_hex(rip); + console::write_panic(b" cs: "); + write_hex(cs); + console::write_panic(b"\r\nrflags: "); + write_hex(rflags); + console::write_panic(b"\r\nrsp: "); + write_hex(rsp); + console::write_panic(b"\r\n"); + + loop { + unsafe { + asm!("cli", "hlt", options(nomem, nostack, preserves_flags)); + } + } +} + +fn write_hex(value: u64) { + console::write_panic(b"0x"); + + for index in 0..16 { + let shift = (15 - index) * 4; + let digit = ((value >> shift) & 0xf) as u8; + write_hex_digit(digit); + } +} + +fn write_hex_digit(digit: u8) { + console::write_panic(match digit { + 0 => b"0", + 1 => b"1", + 2 => b"2", + 3 => b"3", + 4 => b"4", + 5 => b"5", + 6 => b"6", + 7 => b"7", + 8 => b"8", + 9 => b"9", + 10 => b"a", + 11 => b"b", + 12 => b"c", + 13 => b"d", + 14 => b"e", + _ => b"f", + }); +} + +fn write_hex_byte(value: u8) { + write_hex_digit(value >> 4); + write_hex_digit(value & 0x0f); +} + +fn current_code_segment() -> u16 { + let cs: u16; + unsafe { + asm!("mov {0:x}, cs", out(reg) cs, options(nomem, nostack, preserves_flags)); + } + cs +} diff --git a/src/io.rs b/src/io.rs new file mode 100644 index 0000000..a7db439 --- /dev/null +++ b/src/io.rs @@ -0,0 +1,66 @@ +use core::arch::asm; + +pub unsafe fn inb(port: u16) -> u8 { + let value: u8; + unsafe { + asm!("in al, dx", out("al") value, in("dx") port, options(nomem, nostack, preserves_flags)); + } + value +} + +pub unsafe fn outb(port: u16, value: u8) { + unsafe { + asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); + } +} + +pub unsafe fn inl(port: u16) -> u32 { + let value: u32; + unsafe { + asm!("in eax, dx", out("eax") value, in("dx") port, options(nomem, nostack, preserves_flags)); + } + value +} + +pub unsafe fn outl(port: u16, value: u32) { + unsafe { + asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); + } +} + +pub fn enable_interrupts() { + unsafe { + asm!("sti", options(nomem, nostack, preserves_flags)); + } +} + +pub fn disable_interrupts() { + unsafe { + asm!("cli", options(nomem, nostack, preserves_flags)); + } +} + +pub fn save_flags_and_disable_interrupts() -> u64 { + let flags: u64; + unsafe { + asm!( + "pushfq", + "pop {}", + "cli", + out(reg) flags, + options(nomem, preserves_flags), + ); + } + flags +} + +pub fn restore_flags(flags: u64) { + unsafe { + asm!( + "push {}", + "popfq", + in(reg) flags, + options(nomem, preserves_flags), + ); + } +} diff --git a/src/ipc.rs b/src/ipc.rs new file mode 100644 index 0000000..d3a12c7 --- /dev/null +++ b/src/ipc.rs @@ -0,0 +1,430 @@ +#![allow(dead_code)] + +use crate::task; + +pub const IPC_FD_BASE: usize = 256; +pub const MAX_FDS: usize = 512; + +const MAX_ENDPOINTS: usize = 256; +const IPC_BUFFER_SIZE: usize = 4096; + +const EAFNOSUPPORT: isize = 97; +const EAGAIN: isize = 11; +const EBADF: isize = 9; +const EFAULT: isize = 14; +const EINVAL: isize = 22; +const EMFILE: isize = 24; +const ENFILE: isize = 23; +const ENOTCONN: isize = 107; +const EOPNOTSUPP: isize = 95; +const EPIPE: isize = 32; + +const AF_UNIX: usize = 1; +const SOCK_STREAM: usize = 1; +const SOCK_DGRAM: usize = 2; +const SOCK_NONBLOCK: usize = 0o4000; +const SOCK_CLOEXEC: usize = 0o2000000; + +const POLLIN: i16 = 0x0001; +const POLLOUT: i16 = 0x0004; +const POLLERR: i16 = 0x0008; +const POLLHUP: i16 = 0x0010; + +const S_IFIFO: u32 = 0o010000; +const S_IFSOCK: u32 = 0o140000; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum EndpointKind { + Empty, + PipeRead, + PipeWrite, + UnixSocket, +} + +#[derive(Clone, Copy)] +struct Endpoint { + kind: EndpointKind, + owner_pid: usize, + peer: Option, + refs: usize, + buffer: [u8; IPC_BUFFER_SIZE], + read_pos: usize, + write_pos: usize, + len: usize, + closed: bool, + peer_closed: bool, +} + +impl Endpoint { + const fn empty() -> Self { + Self { + kind: EndpointKind::Empty, + owner_pid: 0, + peer: None, + refs: 0, + buffer: [0; IPC_BUFFER_SIZE], + read_pos: 0, + write_pos: 0, + len: 0, + closed: false, + peer_closed: false, + } + } + + fn is_used(&self) -> bool { + self.kind != EndpointKind::Empty + } +} + +#[derive(Clone, Copy)] +struct FileHandle { + endpoint: Option, +} + +impl FileHandle { + const fn empty() -> Self { + Self { endpoint: None } + } +} + +#[derive(Clone, Copy)] +pub struct Message { + pub sender: usize, + pub opcode: u16, + pub args: [usize; 6], +} + +static mut ENDPOINTS: [Endpoint; MAX_ENDPOINTS] = [Endpoint::empty(); MAX_ENDPOINTS]; +static mut FDS: [FileHandle; MAX_FDS] = [FileHandle::empty(); MAX_FDS]; + +pub unsafe fn init() { + unsafe { + let mut index = 0; + while index < MAX_ENDPOINTS { + ENDPOINTS[index] = Endpoint::empty(); + index += 1; + } + let mut fd = 0; + while fd < MAX_FDS { + FDS[fd] = FileHandle::empty(); + fd += 1; + } + } +} + +pub fn pipe(pipefd: *mut i32) -> isize { + if pipefd.is_null() { + return -EFAULT; + } + + unsafe { + let Some(read_endpoint) = alloc_endpoint(EndpointKind::PipeRead) else { + return -ENFILE; + }; + let Some(write_endpoint) = alloc_endpoint(EndpointKind::PipeWrite) else { + free_endpoint(read_endpoint); + return -ENFILE; + }; + ENDPOINTS[read_endpoint].peer = Some(write_endpoint); + ENDPOINTS[write_endpoint].peer = Some(read_endpoint); + + let Some(read_fd) = alloc_fd_for_endpoint(read_endpoint, IPC_FD_BASE) else { + free_endpoint(write_endpoint); + free_endpoint(read_endpoint); + return -EMFILE; + }; + let Some(write_fd) = alloc_fd_for_endpoint(write_endpoint, IPC_FD_BASE) else { + release_fd(read_fd); + free_endpoint(write_endpoint); + free_endpoint(read_endpoint); + return -EMFILE; + }; + + pipefd.write(read_fd as i32); + pipefd.add(1).write(write_fd as i32); + 0 + } +} + +pub fn socketpair(domain: usize, socket_type: usize, protocol: usize, sv: *mut i32) -> isize { + if sv.is_null() { + return -EFAULT; + } + if domain != AF_UNIX { + return -EAFNOSUPPORT; + } + if protocol != 0 { + return -EOPNOTSUPP; + } + + let base_type = socket_type & !(SOCK_NONBLOCK | SOCK_CLOEXEC); + if base_type != SOCK_STREAM && base_type != SOCK_DGRAM { + return -EOPNOTSUPP; + } + + unsafe { + let Some(left_endpoint) = alloc_endpoint(EndpointKind::UnixSocket) else { + return -ENFILE; + }; + let Some(right_endpoint) = alloc_endpoint(EndpointKind::UnixSocket) else { + free_endpoint(left_endpoint); + return -ENFILE; + }; + ENDPOINTS[left_endpoint].peer = Some(right_endpoint); + ENDPOINTS[right_endpoint].peer = Some(left_endpoint); + + let Some(left_fd) = alloc_fd_for_endpoint(left_endpoint, IPC_FD_BASE) else { + free_endpoint(right_endpoint); + free_endpoint(left_endpoint); + return -EMFILE; + }; + let Some(right_fd) = alloc_fd_for_endpoint(right_endpoint, IPC_FD_BASE) else { + release_fd(left_fd); + free_endpoint(right_endpoint); + free_endpoint(left_endpoint); + return -EMFILE; + }; + + sv.write(left_fd as i32); + sv.add(1).write(right_fd as i32); + 0 + } +} + +pub fn read(fd: usize, buffer: *mut u8, len: usize) -> Option { + let endpoint_index = endpoint_for_fd(fd)?; + if buffer.is_null() { + return Some(-EFAULT); + } + if len == 0 { + return Some(0); + } + + unsafe { + let endpoint = &mut ENDPOINTS[endpoint_index]; + match endpoint.kind { + EndpointKind::PipeWrite => Some(-EBADF), + EndpointKind::PipeRead | EndpointKind::UnixSocket => { + if endpoint.len == 0 { + return Some(if endpoint.peer_closed { 0 } else { -EAGAIN }); + } + let count = len.min(endpoint.len); + for index in 0..count { + buffer.add(index).write(endpoint.buffer[endpoint.read_pos]); + endpoint.read_pos = (endpoint.read_pos + 1) % IPC_BUFFER_SIZE; + } + endpoint.len -= count; + Some(count as isize) + } + EndpointKind::Empty => Some(-EBADF), + } + } +} + +pub fn write(fd: usize, buffer: *const u8, len: usize) -> Option { + let endpoint_index = endpoint_for_fd(fd)?; + if buffer.is_null() { + return Some(-EFAULT); + } + if len == 0 { + return Some(0); + } + + unsafe { + let endpoint = ENDPOINTS[endpoint_index]; + match endpoint.kind { + EndpointKind::PipeRead => Some(-EBADF), + EndpointKind::PipeWrite | EndpointKind::UnixSocket => { + let Some(peer_index) = endpoint.peer else { + return Some(-ENOTCONN); + }; + let peer = &mut ENDPOINTS[peer_index]; + if peer.closed || endpoint.peer_closed { + return Some(-EPIPE); + } + let space = IPC_BUFFER_SIZE - peer.len; + if space == 0 { + return Some(0); + } + let count = len.min(space); + for index in 0..count { + peer.buffer[peer.write_pos] = buffer.add(index).read(); + peer.write_pos = (peer.write_pos + 1) % IPC_BUFFER_SIZE; + } + peer.len += count; + Some(count as isize) + } + EndpointKind::Empty => Some(-EBADF), + } + } +} + +pub fn close(fd: usize) -> Option { + endpoint_for_fd(fd)?; + unsafe { + release_fd(fd); + } + Some(0) +} + +pub fn dup(fd: usize, min_newfd: usize) -> Option { + let endpoint = endpoint_for_fd(fd)?; + unsafe { + alloc_fd_for_endpoint(endpoint, min_newfd) + .map(|newfd| newfd as isize) + .or(Some(-EMFILE)) + } +} + +pub fn dup2(oldfd: usize, newfd: usize) -> Option { + let endpoint = endpoint_for_fd(oldfd)?; + if newfd >= MAX_FDS { + return Some(-EBADF); + } + if oldfd == newfd { + return Some(newfd as isize); + } + + unsafe { + if FDS[newfd].endpoint.is_some() { + release_fd(newfd); + } + FDS[newfd].endpoint = Some(endpoint); + ENDPOINTS[endpoint].refs += 1; + } + Some(newfd as isize) +} + +pub fn poll(fd: usize, events: i16) -> Option { + let endpoint_index = endpoint_for_fd(fd)?; + unsafe { + let endpoint = ENDPOINTS[endpoint_index]; + let mut revents = 0i16; + if events & POLLIN != 0 && endpoint.len > 0 { + revents |= POLLIN; + } + if events & POLLOUT != 0 { + if let Some(peer_index) = endpoint.peer { + let peer = ENDPOINTS[peer_index]; + if !peer.closed && peer.len < IPC_BUFFER_SIZE { + revents |= POLLOUT; + } + } else { + revents |= POLLERR; + } + } + if endpoint.peer_closed { + revents |= POLLHUP; + } + Some(revents) + } +} + +pub fn fstat(fd: usize, stat: *mut u8) -> Option { + let endpoint_index = endpoint_for_fd(fd)?; + if stat.is_null() { + return Some(-EFAULT); + } + + unsafe { + core::ptr::write_bytes(stat, 0, 128); + let mode = match ENDPOINTS[endpoint_index].kind { + EndpointKind::PipeRead | EndpointKind::PipeWrite => S_IFIFO | 0o600, + EndpointKind::UnixSocket => S_IFSOCK | 0o600, + EndpointKind::Empty => return Some(-EBADF), + }; + (stat.add(24) as *mut u32).write(mode); + } + Some(0) +} + +pub fn is_fd(fd: usize) -> bool { + endpoint_for_fd(fd).is_some() +} + +pub fn socket(_domain: usize, _socket_type: usize, _protocol: usize) -> isize { + -EOPNOTSUPP +} + +pub fn unsupported_socket_op() -> isize { + -EOPNOTSUPP +} + +fn endpoint_for_fd(fd: usize) -> Option { + if fd >= MAX_FDS { + return None; + } + unsafe { FDS[fd].endpoint } +} + +unsafe fn alloc_endpoint(kind: EndpointKind) -> Option { + unsafe { + for index in 0..MAX_ENDPOINTS { + if !ENDPOINTS[index].is_used() { + ENDPOINTS[index] = Endpoint { + kind, + owner_pid: task::current_pid(), + peer: None, + refs: 0, + buffer: [0; IPC_BUFFER_SIZE], + read_pos: 0, + write_pos: 0, + len: 0, + closed: false, + peer_closed: false, + }; + return Some(index); + } + } + } + None +} + +unsafe fn alloc_fd_for_endpoint(endpoint: usize, min_fd: usize) -> Option { + unsafe { + let start = min_fd.min(MAX_FDS); + for fd in start..MAX_FDS { + if FDS[fd].endpoint.is_none() { + FDS[fd].endpoint = Some(endpoint); + ENDPOINTS[endpoint].refs += 1; + return Some(fd); + } + } + } + None +} + +unsafe fn release_fd(fd: usize) { + unsafe { + if fd >= MAX_FDS { + return; + } + let Some(endpoint_index) = FDS[fd].endpoint.take() else { + return; + }; + if ENDPOINTS[endpoint_index].refs > 0 { + ENDPOINTS[endpoint_index].refs -= 1; + } + if ENDPOINTS[endpoint_index].refs == 0 { + close_endpoint(endpoint_index); + } + } +} + +unsafe fn close_endpoint(endpoint_index: usize) { + unsafe { + let peer = ENDPOINTS[endpoint_index].peer; + ENDPOINTS[endpoint_index].closed = true; + if let Some(peer_index) = peer { + ENDPOINTS[peer_index].peer_closed = true; + ENDPOINTS[peer_index].peer = None; + } + free_endpoint(endpoint_index); + } +} + +unsafe fn free_endpoint(endpoint_index: usize) { + unsafe { + ENDPOINTS[endpoint_index] = Endpoint::empty(); + } +} diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..26df237 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,39 @@ +use crate::tty::Tty; + +pub enum Level { + Info, + Ok, +} + +impl Level { + fn as_bytes(&self) -> &'static [u8] { + match self { + Self::Info => b"[ ] ", + Self::Ok => b"[ ok ] ", + } + } +} + +pub struct KernelLogger<'a> { + tty: &'a mut Tty, +} + +impl<'a> KernelLogger<'a> { + pub fn new(tty: &'a mut Tty) -> Self { + Self { tty } + } + + pub fn info(&mut self, message: &[u8]) { + self.write(Level::Info, message); + } + + pub fn ok(&mut self, message: &[u8]) { + self.write(Level::Ok, message); + } + + fn write(&mut self, level: Level, message: &[u8]) { + self.tty.write_bytes(level.as_bytes()); + self.tty.write_bytes(message); + self.tty.write_bytes(b"\r\n"); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c526081 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,346 @@ +#![no_std] +#![no_main] + +mod console; +mod drivers; +mod efi; +mod elf; +mod font; +mod framebuffer; +mod fs; +mod gdt; +mod interrupts; +mod io; +mod ipc; +mod log; +mod memory; +mod paging; +mod services; +mod syscall; +mod task; +mod tty; +mod user; + +use core::panic::PanicInfo; + +use efi::{EFI_SUCCESS, EfiHandle, EfiStatus, EfiSystemTable}; +use framebuffer::Framebuffer; +use memory::PhysicalMemoryManager; +use paging::{AddressSpace, PageFlags}; +use tty::Tty; + +static mut MEMORY_MAP_BUFFER: [u8; 65536] = [0; 65536]; +static mut PHYSICAL_MEMORY: PhysicalMemoryManager = PhysicalMemoryManager::new(); + +#[unsafe(no_mangle)] +extern "efiapi" fn efi_main( + image_handle: EfiHandle, + system_table: *mut EfiSystemTable, +) -> EfiStatus { + unsafe { + io::disable_interrupts(); + + let Some(boot_services) = efi::boot_services(system_table) else { + halt_forever(); + }; + + let Some(gop) = efi::locate_gop(boot_services) else { + halt_forever(); + }; + + let Some(framebuffer) = Framebuffer::from_gop(gop) else { + halt_forever(); + }; + + let mut tty = Tty::new(framebuffer); + tty.clear(); + console::init(&raw mut tty); + let mut logger = log::KernelLogger::new(&mut tty); + logger.info(b"ZeroOS kernel initializing"); + + drivers::platform::pic::remap_and_mask_all(); + logger.ok(b"pic"); + + if let Some(unix_seconds) = efi::read_unix_time(system_table) { + drivers::platform::timer::set_wall_clock_boot_time(unix_seconds); + logger.ok(b"UEFI time"); + } else { + logger.info(b"UEFI time unavailable"); + } + + let Some(memory_map) = efi::get_memory_map( + boot_services, + (&raw mut MEMORY_MAP_BUFFER).cast::(), + 65536, + ) else { + halt_forever(); + }; + logger.ok(b"UEFI memory map"); + + let memory = &mut *(&raw mut PHYSICAL_MEMORY); + task::init(); + memory.init_from_uefi_memory_map(memory_map); + let stats = memory.stats(); + let _ = (stats.total_pages, stats.free_pages, stats.used_pages); + logger.ok(b"physical memory manager"); + + fs::vfs::init(); + logger.ok(b"vfs"); + ipc::init(); + logger.ok(b"ipc"); + + let ahci_disks = drivers::storage::ahci::init(memory); + let mut ext4_user_image = None; + let mut ext4_user_path = None; + if ahci_disks > 0 { + logger.ok(b"ahci"); + if let Some(mut disk) = drivers::storage::ahci::first_disk() { + match drivers::storage::partition::find_linux_partition(&mut disk) { + Ok(partition) => { + let mut root = + drivers::storage::partition::PartitionBlockDevice::new(disk, partition); + match fs::ext4::mount(&mut root) { + Ok(ext4) => { + logger.ok(b"ext4"); + let _ = fs::ext4::mount_vfs_tree(&mut root, &ext4); + fs::ext4::register_root(root, ext4); + match fs::ext4::load_path( + &mut root, + &ext4, + memory, + b"/usr/bin/busybox", + ) { + Ok(image) => { + ext4_user_image = Some(image); + ext4_user_path = Some(b"/usr/bin/busybox" as &'static [u8]); + logger.ok(b"ext4 /usr/bin/busybox"); + } + Err(_) => match fs::ext4::load_path( + &mut root, + &ext4, + memory, + b"/usr/bin/sh", + ) { + Ok(image) => { + ext4_user_image = Some(image); + ext4_user_path = Some(b"/usr/bin/sh" as &'static [u8]); + logger.ok(b"ext4 /usr/bin/sh"); + } + Err(_) => logger.info(b"ext4 userspace not loaded"), + }, + } + } + Err(_) => logger.info(b"ext4 not mounted"), + } + } + Err(_) => logger.info(b"no linux gpt partition"), + } + } + } else { + logger.info(b"no ahci disk"); + } + + let (loaded_user_image, loaded_user_path) = if let Some(image) = ext4_user_image { + (Some(image), ext4_user_path) + } else if let Some(image) = + fs::uefi::load_file(image_handle, boot_services, memory, fs::uefi::BASH_PATH) + { + (Some(image), Some(b"/bin/bash" as &'static [u8])) + } else if let Some(image) = + fs::uefi::load_file(image_handle, boot_services, memory, fs::uefi::SH_PATH) + { + (Some(image), Some(b"/bin/sh" as &'static [u8])) + } else { + (None, None) + }; + let userspace_image_found = loaded_user_image.is_some(); + if loaded_user_image.is_some() { + if let (Some(image), Some(path)) = (&loaded_user_image, loaded_user_path) { + let _ = fs::vfs::mount_static_file(path, image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/usr/bin/busybox", image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/bin/busybox", image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/usr/bin/sh", image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/usr/bin/bash", image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/bin/sh", image.ptr, image.len); + let _ = fs::vfs::mount_static_file(b"/bin/bash", image.ptr, image.len); + } + logger.ok(b"userspace image loaded from esp"); + } else { + logger.info(b"no /bin/bash or /bin/sh on esp, using built-in user program"); + } + + gdt::init(); + logger.ok(b"gdt"); + + if interrupts::init(memory).is_none() { + halt_forever(); + } + logger.ok(b"idt"); + + drivers::platform::timer::init(); + logger.ok(b"pit timer"); + + drivers::input::ps2::init(); + logger.ok(b"ps/2 keyboard"); + + let Some(mut address_space) = AddressSpace::new_kernel(memory) else { + halt_forever(); + }; + logger.ok(b"address space"); + + let mut userspace_program = None; + let user_entry = if let Some(image) = loaded_user_image { + match elf::load_user_elf(&image, memory) { + Some(program) => { + logger.ok(b"elf userspace program"); + userspace_program = Some(user::UserAux { + entry: program.entry, + phdr: program.phdr, + phent: program.phent, + phnum: program.phnum, + }); + Some(program.entry) + } + None => { + logger.info(b"elf load failed, using built-in user program"); + None + } + } + } else { + None + }; + + let Some(test_page) = memory.alloc_page() else { + halt_forever(); + }; + if address_space + .map_page( + memory, + 0xffff_8000_0000_0000, + test_page as u64, + PageFlags::WRITABLE, + ) + .is_none() + { + halt_forever(); + } + let _ = address_space.translate(0xffff_8000_0000_0000); + let _ = address_space.unmap_page(0xffff_8000_0000_0000); + memory.free_page(test_page); + let _ = (address_space.root_table(), address_space.identity_tables()); + logger.ok(b"paging smoke test"); + + let Some(final_memory_map) = efi::get_memory_map( + boot_services, + (&raw mut MEMORY_MAP_BUFFER).cast::(), + 65536, + ) else { + logger.info(b"final UEFI memory map failed"); + halt_forever(); + }; + logger.ok(b"final UEFI memory map"); + + let exit_status = ((*boot_services).exit_boot_services)(image_handle, final_memory_map.key); + if exit_status != EFI_SUCCESS { + logger.info(b"exit boot services failed"); + halt_forever(); + } + logger.ok(b"exit boot services"); + + address_space.activate(); + logger.ok(b"cr3"); + tty.clear(); + if user_entry.is_some() { + if loaded_user_path == Some(b"/usr/bin/busybox" as &'static [u8]) { + console::write(b"enter userspace: /usr/bin/busybox sh\r\n"); + } else if loaded_user_path == Some(b"/usr/bin/sh" as &'static [u8]) { + console::write(b"enter userspace: /usr/bin/sh\r\n"); + } else if loaded_user_path == Some(b"/usr/bin/bash" as &'static [u8]) { + console::write(b"enter userspace: /usr/bin/bash\r\n"); + } else { + console::write(b"enter userspace: /bin/bash\r\n"); + } + } else if userspace_image_found { + console::write(b"/bin/bash found but ELF load failed; enter built-in userspace\r\n"); + } else { + console::write(b"/bin/bash not found on ESP; enter built-in userspace\r\n"); + } + syscall::init(memory, &raw mut address_space); + io::enable_interrupts(); + if let Some(entry) = user_entry { + if loaded_user_path == Some(b"/usr/bin/busybox" as &'static [u8]) { + user::enter_elf_with_args( + entry, + &[b"/usr/bin/busybox", b"sh"], + user_program_or_default(user_entry, userspace_program), + ); + } else if loaded_user_path == Some(b"/usr/bin/sh" as &'static [u8]) { + user::enter_elf_with_args( + entry, + &[b"/usr/bin/sh"], + user_program_or_default(user_entry, userspace_program), + ); + } else if loaded_user_path == Some(b"/usr/bin/bash" as &'static [u8]) { + user::enter_elf_with_args( + entry, + &[b"/usr/bin/bash"], + user_program_or_default(user_entry, userspace_program), + ); + } else if loaded_user_path == Some(b"/bin/sh" as &'static [u8]) { + user::enter_elf_with_args( + entry, + &[b"/bin/sh"], + user_program_or_default(user_entry, userspace_program), + ); + } else { + user::enter_elf_with_args( + entry, + &[b"/bin/bash"], + user_program_or_default(user_entry, userspace_program), + ); + } + } else { + user::enter(); + } + } +} + +#[allow(dead_code)] +fn kernel_loop(tty: &mut Tty) -> ! { + loop { + while let Some(ch) = drivers::input::ps2::read_char() { + match ch { + 0x08 => tty.backspace(), + b'\r' => tty.write_bytes(b"\r\n"), + ch => tty.put_char(ch), + } + } + halt(); + } +} + +fn halt() { + unsafe { + core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); + } +} + +fn user_program_or_default(entry: Option, aux: Option) -> user::UserAux { + aux.unwrap_or(user::UserAux { + entry: entry.unwrap_or(0), + phdr: 0, + phent: 0, + phnum: 0, + }) +} + +fn halt_forever() -> ! { + loop { + halt(); + } +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + halt_forever(); +} diff --git a/src/memory.rs b/src/memory.rs new file mode 100644 index 0000000..1076646 --- /dev/null +++ b/src/memory.rs @@ -0,0 +1,194 @@ +use crate::efi::{EFI_CONVENTIONAL_MEMORY, EfiMemoryDescriptor, MemoryMap}; + +pub const PAGE_SIZE: usize = 4096; +pub const MAX_PHYSICAL_MEMORY: u64 = 64 * 1024 * 1024 * 1024; +pub const MAX_PHYSICAL_PAGES: usize = MAX_PHYSICAL_MEMORY as usize / PAGE_SIZE; +const BITMAP_WORDS: usize = MAX_PHYSICAL_PAGES / 64; +const MIN_USABLE_PHYSICAL_ADDRESS: u64 = 0x100000; +const USER_ELF_LOW_START: u64 = 0x400000; +const USER_ELF_LOW_END: u64 = 0x4000000; + +pub struct PhysicalMemoryManager { + bitmap: [u64; BITMAP_WORDS], + total_pages: usize, + free_pages: usize, + used_pages: usize, + next_search_page: usize, +} + +#[derive(Clone, Copy)] +pub struct MemoryStats { + pub total_pages: usize, + pub free_pages: usize, + pub used_pages: usize, +} + +impl PhysicalMemoryManager { + pub const fn new() -> Self { + Self { + // 1 means reserved/used. Start fully reserved until UEFI memory map marks pages free. + bitmap: [u64::MAX; BITMAP_WORDS], + total_pages: 0, + free_pages: 0, + used_pages: MAX_PHYSICAL_PAGES, + next_search_page: 0, + } + } + + pub unsafe fn init_from_uefi_memory_map(&mut self, memory_map: MemoryMap) { + self.bitmap.fill(u64::MAX); + self.total_pages = 0; + self.free_pages = 0; + self.used_pages = MAX_PHYSICAL_PAGES; + self.next_search_page = page_index(MIN_USABLE_PHYSICAL_ADDRESS); + + let mut offset = 0; + while offset < memory_map.byte_len { + let descriptor = + unsafe { (memory_map.ptr as *const u8).add(offset) as *const EfiMemoryDescriptor }; + let descriptor = unsafe { *descriptor }; + + let start = align_up( + descriptor.physical_start.max(MIN_USABLE_PHYSICAL_ADDRESS), + PAGE_SIZE as u64, + ); + let end = descriptor + .physical_start + .saturating_add(descriptor.number_of_pages.saturating_mul(PAGE_SIZE as u64)) + .min(MAX_PHYSICAL_MEMORY); + + if start < end { + self.total_pages += ((end - start) as usize) / PAGE_SIZE; + } + + if descriptor.ty == EFI_CONVENTIONAL_MEMORY { + self.mark_range_free(start, end); + } + + offset += memory_map.descriptor_size; + } + + self.mark_range_used(USER_ELF_LOW_START, USER_ELF_LOW_END); + } + + pub fn alloc_page(&mut self) -> Option<*mut u8> { + self.alloc_pages(1) + } + + pub fn alloc_pages(&mut self, count: usize) -> Option<*mut u8> { + if count == 0 || count > MAX_PHYSICAL_PAGES { + return None; + } + + let mut run_start = self.next_search_page; + let mut run_len = 0; + + for page in self.next_search_page..MAX_PHYSICAL_PAGES { + if self.is_free(page) { + if run_len == 0 { + run_start = page; + } + run_len += 1; + + if run_len == count { + for used_page in run_start..run_start + count { + self.set_used(used_page); + } + self.next_search_page = run_start + count; + + let address = (run_start * PAGE_SIZE) as *mut u8; + unsafe { + core::ptr::write_bytes(address, 0, count * PAGE_SIZE); + } + return Some(address); + } + } else { + run_len = 0; + } + } + + self.next_search_page = page_index(MIN_USABLE_PHYSICAL_ADDRESS); + None + } + + pub unsafe fn free_page(&mut self, page: *mut u8) { + unsafe { + self.free_pages(page, 1); + } + } + + pub unsafe fn free_pages(&mut self, base: *mut u8, count: usize) { + let start = page_index(base as u64); + for page in start..start.saturating_add(count).min(MAX_PHYSICAL_PAGES) { + self.set_free(page); + } + if start < self.next_search_page { + self.next_search_page = start; + } + } + + pub fn stats(&self) -> MemoryStats { + MemoryStats { + total_pages: self.total_pages, + free_pages: self.free_pages, + used_pages: self.used_pages, + } + } + + fn mark_range_free(&mut self, start: u64, end: u64) { + let start_page = page_index(start); + let end_page = page_index(end); + + for page in start_page..end_page.min(MAX_PHYSICAL_PAGES) { + self.set_free(page); + } + } + + fn mark_range_used(&mut self, start: u64, end: u64) { + let start_page = page_index(start); + let end_page = page_index(end); + + for page in start_page..end_page.min(MAX_PHYSICAL_PAGES) { + self.set_used(page); + } + if self.next_search_page >= start_page && self.next_search_page < end_page { + self.next_search_page = end_page; + } + } + + fn is_free(&self, page: usize) -> bool { + let word = self.bitmap[page / 64]; + let bit = page % 64; + word & (1u64 << bit) == 0 + } + + fn set_free(&mut self, page: usize) { + let word = page / 64; + let bit = page % 64; + let mask = 1u64 << bit; + if self.bitmap[word] & mask != 0 { + self.bitmap[word] &= !mask; + self.free_pages += 1; + self.used_pages = self.used_pages.saturating_sub(1); + } + } + + fn set_used(&mut self, page: usize) { + let word = page / 64; + let bit = page % 64; + let mask = 1u64 << bit; + if self.bitmap[word] & mask == 0 { + self.bitmap[word] |= mask; + self.free_pages = self.free_pages.saturating_sub(1); + self.used_pages += 1; + } + } +} + +const fn page_index(address: u64) -> usize { + (address as usize) / PAGE_SIZE +} + +const fn align_up(value: u64, align: u64) -> u64 { + (value + align - 1) & !(align - 1) +} diff --git a/src/paging.rs b/src/paging.rs new file mode 100644 index 0000000..913a483 --- /dev/null +++ b/src/paging.rs @@ -0,0 +1,258 @@ +use core::arch::asm; + +use crate::memory::{PAGE_SIZE, PhysicalMemoryManager}; + +const PRESENT: u64 = 1 << 0; +const WRITABLE: u64 = 1 << 1; +const HUGE_PAGE: u64 = 1 << 7; +const CR4_PSE: u64 = 1 << 4; + +const ENTRY_COUNT: usize = 512; +const IDENTITY_MAP_GIB: usize = 512; +const PDPTE_COUNT: usize = IDENTITY_MAP_GIB / 2; + +#[derive(Clone, Copy)] +pub struct PageFlags(u64); + +impl PageFlags { + #[allow(dead_code)] + pub const PRESENT: Self = Self(PRESENT); + pub const WRITABLE: Self = Self(WRITABLE); + #[allow(dead_code)] + pub const USER: Self = Self(1 << 2); + #[allow(dead_code)] + pub const NO_EXECUTE: Self = Self(1 << 63); + + pub const fn bits(self) -> u64 { + self.0 + } + + #[allow(dead_code)] + pub const fn union(self, other: Self) -> Self { + Self(self.0 | other.0) + } +} + +#[repr(C, align(4096))] +pub struct PageTable { + entries: [u64; ENTRY_COUNT], +} + +impl PageTable { + fn zero(&mut self) { + self.entries.fill(0); + } +} + +pub struct AddressSpace { + pml4: *mut PageTable, + identity_pdpt: *mut PageTable, + identity_pds: [*mut PageTable; PDPTE_COUNT], +} + +impl AddressSpace { + pub unsafe fn new_kernel(memory: &mut PhysicalMemoryManager) -> Option { + let pml4 = memory.alloc_page()? as *mut PageTable; + let identity_pdpt = memory.alloc_page()? as *mut PageTable; + let mut identity_pds = [core::ptr::null_mut(); PDPTE_COUNT]; + + let identity_flags = PRESENT | WRITABLE | PageFlags::USER.bits(); + + unsafe { + (*pml4).zero(); + (*identity_pdpt).zero(); + (*pml4).entries[0] = identity_pdpt as u64 | identity_flags; + } + + for (pdpt_index, pd) in identity_pds.iter_mut().enumerate() { + *pd = memory.alloc_page()? as *mut PageTable; + + unsafe { + (**pd).zero(); + (*identity_pdpt).entries[pdpt_index] = *pd as u64 | identity_flags; + + for pd_index in 0..ENTRY_COUNT { + let physical = ((pdpt_index * ENTRY_COUNT + pd_index) as u64) * 2 * 1024 * 1024; + (**pd).entries[pd_index] = physical | identity_flags | HUGE_PAGE; + } + } + } + + Some(Self { + pml4, + identity_pdpt, + identity_pds, + }) + } + + pub unsafe fn map_page( + &mut self, + memory: &mut PhysicalMemoryManager, + virtual_address: u64, + physical_address: u64, + flags: PageFlags, + ) -> Option<()> { + if !is_aligned(virtual_address) || !is_aligned(physical_address) { + return None; + } + + let table = unsafe { self.walk_create(memory, virtual_address)? }; + let index = pt_index(virtual_address); + + unsafe { + if (*table).entries[index] & PRESENT != 0 { + return None; + } + (*table).entries[index] = physical_address | flags.bits() | PRESENT; + flush_tlb_one(virtual_address); + } + + Some(()) + } + + pub unsafe fn unmap_page(&mut self, virtual_address: u64) -> Option { + let table = unsafe { self.walk(virtual_address)? }; + let index = pt_index(virtual_address); + + unsafe { + let entry = (*table).entries[index]; + if entry & PRESENT == 0 { + return None; + } + (*table).entries[index] = 0; + flush_tlb_one(virtual_address); + Some(entry & 0x000f_ffff_ffff_f000) + } + } + + pub unsafe fn translate(&self, virtual_address: u64) -> Option { + let pml4 = unsafe { &*self.pml4 }; + let pml4e = pml4.entries[pml4_index(virtual_address)]; + let pdpt = next_table(pml4e)?; + + let pdpte = unsafe { (*pdpt).entries[pdpt_index(virtual_address)] }; + if pdpte & HUGE_PAGE != 0 { + return Some((pdpte & 0x000f_ffff_c000_0000) | (virtual_address & 0x3fff_ffff)); + } + let pd = next_table(pdpte)?; + + let pde = unsafe { (*pd).entries[pd_index(virtual_address)] }; + if pde & HUGE_PAGE != 0 { + return Some((pde & 0x000f_ffff_ffe0_0000) | (virtual_address & 0x1f_ffff)); + } + let pt = next_table(pde)?; + + let pte = unsafe { (*pt).entries[pt_index(virtual_address)] }; + if pte & PRESENT == 0 { + None + } else { + Some((pte & 0x000f_ffff_ffff_f000) | (virtual_address & 0xfff)) + } + } + + pub unsafe fn activate(&self) { + unsafe { + let cr4: u64; + asm!("mov {}, cr4", out(reg) cr4, options(nostack, preserves_flags)); + asm!( + "mov cr4, {}", + in(reg) cr4 | CR4_PSE, + options(nostack, preserves_flags) + ); + asm!( + "mov cr3, {}", + in(reg) self.pml4 as u64, + options(nostack, preserves_flags) + ); + } + } + + pub fn root_table(&self) -> u64 { + self.pml4 as u64 + } + + pub fn identity_tables(&self) -> (*mut PageTable, [*mut PageTable; PDPTE_COUNT]) { + (self.identity_pdpt, self.identity_pds) + } + + unsafe fn walk_create( + &mut self, + memory: &mut PhysicalMemoryManager, + virtual_address: u64, + ) -> Option<*mut PageTable> { + let mut table = self.pml4; + for index in [ + pml4_index(virtual_address), + pdpt_index(virtual_address), + pd_index(virtual_address), + ] { + unsafe { + let entry = &mut (*table).entries[index]; + if *entry & HUGE_PAGE != 0 { + return None; + } + if *entry & PRESENT == 0 { + let new_table = memory.alloc_page()? as *mut PageTable; + (*new_table).zero(); + *entry = new_table as u64 | PRESENT | WRITABLE | PageFlags::USER.bits(); + } + table = (*entry & 0x000f_ffff_ffff_f000) as *mut PageTable; + } + } + + Some(table) + } + + unsafe fn walk(&self, virtual_address: u64) -> Option<*mut PageTable> { + let mut table = self.pml4; + for index in [ + pml4_index(virtual_address), + pdpt_index(virtual_address), + pd_index(virtual_address), + ] { + unsafe { + let entry = (*table).entries[index]; + if entry & PRESENT == 0 || entry & HUGE_PAGE != 0 { + return None; + } + table = (entry & 0x000f_ffff_ffff_f000) as *mut PageTable; + } + } + + Some(table) + } +} + +fn next_table(entry: u64) -> Option<*mut PageTable> { + if entry & PRESENT == 0 { + None + } else { + Some((entry & 0x000f_ffff_ffff_f000) as *mut PageTable) + } +} + +fn pml4_index(address: u64) -> usize { + ((address >> 39) & 0x1ff) as usize +} + +fn pdpt_index(address: u64) -> usize { + ((address >> 30) & 0x1ff) as usize +} + +fn pd_index(address: u64) -> usize { + ((address >> 21) & 0x1ff) as usize +} + +fn pt_index(address: u64) -> usize { + ((address >> 12) & 0x1ff) as usize +} + +fn is_aligned(address: u64) -> bool { + address as usize & (PAGE_SIZE - 1) == 0 +} + +unsafe fn flush_tlb_one(virtual_address: u64) { + unsafe { + asm!("invlpg [{}]", in(reg) virtual_address, options(nostack, preserves_flags)); + } +} diff --git a/src/services/mod.rs b/src/services/mod.rs new file mode 100644 index 0000000..b5ed10d --- /dev/null +++ b/src/services/mod.rs @@ -0,0 +1,17 @@ +#![allow(dead_code)] + +pub mod tty; +pub mod vfs; + +pub const SERVICE_VFS: usize = 1; +pub const SERVICE_TTY: usize = 2; + +#[derive(Clone, Copy)] +pub enum ServiceRequest { + Read, + Write, + Open, + Close, + Stat, + Ioctl, +} diff --git a/src/services/tty.rs b/src/services/tty.rs new file mode 100644 index 0000000..d03f538 --- /dev/null +++ b/src/services/tty.rs @@ -0,0 +1,9 @@ +use crate::ipc::Message; + +pub const OP_READ: u16 = 1; +pub const OP_WRITE: u16 = 2; +pub const OP_IOCTL: u16 = 3; + +pub fn dispatch(_message: Message) -> isize { + -38 +} diff --git a/src/services/vfs.rs b/src/services/vfs.rs new file mode 100644 index 0000000..66884e9 --- /dev/null +++ b/src/services/vfs.rs @@ -0,0 +1,10 @@ +use crate::ipc::Message; + +pub const OP_OPEN: u16 = 1; +pub const OP_READ: u16 = 2; +pub const OP_WRITE: u16 = 3; +pub const OP_CLOSE: u16 = 4; + +pub fn dispatch(_message: Message) -> isize { + -38 +} diff --git a/src/syscall.rs b/src/syscall.rs new file mode 100644 index 0000000..82cba40 --- /dev/null +++ b/src/syscall.rs @@ -0,0 +1,1546 @@ +use core::arch::{asm, global_asm}; + +use crate::{ + console, + drivers::platform::timer, + elf, + fs::vfs, + gdt, ipc, + memory::{PAGE_SIZE, PhysicalMemoryManager}, + paging::{AddressSpace, PageFlags}, + task, user, +}; + +pub const SYS_READ: usize = 0; +pub const SYS_WRITE: usize = 1; +pub const SYS_OPEN: usize = 2; +pub const SYS_CLOSE: usize = 3; +pub const SYS_FSTAT: usize = 5; +pub const SYS_POLL: usize = 7; +pub const SYS_LSEEK: usize = 8; +pub const SYS_MMAP: usize = 9; +pub const SYS_MPROTECT: usize = 10; +pub const SYS_MUNMAP: usize = 11; +pub const SYS_BRK: usize = 12; +pub const SYS_RT_SIGACTION: usize = 13; +pub const SYS_RT_SIGPROCMASK: usize = 14; +pub const SYS_IOCTL: usize = 16; +pub const SYS_WRITEV: usize = 20; +pub const SYS_ACCESS: usize = 21; +pub const SYS_PIPE: usize = 22; +pub const SYS_SOCKET: usize = 41; +pub const SYS_CONNECT: usize = 42; +pub const SYS_ACCEPT: usize = 43; +pub const SYS_SENDTO: usize = 44; +pub const SYS_RECVFROM: usize = 45; +pub const SYS_SENDMSG: usize = 46; +pub const SYS_RECVMSG: usize = 47; +pub const SYS_SHUTDOWN: usize = 48; +pub const SYS_BIND: usize = 49; +pub const SYS_LISTEN: usize = 50; +pub const SYS_GETSOCKNAME: usize = 51; +pub const SYS_GETPEERNAME: usize = 52; +pub const SYS_SOCKETPAIR: usize = 53; +pub const SYS_SETSOCKOPT: usize = 54; +pub const SYS_GETSOCKOPT: usize = 55; +pub const SYS_CLONE: usize = 56; +pub const SYS_FORK: usize = 57; +pub const SYS_VFORK: usize = 58; +pub const SYS_EXECVE: usize = 59; +pub const SYS_WAIT4: usize = 61; +pub const SYS_FCNTL: usize = 72; +pub const SYS_DUP: usize = 32; +pub const SYS_DUP2: usize = 33; +pub const SYS_GETDENTS64: usize = 217; +pub const SYS_GETRANDOM: usize = 318; +pub const SYS_PRLIMIT64: usize = 302; +pub const SYS_GETPGID: usize = 121; +pub const SYS_SETPGID: usize = 109; +pub const SYS_GETSID: usize = 124; +pub const SYS_RT_SIGRETURN: usize = 15; +pub const SYS_GETCWD: usize = 79; +pub const SYS_CHDIR: usize = 80; +pub const SYS_MKDIR: usize = 83; +pub const SYS_READLINK: usize = 89; +pub const SYS_PRCTL: usize = 157; +pub const SYS_TIME: usize = 201; +pub const SYS_GETUID: usize = 102; +pub const SYS_GETGID: usize = 104; +pub const SYS_GETEUID: usize = 107; +pub const SYS_GETEGID: usize = 108; +pub const SYS_GETPPID: usize = 110; +pub const SYS_UNAME: usize = 63; +pub const SYS_ARCH_PRCTL: usize = 158; +pub const SYS_GETPID: usize = 39; +pub const SYS_EXIT: usize = 60; +pub const SYS_EXIT_GROUP: usize = 231; +pub const SYS_OPENAT: usize = 257; +pub const SYS_MKDIRAT: usize = 258; +pub const SYS_NEWFSTATAT: usize = 262; +pub const SYS_READLINKAT: usize = 267; +pub const SYS_UTIMENSAT: usize = 280; +pub const SYS_SET_TID_ADDRESS: usize = 218; +pub const SYS_GETTID: usize = 186; +pub const SYS_TGKILL: usize = 234; +pub const SYS_SET_ROBUST_LIST: usize = 273; +pub const SYS_RSEQ: usize = 334; +pub const SYS_CLOCK_GETTIME: usize = 228; +pub const SYS_CLOCK_NANOSLEEP: usize = 230; +pub const SYS_ISATTY: usize = 1000; + +pub const STDIN_FILENO: usize = 0; +pub const STDOUT_FILENO: usize = 1; +pub const STDERR_FILENO: usize = 2; + +const EBADF: isize = 9; +const EFAULT: isize = 14; +const EINVAL: isize = 22; +const ENOENT: isize = 2; +const ENOSYS: isize = 38; +const ENODEV: isize = 19; +const ESPIPE: isize = 29; +const EAGAIN: isize = 11; +const TIMER_ABSTIME: usize = 1; + +const MAP_ANONYMOUS: usize = 0x20; +const MAP_FIXED: usize = 0x10; +const F_DUPFD: usize = 0; +const F_GETFD: usize = 1; +const F_SETFD: usize = 2; +const F_GETFL: usize = 3; +const F_SETFL: usize = 4; +const F_DUPFD_CLOEXEC: usize = 1030; +const POLLIN: i16 = 0x0001; +const POLLOUT: i16 = 0x0004; +const TCGETS: usize = 0x5401; +const TCSETS: usize = 0x5402; +const TCSETSW: usize = 0x5403; +const TCSETSF: usize = 0x5404; +const TCGETA: usize = 0x5405; +const TCSETA: usize = 0x5406; +const TCSETAW: usize = 0x5407; +const TCSETAF: usize = 0x5408; +const TIOCGWINSZ: usize = 0x5413; +const TIOCSWINSZ: usize = 0x5414; +const TCGETS2: usize = 0x802c542a; +const TCSETS2: usize = 0x402c542b; +const TCSETSW2: usize = 0x402c542c; +const TCSETSF2: usize = 0x402c542d; +const ARCH_SET_FS: usize = 0x1002; +const ARCH_GET_FS: usize = 0x1003; +const AT_EMPTY_PATH: usize = 0x1000; +const IA32_FS_BASE: u32 = 0xc000_0100; + +const IA32_EFER: u32 = 0xc000_0080; +const IA32_STAR: u32 = 0xc000_0081; +const IA32_LSTAR: u32 = 0xc000_0082; +const IA32_FMASK: u32 = 0xc000_0084; +const EFER_SCE: u64 = 1; +const RFLAGS_INTERRUPT_ENABLE: u64 = 1 << 9; + +static mut MEMORY: *mut PhysicalMemoryManager = core::ptr::null_mut(); +static mut ADDRESS_SPACE: *mut AddressSpace = core::ptr::null_mut(); +static mut PROGRAM_BREAK_START: usize = 0; +static mut PROGRAM_BREAK: usize = 0; +static mut PROGRAM_BREAK_LIMIT: usize = 0; +static mut USER_BRK_CURSOR: usize = USER_BRK_BASE; +static mut USER_MMAP_CURSOR: usize = USER_MMAP_BASE; +const SAVED_PROGRAM_BREAK_IMAGE_SIZE: usize = 1024 * PAGE_SIZE; +const USER_ELF_SNAPSHOT_START: usize = 0x400000; +const USER_ELF_SNAPSHOT_SIZE: usize = 1024 * PAGE_SIZE; +static mut SAVED_PROGRAM_BREAK_START: usize = 0; +static mut SAVED_PROGRAM_BREAK: usize = 0; +static mut SAVED_PROGRAM_BREAK_LIMIT: usize = 0; +static mut SAVED_PROGRAM_BREAK_IMAGE: *mut u8 = core::ptr::null_mut(); +static mut SAVED_USER_ELF_IMAGE: *mut u8 = core::ptr::null_mut(); +static mut LAST_UNSUPPORTED_SYSCALL: usize = usize::MAX; +static mut SIGNAL_MASK: u64 = 0; +static mut SIGNAL_ACTIONS: [[usize; 4]; 65] = [[0; 4]; 65]; +static mut SYSCALL_TRACE_COUNT: usize = 0; +static mut IOCTL_TRACE_COUNT: usize = 0; + +const TRACE_SYSCALLS: bool = false; +const TRACE_IOCTL: bool = false; +const TRACE_ALLOC: bool = false; +const TRACE_FS_FDS: bool = false; + +const USER_BRK_BASE: usize = 0x0000_0100_0000_0000; +const USER_BRK_PAGES: usize = 1024; +const USER_BRK_LIMIT: usize = 0x0000_0200_0000_0000; +const USER_MMAP_BASE: usize = 0x0000_0200_0000_0000; +const USER_MMAP_LIMIT: usize = 0x0000_0300_0000_0000; + +global_asm!( + r#" + .global zeroos_syscall_entry +zeroos_syscall_entry: + cld + mov [rip + zeroos_saved_user_rsp], rsp + lea rsp, [rip + zeroos_syscall_stack_top] + push qword ptr [rip + zeroos_saved_user_rsp] + push r11 + push rcx + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + push rbx + push rdx + push rcx + push rax + sub rsp, 32 + lea rcx, [rsp + 32] + call zeroos_syscall_handler + add rsp, 32 + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + pop rcx + pop r11 + pop r10 + push 0x23 + push r10 + push r11 + push 0x2b + push rcx + iretq + + .section .bss + .align 16 +zeroos_syscall_stack: + .zero 16384 +zeroos_syscall_stack_top: +zeroos_saved_user_rsp: + .zero 8 + .section .text +"# +); + +unsafe extern "C" { + fn zeroos_syscall_entry(); +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct TrapFrame { + pub rax: usize, + pub rcx: usize, + pub rdx: usize, + pub rbx: usize, + pub rbp: usize, + pub rsi: usize, + pub rdi: usize, + pub r8: usize, + pub r9: usize, + pub r10: usize, + pub r11: usize, + pub r12: usize, + pub r13: usize, + pub r14: usize, + pub r15: usize, + pub rip: usize, + pub rflags: usize, + pub rsp: usize, +} + +impl TrapFrame { + pub const fn zero() -> Self { + Self { + rax: 0, + rcx: 0, + rdx: 0, + rbx: 0, + rbp: 0, + rsi: 0, + rdi: 0, + r8: 0, + r9: 0, + r10: 0, + r11: 0, + r12: 0, + r13: 0, + r14: 0, + r15: 0, + rip: 0, + rflags: 0, + rsp: 0, + } + } +} + +pub unsafe fn init(memory: *mut PhysicalMemoryManager, address_space: *mut AddressSpace) { + unsafe { + MEMORY = memory; + ADDRESS_SPACE = address_space; + let efer = read_msr(IA32_EFER); + write_msr(IA32_EFER, efer | EFER_SCE); + + let star = ((gdt::SYSCALL_USER_SELECTOR_BASE as u64) << 48) + | ((gdt::KERNEL_CODE_SELECTOR as u64) << 32); + write_msr(IA32_STAR, star); + write_msr( + IA32_LSTAR, + zeroos_syscall_entry as *const () as usize as u64, + ); + write_msr(IA32_FMASK, RFLAGS_INTERRUPT_ENABLE); + } +} + +pub fn is_user_mapped(address: usize, len: usize) -> bool { + unsafe { + if ADDRESS_SPACE.is_null() || len == 0 { + return false; + } + let Some(end) = address.checked_add(len - 1) else { + return false; + }; + if !is_canonical_user_address(address) || !is_canonical_user_address(end) { + return false; + } + (*ADDRESS_SPACE).translate(address as u64).is_some() + && (*ADDRESS_SPACE).translate(end as u64).is_some() + } +} + +fn is_canonical_user_address(address: usize) -> bool { + address < 0x0000_8000_0000_0000 +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_syscall_handler(frame: *mut TrapFrame) { + let frame = unsafe { &mut *frame }; + frame.rax = zeroos_syscall_dispatch( + frame.rax, frame.rdi, frame.rsi, frame.rdx, frame.r10, frame.r8, frame.r9, frame, + ) as usize; +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_int80_syscall_dispatch( + number: usize, + arg0: usize, + arg1: usize, + arg2: usize, + arg3: usize, + arg4: usize, + arg5: usize, +) -> isize { + zeroos_syscall_dispatch( + number, + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + core::ptr::null_mut(), + ) +} + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_syscall_dispatch( + number: usize, + arg0: usize, + arg1: usize, + arg2: usize, + arg3: usize, + arg4: usize, + arg5: usize, + frame: *mut TrapFrame, +) -> isize { + trace_syscall(number); + match number { + SYS_READ => sys_read(arg0, arg1 as *mut u8, arg2), + SYS_WRITE => sys_write(arg0, arg1 as *const u8, arg2), + SYS_OPEN => vfs::open_user_path_with_flags(arg0 as *const u8, arg1), + SYS_CLOSE => trace_fs_result(b"close", arg0, 0, sys_close(arg0)), + SYS_FSTAT => sys_fstat(arg0, arg1 as *mut u8), + SYS_POLL => sys_poll(arg0 as *mut PollFd, arg1, arg2 as isize), + SYS_DUP => sys_dup(arg0), + SYS_DUP2 => sys_dup2(arg0, arg1), + SYS_PIPE => sys_pipe(arg0 as *mut i32), + SYS_SOCKET => ipc::socket(arg0, arg1, arg2), + SYS_CONNECT | SYS_ACCEPT | SYS_SENDMSG | SYS_RECVMSG | SYS_SHUTDOWN | SYS_BIND + | SYS_LISTEN | SYS_GETSOCKNAME | SYS_GETPEERNAME | SYS_SETSOCKOPT | SYS_GETSOCKOPT => { + ipc::unsupported_socket_op() + } + SYS_SENDTO => sys_write(arg0, arg1 as *const u8, arg2), + SYS_RECVFROM => sys_read(arg0, arg1 as *mut u8, arg2), + SYS_SOCKETPAIR => ipc::socketpair(arg0, arg1, arg2, arg3 as *mut i32), + SYS_MMAP => sys_mmap(arg0, arg1, arg2, arg3, arg4 as isize, arg5), + SYS_MPROTECT => 0, + SYS_MUNMAP => 0, + SYS_BRK => sys_brk(arg0), + SYS_RT_SIGACTION => sys_rt_sigaction(arg0, arg1 as *const usize, arg2 as *mut usize, arg3), + SYS_RT_SIGRETURN => -ENOSYS, + SYS_RT_SIGPROCMASK => sys_rt_sigprocmask(arg0, arg1 as *const u64, arg2 as *mut u64, arg3), + SYS_IOCTL => sys_ioctl(arg0, arg1, arg2 as *mut u8, frame), + SYS_WRITEV => sys_writev(arg0, arg1 as *const IoVec, arg2), + SYS_ACCESS => vfs::access_user_path(arg0 as *const u8), + SYS_LSEEK => sys_lseek(arg0, arg1 as isize, arg2), + SYS_CLONE => sys_clone(arg0, frame), + SYS_FORK => sys_fork(frame), + SYS_VFORK => sys_vfork(frame), + SYS_EXECVE => sys_execve(arg0 as *const u8, arg1 as *const *const u8, frame), + SYS_WAIT4 => sys_wait4(arg0 as isize, arg1 as *mut i32), + SYS_FCNTL => sys_fcntl(arg0, arg1, arg2), + SYS_GETCWD => sys_getcwd(arg0 as *mut u8, arg1), + SYS_CHDIR => sys_chdir(arg0 as *const u8), + SYS_READLINK => -ENOENT, + SYS_PRCTL => 0, + SYS_TIME => sys_time(arg0 as *mut u64), + SYS_UNAME => sys_uname(arg0 as *mut u8), + SYS_GETUID | SYS_GETGID | SYS_GETEUID | SYS_GETEGID => 0, + SYS_GETPPID => task::current_ppid() as isize, + SYS_ARCH_PRCTL => sys_arch_prctl(arg0, arg1), + SYS_GETPID => task::current_pid() as isize, + SYS_GETTID => task::current_pid() as isize, + SYS_GETPGID | SYS_GETSID => 1, + SYS_SETPGID => 0, + SYS_EXIT | SYS_EXIT_GROUP => sys_exit(arg0 as isize, frame), + SYS_SET_TID_ADDRESS => 1, + SYS_CLOCK_GETTIME => sys_clock_gettime(arg0, arg1 as *mut u8), + SYS_CLOCK_NANOSLEEP => sys_clock_nanosleep(arg0, arg1, arg2 as *const u8, arg3 as *mut u8), + SYS_TGKILL => sys_tgkill(arg0, arg1, arg2, frame), + SYS_SET_ROBUST_LIST => sys_set_robust_list(arg0, arg1), + SYS_RSEQ => sys_rseq(arg0, arg1, arg2), + SYS_GETRANDOM => sys_getrandom(arg0 as *mut u8, arg1, arg2), + SYS_PRLIMIT64 => sys_prlimit64(arg0, arg1, arg2 as *const u8, arg3 as *mut u8), + SYS_GETDENTS64 => trace_fs_result( + b"getdents", + arg0, + arg2, + vfs::getdents64(arg0, arg1 as *mut u8, arg2), + ), + SYS_OPENAT => trace_fs_result( + b"openat", + arg0, + arg2, + vfs::openat_user_path(sys_fd_arg(arg0), arg1 as *const u8, arg2), + ), + SYS_MKDIR => vfs::mkdir_user_path(arg0 as *const u8), + SYS_MKDIRAT => vfs::mkdirat_user_path(sys_fd_arg(arg0), arg1 as *const u8), + SYS_NEWFSTATAT => { + sys_newfstatat(sys_fd_arg(arg0), arg1 as *const u8, arg2 as *mut u8, arg3) + } + SYS_READLINKAT => -ENOENT, + SYS_UTIMENSAT => vfs::touch_user_path(sys_fd_arg(arg0), arg1 as *const u8), + SYS_ISATTY => sys_isatty(arg0), + _ => { + trace_unsupported_syscall(number); + -ENOSYS + } + } +} + +fn trace_syscall(number: usize) { + if !TRACE_SYSCALLS { + return; + } + if number == SYS_READ { + return; + } + unsafe { + if SYSCALL_TRACE_COUNT >= 128 { + return; + } + SYSCALL_TRACE_COUNT += 1; + } + console::write(b"sys "); + write_usize(number); + console::write(b"\n"); +} + +fn sys_read(fd: usize, buffer: *mut u8, len: usize) -> isize { + if len == 0 { + return 0; + } + if buffer.is_null() { + return -EBADF; + } + + loop { + if let Some(read) = ipc::read(fd, buffer, len) { + if read != -EAGAIN { + return read; + } + wait_for_interrupt(); + continue; + } + let read = vfs::read(fd, buffer, len); + if read != 0 { + return read; + } + wait_for_interrupt(); + } +} + +fn wait_for_interrupt() { + unsafe { + asm!("sti; hlt; cli", options(nomem, nostack, preserves_flags)); + } +} + +fn sys_poll(fds: *mut PollFd, nfds: usize, timeout_ms: isize) -> isize { + if fds.is_null() { + return -EFAULT; + } + + let start_tick = timer::ticks(); + let timeout_ticks = if timeout_ms < 0 { + None + } else { + Some(((timeout_ms as u64) * timer::HZ).div_ceil(1000)) + }; + + loop { + let ready = poll_once(fds, nfds); + if ready != 0 || timeout_ms == 0 { + return ready; + } + if let Some(limit) = timeout_ticks { + if timer::ticks().saturating_sub(start_tick) >= limit { + return 0; + } + } + wait_for_interrupt(); + } +} + +fn poll_once(fds: *mut PollFd, nfds: usize) -> isize { + let mut ready = 0isize; + unsafe { + for index in 0..nfds { + let pollfd = &mut *fds.add(index); + pollfd.revents = 0; + if pollfd.fd < 0 { + continue; + } + if let Some(revents) = ipc::poll(pollfd.fd as usize, pollfd.events) { + pollfd.revents = revents; + } else if let Some(revents) = vfs::poll(pollfd.fd as usize, pollfd.events) { + pollfd.revents = revents; + } else { + if pollfd.events & POLLIN != 0 { + pollfd.revents |= POLLIN; + } + if pollfd.events & POLLOUT != 0 { + pollfd.revents |= POLLOUT; + } + } + if pollfd.revents != 0 { + ready += 1; + } + } + } + + ready +} + +fn sys_write(fd: usize, buffer: *const u8, len: usize) -> isize { + if buffer.is_null() { + return -EBADF; + } + if let Some(written) = ipc::write(fd, buffer, len) { + return written; + } + vfs::write(fd, buffer, len) +} + +#[repr(C)] +struct IoVec { + base: *const u8, + len: usize, +} + +#[repr(C)] +struct PollFd { + fd: i32, + events: i16, + revents: i16, +} + +fn sys_writev(fd: usize, iov: *const IoVec, iovcnt: usize) -> isize { + if iov.is_null() { + return -EFAULT; + } + if iovcnt > 1024 { + return -EINVAL; + } + + let mut total = 0isize; + for index in 0..iovcnt { + let vec = unsafe { &*iov.add(index) }; + if vec.len == 0 { + continue; + } + if vec.base.is_null() { + return -EFAULT; + } + let written = sys_write(fd, vec.base, vec.len); + if written < 0 { + return if total > 0 { total } else { written }; + } + total += written; + } + total +} + +fn sys_close(fd: usize) -> isize { + if let Some(result) = ipc::close(fd) { + return result; + } + if fd <= STDERR_FILENO { + 0 + } else { + vfs::close(fd) + } +} + +fn sys_pipe(pipefd: *mut i32) -> isize { + if pipefd.is_null() { + return -EFAULT; + } + ipc::pipe(pipefd) +} + +fn sys_fstat(fd: usize, stat: *mut u8) -> isize { + if stat.is_null() { + return -EBADF; + } + if let Some(result) = ipc::fstat(fd, stat) { + return result; + } + if fd > STDERR_FILENO { + return vfs::fstat(fd, stat); + } + + unsafe { + core::ptr::write_bytes(stat, 0, 128); + // Enough for libc to treat stdio as a character device. + let st_mode = stat.add(24) as *mut u32; + st_mode.write(0o020000 | 0o600); + } + 0 +} + +fn sys_lseek(fd: usize, offset: isize, whence: usize) -> isize { + if ipc::fstat(fd, core::ptr::null_mut()).is_some() { + return -ESPIPE; + } + if fd <= STDERR_FILENO { + -ESPIPE + } else { + vfs::lseek(fd, offset, whence) + } +} + +fn sys_dup2(oldfd: usize, newfd: usize) -> isize { + if let Some(result) = ipc::dup2(oldfd, newfd) { + return result; + } + if ipc::is_fd(newfd) { + let _ = ipc::close(newfd); + } + vfs::dup2(oldfd, newfd) +} + +fn sys_dup(fd: usize) -> isize { + if let Some(result) = ipc::dup(fd, 3) { + return result; + } + vfs::dup(fd) +} + +fn sys_fork(frame: *mut TrapFrame) -> isize { + if frame.is_null() { + return -EFAULT; + } + task::fork_like(unsafe { &*frame }) +} + +fn sys_vfork(frame: *mut TrapFrame) -> isize { + if frame.is_null() { + return -EFAULT; + } + snapshot_user_heap(); + task::vfork(unsafe { &*frame }) +} + +fn sys_clone(_flags: usize, frame: *mut TrapFrame) -> isize { + if frame.is_null() { + return -EFAULT; + } + task::fork_like(unsafe { &*frame }) +} + +fn sys_wait4(pid: isize, status: *mut i32) -> isize { + task::wait4(pid, status) +} + +fn sys_fcntl(fd: usize, command: usize, arg: usize) -> isize { + match command { + F_DUPFD | F_DUPFD_CLOEXEC => { + if let Some(result) = ipc::dup(fd, arg) { + result + } else { + vfs::dup_min(fd, arg) + } + } + F_GETFD => 0, + F_SETFD => 0, + F_GETFL => 0, + F_SETFL => 0, + _ => { + console::write(b"unsupported fcntl "); + write_usize(command); + console::write(b"\n"); + -EINVAL + } + } +} + +fn sys_fd_arg(value: usize) -> isize { + (value as i32) as isize +} + +fn sys_execve(path: *const u8, argv: *const *const u8, frame: *mut TrapFrame) -> isize { + if path.is_null() || frame.is_null() { + return -EFAULT; + } + let Some((ptr, len)) = vfs::file_data_user_path(path) else { + return -ENOENT; + }; + + let mut arg_storage = [[0u8; 128]; 16]; + let mut arg_lens = [0usize; 16]; + let argc = read_exec_args(path, argv, &mut arg_storage, &mut arg_lens); + let mut args: [&[u8]; 16] = [b""; 16]; + for index in 0..argc { + args[index] = &arg_storage[index][..arg_lens[index]]; + } + + let image = crate::fs::uefi::LoadedFile { ptr, len }; + unsafe { + if MEMORY.is_null() { + return -ENODEV; + } + let Some(program) = elf::load_user_elf(&image, &mut *MEMORY) else { + return -ENOENT; + }; + let aux = user::UserAux { + entry: program.entry, + phdr: program.phdr, + phent: program.phent, + phnum: program.phnum, + }; + let stack = user::prepare_elf_stack(&args[..argc], aux); + reset_exec_user_memory(); + let frame = &mut *frame; + frame.rax = 0; + frame.rip = program.entry as usize; + frame.rsp = stack as usize; + frame.rbx = 0; + frame.rbp = 0; + frame.r12 = 0; + frame.r13 = 0; + frame.r14 = 0; + frame.r15 = 0; + } + 0 +} + +fn read_exec_args( + path: *const u8, + argv: *const *const u8, + arg_storage: &mut [[u8; 128]; 16], + arg_lens: &mut [usize; 16], +) -> usize { + if argv.is_null() { + arg_lens[0] = read_user_bytes(path, &mut arg_storage[0]); + return 1; + } + + let mut argc = 0; + while argc < arg_storage.len() { + let ptr = unsafe { argv.add(argc).read() }; + if ptr.is_null() { + break; + } + arg_lens[argc] = read_user_bytes(ptr, &mut arg_storage[argc]); + argc += 1; + } + + if argc == 0 { + arg_lens[0] = read_user_bytes(path, &mut arg_storage[0]); + 1 + } else { + argc + } +} + +fn read_user_bytes(ptr: *const u8, buffer: &mut [u8]) -> usize { + if ptr.is_null() || buffer.is_empty() { + return 0; + } + let mut index = 0; + while index < buffer.len() { + let byte = unsafe { ptr.add(index).read() }; + if byte == 0 { + break; + } + buffer[index] = byte; + index += 1; + } + index +} + +fn sys_mmap( + requested_addr: usize, + len: usize, + _prot: usize, + flags: usize, + fd: isize, + _offset: usize, +) -> isize { + if len == 0 { + return -EINVAL; + } + if fd >= 0 && flags & MAP_ANONYMOUS == 0 { + return -ENODEV; + } + + let pages = len.div_ceil(PAGE_SIZE); + let map_len = pages * PAGE_SIZE; + unsafe { + if MEMORY.is_null() || ADDRESS_SPACE.is_null() { + return -ENODEV; + } + let base = if requested_addr != 0 && flags & MAP_FIXED != 0 { + align_down(requested_addr) + } else if requested_addr >= USER_MMAP_BASE + && requested_addr + .checked_add(map_len) + .is_some_and(|end| end <= USER_MMAP_LIMIT) + { + align_up(requested_addr) + } else { + let base = align_up(USER_MMAP_CURSOR); + let Some(next) = base.checked_add(map_len) else { + return -ENODEV; + }; + if next > USER_MMAP_LIMIT { + return -ENODEV; + } + USER_MMAP_CURSOR = next; + base + }; + if base < USER_MMAP_BASE + || base + .checked_add(map_len) + .is_none_or(|end| end > USER_MMAP_LIMIT) + { + return -ENODEV; + } + if map_user_pages(base, pages).is_none() { + return -ENODEV; + } + trace_alloc(b"mmap", base, len); + base as isize + } +} + +fn sys_brk(addr: usize) -> isize { + unsafe { + if MEMORY.is_null() || ADDRESS_SPACE.is_null() { + return 0; + } + if PROGRAM_BREAK_START == 0 { + let Some((base, limit)) = ensure_brk_region() else { + return 0; + }; + PROGRAM_BREAK_START = base; + PROGRAM_BREAK = PROGRAM_BREAK_START; + PROGRAM_BREAK_LIMIT = limit; + trace_alloc( + b"brk", + PROGRAM_BREAK_START, + PROGRAM_BREAK_LIMIT - PROGRAM_BREAK_START, + ); + } + + if addr == 0 { + return PROGRAM_BREAK as isize; + } + if (PROGRAM_BREAK_START..=PROGRAM_BREAK_LIMIT).contains(&addr) { + PROGRAM_BREAK = addr; + } + PROGRAM_BREAK as isize + } +} + +unsafe fn reset_exec_user_memory() { + unsafe { + if MEMORY.is_null() || ADDRESS_SPACE.is_null() { + PROGRAM_BREAK_START = 0; + PROGRAM_BREAK = 0; + PROGRAM_BREAK_LIMIT = 0; + return; + } + let Some((base, limit)) = ensure_brk_region() else { + PROGRAM_BREAK_START = 0; + PROGRAM_BREAK = 0; + PROGRAM_BREAK_LIMIT = 0; + return; + }; + core::ptr::write_bytes(base as *mut u8, 0, limit - base); + PROGRAM_BREAK_START = base; + PROGRAM_BREAK = base; + PROGRAM_BREAK_LIMIT = limit; + trace_alloc(b"exec-brk", base, limit - base); + } +} + +unsafe fn ensure_brk_region() -> Option<(usize, usize)> { + unsafe { + if SAVED_PROGRAM_BREAK_START != 0 && SAVED_PROGRAM_BREAK_LIMIT > SAVED_PROGRAM_BREAK_START { + return Some((SAVED_PROGRAM_BREAK_START, SAVED_PROGRAM_BREAK_LIMIT)); + } + if PROGRAM_BREAK_START != 0 && PROGRAM_BREAK_LIMIT > PROGRAM_BREAK_START { + return Some((PROGRAM_BREAK_START, PROGRAM_BREAK_LIMIT)); + } + + let base = align_up(USER_BRK_CURSOR); + let len = USER_BRK_PAGES * PAGE_SIZE; + let next = base.checked_add(len)?; + if next > USER_BRK_LIMIT { + return None; + } + if map_user_pages(base, USER_BRK_PAGES).is_none() { + return None; + } + USER_BRK_CURSOR = next; + Some((base, next)) + } +} + +unsafe fn map_user_pages(base: usize, pages: usize) -> Option<()> { + if base & (PAGE_SIZE - 1) != 0 || pages == 0 { + return None; + } + let flags = PageFlags::WRITABLE.union(PageFlags::USER); + for index in 0..pages { + let physical = unsafe { (*MEMORY).alloc_page()? as u64 }; + let virtual_address = (base + index * PAGE_SIZE) as u64; + if unsafe { + (*ADDRESS_SPACE) + .map_page(&mut *MEMORY, virtual_address, physical, flags) + .is_none() + } { + return None; + } + } + Some(()) +} + +const fn align_down(value: usize) -> usize { + value & !(PAGE_SIZE - 1) +} + +const fn align_up(value: usize) -> usize { + value.saturating_add(PAGE_SIZE - 1) & !(PAGE_SIZE - 1) +} + +fn sys_ioctl(fd: usize, request: usize, arg: *mut u8, frame: *mut TrapFrame) -> isize { + if arg.is_null() { + return -EBADF; + } + if fd > STDERR_FILENO && !vfs::is_tty(fd) { + return -EBADF; + } + + trace_ioctl(request, arg as usize, frame); + + unsafe { + match request { + TCGETS | TCGETA => { + // Linux x86_64 TCGETS uses the kernel termios layout: + // tcflag_t[4], c_line, c_cc[19] => 36 bytes. + write_termios(arg, false); + 0 + } + TCGETS2 => { + write_termios(arg, true); + 0 + } + TCSETS | TCSETSW | TCSETSF | TCSETA | TCSETAW | TCSETAF => { + set_termios_mode(arg, false); + 0 + } + TCSETS2 | TCSETSW2 | TCSETSF2 => { + set_termios_mode(arg, true); + 0 + } + TIOCGWINSZ => { + let (columns, rows) = console::size(); + let winsize = arg as *mut u16; + winsize.add(0).write(rows as u16); + winsize.add(1).write(columns as u16); + winsize.add(2).write(0); + winsize.add(3).write(0); + 0 + } + TIOCSWINSZ => 0, + _ => -EINVAL, + } + } +} + +unsafe fn write_termios(arg: *mut u8, termios2: bool) { + unsafe { + let len = if termios2 { 44 } else { 36 }; + core::ptr::write_bytes(arg, 0, len); + (arg as *mut u32).add(0).write(0); // c_iflag + (arg as *mut u32).add(1).write(0); // c_oflag + (arg as *mut u32).add(2).write(0o0000277); // c_cflag: B38400 | CS8 | CREAD + (arg as *mut u32).add(3).write(0o0000002 | 0o0000010); // c_lflag: ICANON | ECHO + arg.add(16).write(0); // c_line + if termios2 { + (arg.add(36) as *mut u32).write(38400); // c_ispeed + (arg.add(40) as *mut u32).write(38400); // c_ospeed + } + } +} + +unsafe fn set_termios_mode(arg: *mut u8, _termios2: bool) { + unsafe { + let lflag = (arg as *const u32).add(3).read(); + const ICANON: u32 = 0o0000002; + crate::console::set_raw_mode(lflag & ICANON == 0); + } +} + +fn sys_rt_sigaction( + sig: usize, + act: *const usize, + oldact: *mut usize, + sigset_size: usize, +) -> isize { + if sig == 0 || sig >= 65 || sigset_size != core::mem::size_of::() { + return -EINVAL; + } + + unsafe { + if !oldact.is_null() { + for index in 0..4 { + oldact.add(index).write(SIGNAL_ACTIONS[sig][index]); + } + } + if !act.is_null() { + for index in 0..4 { + SIGNAL_ACTIONS[sig][index] = act.add(index).read(); + } + } + } + + 0 +} + +fn sys_rt_sigprocmask(how: usize, set: *const u64, oldset: *mut u64, sigset_size: usize) -> isize { + if sigset_size != core::mem::size_of::() { + return -EINVAL; + } + + unsafe { + if !oldset.is_null() { + oldset.write(SIGNAL_MASK); + } + + if set.is_null() { + return 0; + } + + let value = set.read(); + match how { + 0 => SIGNAL_MASK |= value, + 1 => SIGNAL_MASK &= !value, + 2 => SIGNAL_MASK = value, + _ => return -EINVAL, + } + } + + 0 +} + +fn sys_getcwd(buffer: *mut u8, len: usize) -> isize { + vfs::getcwd(buffer, len) +} + +fn sys_chdir(path: *const u8) -> isize { + vfs::chdir_user_path(path) +} + +fn sys_uname(buffer: *mut u8) -> isize { + if buffer.is_null() { + return -EFAULT; + } + + unsafe { + core::ptr::write_bytes(buffer, 0, 65 * 6); + write_cstr(buffer, b"ZeroOS"); + write_cstr(buffer.add(65), b"zeroos"); + write_cstr(buffer.add(65 * 2), b"0.1"); + write_cstr(buffer.add(65 * 3), b"0.1"); + write_cstr(buffer.add(65 * 4), b"x86_64"); + } + 0 +} + +fn sys_arch_prctl(code: usize, addr: usize) -> isize { + unsafe { + match code { + ARCH_SET_FS => { + write_msr(IA32_FS_BASE, addr as u64); + 0 + } + ARCH_GET_FS => { + if addr == 0 { + return -EFAULT; + } + (addr as *mut u64).write(read_msr(IA32_FS_BASE)); + 0 + } + _ => -EINVAL, + } + } +} + +fn sys_isatty(fd: usize) -> isize { + if fd <= STDERR_FILENO || vfs::is_tty(fd) { + 1 + } else { + 0 + } +} + +fn sys_clock_gettime(clock_id: usize, timespec: *mut u8) -> isize { + if timespec.is_null() { + return -EFAULT; + } + + let (sec, nsec) = match clock_id { + 0 => timer::realtime(), + 1 | 4 | 7 => timer::monotonic_time(), + _ => return -EINVAL, + }; + + unsafe { + (timespec as *mut u64).write(sec); + (timespec.add(8) as *mut u64).write(nsec); + } + 0 +} + +fn sys_clock_nanosleep( + clock_id: usize, + flags: usize, + request: *const u8, + _remain: *mut u8, +) -> isize { + if request.is_null() { + return -EFAULT; + } + + let (sec, nsec) = unsafe { + ( + (request as *const u64).read(), + (request.add(8) as *const u64).read(), + ) + }; + if nsec >= 1_000_000_000 { + return -EINVAL; + } + + let sleep_ticks = if flags & TIMER_ABSTIME != 0 { + let (now_sec, now_nsec) = match clock_id { + 0 => timer::realtime(), + 1 | 4 | 7 => timer::monotonic_time(), + _ => return -EINVAL, + }; + let now_ns = now_sec + .saturating_mul(1_000_000_000) + .saturating_add(now_nsec); + let target_ns = sec.saturating_mul(1_000_000_000).saturating_add(nsec); + if target_ns <= now_ns { + return 0; + } + ns_to_ticks(target_ns - now_ns) + } else { + ns_to_ticks(sec.saturating_mul(1_000_000_000).saturating_add(nsec)) + }; + + if sleep_ticks == 0 { + return 0; + } + let deadline = timer::ticks().saturating_add(sleep_ticks); + while timer::ticks() < deadline { + wait_for_interrupt(); + } + 0 +} + +fn ns_to_ticks(ns: u64) -> u64 { + let tick_ns = 1_000_000_000 / timer::HZ; + ns.div_ceil(tick_ns).max(1) +} + +fn sys_time(tloc: *mut u64) -> isize { + let (sec, _) = timer::realtime(); + if !tloc.is_null() { + unsafe { + tloc.write(sec); + } + } + sec as isize +} + +fn sys_tgkill(tgid: usize, tid: usize, sig: usize, frame: *mut TrapFrame) -> isize { + let current = task::current_pid(); + if tgid != current || tid != current { + return -EINVAL; + } + match sig { + 0 => 0, + 6 => sys_exit(128 + 6, frame), + 9 | 15 => sys_exit(128 + sig as isize, frame), + _ => -EINVAL, + } +} + +fn sys_set_robust_list(_head: usize, len: usize) -> isize { + if len != 24 { + return -EINVAL; + } + 0 +} + +fn sys_rseq(rseq: usize, rseq_len: usize, flags: usize) -> isize { + if rseq == 0 { + return 0; + } + if flags != 0 { + return -EINVAL; + } + if rseq_len != 32 { + return -EINVAL; + } + + unsafe { + let ptr = rseq as *mut u8; + core::ptr::write_bytes(ptr, 0, rseq_len); + } + 0 +} + +fn sys_newfstatat(dirfd: isize, path: *const u8, stat: *mut u8, flags: usize) -> isize { + if path.is_null() || stat.is_null() { + return -EFAULT; + } + if is_dot_path(path) { + static ROOT_PATH: [u8; 2] = [b'/', 0]; + return vfs::stat_user_path(ROOT_PATH.as_ptr(), stat); + } + if unsafe { path.read() } == 0 && flags & AT_EMPTY_PATH != 0 { + if dirfd == vfs::AT_FDCWD { + static ROOT_PATH: [u8; 2] = [b'/', 0]; + return vfs::stat_user_path(ROOT_PATH.as_ptr(), stat); + } + if dirfd >= 0 { + return sys_fstat(dirfd as usize, stat); + } + return -EBADF; + } + vfs::statat_user_path(dirfd, path, stat) +} + +fn is_dot_path(path: *const u8) -> bool { + if path.is_null() { + return false; + } + unsafe { path.read() == b'.' && path.add(1).read() == 0 } +} + +fn sys_getrandom(buffer: *mut u8, len: usize, _flags: usize) -> isize { + if buffer.is_null() { + return -EFAULT; + } + unsafe { + for index in 0..len { + buffer.add(index).write((0xa5u8).wrapping_add(index as u8)); + } + } + len as isize +} + +fn sys_prlimit64( + _pid: usize, + _resource: usize, + _new_limit: *const u8, + old_limit: *mut u8, +) -> isize { + if !old_limit.is_null() { + unsafe { + (old_limit as *mut u64).write(u64::MAX); + (old_limit.add(8) as *mut u64).write(u64::MAX); + } + } + 0 +} + +fn sys_exit(code: isize, frame: *mut TrapFrame) -> isize { + crate::console::reset_terminal(); + if !frame.is_null() && task::exit_current(code, unsafe { &mut *frame }) { + restore_user_heap(); + return unsafe { (*frame).rax as isize }; + } + + loop { + unsafe { + core::arch::asm!("sti; hlt", options(nomem, nostack, preserves_flags)); + } + } +} + +pub fn snapshot_user_heap() { + unsafe { + snapshot_user_elf_image(); + SAVED_PROGRAM_BREAK_START = PROGRAM_BREAK_START; + SAVED_PROGRAM_BREAK = PROGRAM_BREAK; + SAVED_PROGRAM_BREAK_LIMIT = PROGRAM_BREAK_LIMIT; + if PROGRAM_BREAK_START == 0 || PROGRAM_BREAK_LIMIT <= PROGRAM_BREAK_START { + return; + } + if SAVED_PROGRAM_BREAK_IMAGE.is_null() { + if MEMORY.is_null() { + return; + } + let Some(ptr) = (*MEMORY).alloc_pages(SAVED_PROGRAM_BREAK_IMAGE_SIZE / PAGE_SIZE) + else { + return; + }; + SAVED_PROGRAM_BREAK_IMAGE = ptr; + } + + let len = (PROGRAM_BREAK_LIMIT - PROGRAM_BREAK_START).min(SAVED_PROGRAM_BREAK_IMAGE_SIZE); + core::ptr::copy_nonoverlapping( + PROGRAM_BREAK_START as *const u8, + SAVED_PROGRAM_BREAK_IMAGE, + len, + ); + } +} + +pub fn restore_user_heap() { + unsafe { + restore_user_elf_image(); + if SAVED_PROGRAM_BREAK_START != 0 + && SAVED_PROGRAM_BREAK_LIMIT > SAVED_PROGRAM_BREAK_START + && !SAVED_PROGRAM_BREAK_IMAGE.is_null() + { + let len = (SAVED_PROGRAM_BREAK_LIMIT - SAVED_PROGRAM_BREAK_START) + .min(SAVED_PROGRAM_BREAK_IMAGE_SIZE); + core::ptr::copy_nonoverlapping( + SAVED_PROGRAM_BREAK_IMAGE, + SAVED_PROGRAM_BREAK_START as *mut u8, + len, + ); + } + PROGRAM_BREAK_START = SAVED_PROGRAM_BREAK_START; + PROGRAM_BREAK = SAVED_PROGRAM_BREAK; + PROGRAM_BREAK_LIMIT = SAVED_PROGRAM_BREAK_LIMIT; + } +} + +unsafe fn snapshot_user_elf_image() { + unsafe { + if MEMORY.is_null() { + return; + } + if SAVED_USER_ELF_IMAGE.is_null() { + let Some(ptr) = (*MEMORY).alloc_pages(USER_ELF_SNAPSHOT_SIZE / PAGE_SIZE) else { + return; + }; + SAVED_USER_ELF_IMAGE = ptr; + } + core::ptr::copy_nonoverlapping( + USER_ELF_SNAPSHOT_START as *const u8, + SAVED_USER_ELF_IMAGE, + USER_ELF_SNAPSHOT_SIZE, + ); + } +} + +unsafe fn restore_user_elf_image() { + unsafe { + if SAVED_USER_ELF_IMAGE.is_null() { + return; + } + core::ptr::copy_nonoverlapping( + SAVED_USER_ELF_IMAGE, + USER_ELF_SNAPSHOT_START as *mut u8, + USER_ELF_SNAPSHOT_SIZE, + ); + } +} + +unsafe fn write_cstr(dst: *mut u8, src: &[u8]) { + unsafe { + core::ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len()); + dst.add(src.len()).write(0); + } +} + +fn trace_unsupported_syscall(number: usize) { + unsafe { + if LAST_UNSUPPORTED_SYSCALL == number { + return; + } + LAST_UNSUPPORTED_SYSCALL = number; + } + + console::write(b"unsupported syscall "); + write_usize(number); + console::write(b"\n"); +} + +fn write_usize(mut value: usize) { + let mut buffer = [0u8; 20]; + let mut index = buffer.len(); + + if value == 0 { + console::write(b"0"); + return; + } + + while value > 0 { + index -= 1; + buffer[index] = b'0' + (value % 10) as u8; + value /= 10; + } + + console::write(&buffer[index..]); +} + +fn trace_alloc(name: &[u8], addr: usize, len: usize) { + if !TRACE_ALLOC { + return; + } + console::write(name); + console::write(b" "); + write_hex_usize(addr); + console::write(b" "); + write_hex_usize(len); + console::write(b"\n"); +} + +fn trace_ioctl(request: usize, arg: usize, frame: *mut TrapFrame) { + if !TRACE_IOCTL { + return; + } + unsafe { + if IOCTL_TRACE_COUNT >= 8 { + return; + } + IOCTL_TRACE_COUNT += 1; + } + console::write(b"ioctl req="); + write_hex_usize(request); + console::write(b" arg="); + write_hex_usize(arg); + if !frame.is_null() { + let frame = unsafe { &*frame }; + console::write(b" ret="); + write_hex_usize(frame.rcx); + console::write(b" rsp="); + write_hex_usize(frame.rsp); + } + console::write(b"\n"); +} + +fn trace_fs_result(name: &[u8], arg0: usize, arg1: usize, result: isize) -> isize { + if !TRACE_FS_FDS { + return result; + } + console::write(name); + console::write(b" a0="); + write_hex_usize(arg0); + console::write(b" a1="); + write_hex_usize(arg1); + console::write(b" -> "); + if result < 0 { + console::write(b"-"); + write_usize((-result) as usize); + } else { + write_usize(result as usize); + } + console::write(b"\n"); + result +} + +fn write_hex_usize(value: usize) { + console::write(b"0x"); + let mut shift = usize::BITS as usize; + let mut started = false; + while shift > 0 { + shift -= 4; + let digit = ((value >> shift) & 0xf) as u8; + if digit != 0 || started || shift == 0 { + started = true; + let ch = if digit < 10 { + b'0' + digit + } else { + b'a' + digit - 10 + }; + console::write(&[ch]); + } + } +} + +unsafe fn read_msr(msr: u32) -> u64 { + let low: u32; + let high: u32; + unsafe { + asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high, options(nomem, nostack)); + } + ((high as u64) << 32) | low as u64 +} + +unsafe fn write_msr(msr: u32, value: u64) { + unsafe { + asm!( + "wrmsr", + in("ecx") msr, + in("eax") value as u32, + in("edx") (value >> 32) as u32, + options(nomem, nostack) + ); + } +} diff --git a/src/task.rs b/src/task.rs new file mode 100644 index 0000000..8604f11 --- /dev/null +++ b/src/task.rs @@ -0,0 +1,178 @@ +use crate::{syscall::TrapFrame, user}; + +const MAX_TASKS: usize = 64; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum TaskState { + Empty, + Running, + Blocked, + Zombie, +} + +#[derive(Clone, Copy)] +struct Task { + pid: usize, + ppid: usize, + state: TaskState, + exit_code: isize, + vfork_parent: bool, +} + +impl Task { + const fn empty() -> Self { + Self { + pid: 0, + ppid: 0, + state: TaskState::Empty, + exit_code: 0, + vfork_parent: false, + } + } +} + +static mut TASKS: [Task; MAX_TASKS] = [Task::empty(); MAX_TASKS]; +static mut NEXT_PID: usize = 2; +static mut CURRENT_PID: usize = 1; +static mut SAVED_VFORK_PARENT: TrapFrame = TrapFrame::zero(); +static mut SAVED_VFORK_PARENT_PID: usize = 0; +static mut SAVED_VFORK_CHILD_PID: usize = 0; + +pub unsafe fn init() { + unsafe { + TASKS[0] = Task { + pid: 1, + ppid: 0, + state: TaskState::Running, + exit_code: 0, + vfork_parent: false, + }; + NEXT_PID = 2; + CURRENT_PID = 1; + SAVED_VFORK_PARENT = TrapFrame::zero(); + SAVED_VFORK_PARENT_PID = 0; + SAVED_VFORK_CHILD_PID = 0; + } +} + +pub fn current_pid() -> usize { + unsafe { CURRENT_PID } +} + +pub fn current_ppid() -> usize { + unsafe { + find_task(CURRENT_PID) + .map(|index| TASKS[index].ppid) + .unwrap_or(0) + } +} + +pub fn is_current_vfork_child() -> bool { + unsafe { SAVED_VFORK_CHILD_PID != 0 && CURRENT_PID == SAVED_VFORK_CHILD_PID } +} + +pub fn fork_like(frame: &TrapFrame) -> isize { + vfork(frame) +} + +pub fn vfork(frame: &TrapFrame) -> isize { + unsafe { + let Some(slot) = find_empty_slot() else { + return -11; + }; + let parent_pid = CURRENT_PID; + if SAVED_VFORK_PARENT_PID != 0 { + return -11; + } + let pid = NEXT_PID; + NEXT_PID += 1; + + if let Some(parent_index) = find_task(parent_pid) { + TASKS[parent_index].state = TaskState::Blocked; + TASKS[parent_index].vfork_parent = true; + } + + user::snapshot_current_image(); + SAVED_VFORK_PARENT = *frame; + SAVED_VFORK_PARENT.rax = pid; + SAVED_VFORK_PARENT_PID = parent_pid; + SAVED_VFORK_CHILD_PID = pid; + + TASKS[slot] = Task { + pid, + ppid: parent_pid, + state: TaskState::Running, + exit_code: 0, + vfork_parent: false, + }; + CURRENT_PID = pid; + 0 + } +} + +pub fn wait4(pid: isize, status: *mut i32) -> isize { + unsafe { + for index in 0..MAX_TASKS { + let task = TASKS[index]; + if task.state != TaskState::Zombie || task.ppid != CURRENT_PID { + continue; + } + if pid > 0 && task.pid != pid as usize { + continue; + } + if !status.is_null() { + status.write((task.exit_code as i32) << 8); + } + TASKS[index] = Task::empty(); + return task.pid as isize; + } + } + -10 +} + +pub fn exit_current(code: isize, frame: &mut TrapFrame) -> bool { + unsafe { + let exiting_pid = CURRENT_PID; + if let Some(index) = find_task(CURRENT_PID) { + TASKS[index].state = TaskState::Zombie; + TASKS[index].exit_code = code; + } + if SAVED_VFORK_CHILD_PID == exiting_pid && SAVED_VFORK_PARENT_PID != 0 { + let parent_pid = SAVED_VFORK_PARENT_PID; + user::restore_current_image(); + *frame = SAVED_VFORK_PARENT; + if let Some(parent_index) = find_task(parent_pid) { + TASKS[parent_index].state = TaskState::Running; + TASKS[parent_index].vfork_parent = false; + } + CURRENT_PID = parent_pid; + SAVED_VFORK_PARENT = TrapFrame::zero(); + SAVED_VFORK_PARENT_PID = 0; + SAVED_VFORK_CHILD_PID = 0; + return true; + } + } + false +} + +unsafe fn find_empty_slot() -> Option { + unsafe { + for index in 0..MAX_TASKS { + if TASKS[index].state == TaskState::Empty { + return Some(index); + } + } + } + None +} + +unsafe fn find_task(pid: usize) -> Option { + unsafe { + for index in 0..MAX_TASKS { + if TASKS[index].state != TaskState::Empty && TASKS[index].pid == pid { + return Some(index); + } + } + } + None +} diff --git a/src/tty.rs b/src/tty.rs new file mode 100644 index 0000000..a609815 --- /dev/null +++ b/src/tty.rs @@ -0,0 +1,532 @@ +use crate::font::{GLYPH_HEIGHT, GLYPH_WIDTH, HANKAKU}; +use crate::framebuffer::{Framebuffer, Rgb}; + +const FG: Rgb = Rgb { + red: 0xaa, + green: 0xaa, + blue: 0xaa, +}; + +const BG: Rgb = Rgb { + red: 0x00, + green: 0x00, + blue: 0x00, +}; + +const ANSI_COLORS: [Rgb; 8] = [ + Rgb { + red: 0x00, + green: 0x00, + blue: 0x00, + }, + Rgb { + red: 0xaa, + green: 0x00, + blue: 0x00, + }, + Rgb { + red: 0x00, + green: 0xaa, + blue: 0x00, + }, + Rgb { + red: 0xaa, + green: 0x55, + blue: 0x00, + }, + Rgb { + red: 0x00, + green: 0x00, + blue: 0xaa, + }, + Rgb { + red: 0xaa, + green: 0x00, + blue: 0xaa, + }, + Rgb { + red: 0x00, + green: 0xaa, + blue: 0xaa, + }, + Rgb { + red: 0xaa, + green: 0xaa, + blue: 0xaa, + }, +]; + +const ANSI_BRIGHT_COLORS: [Rgb; 8] = [ + Rgb { + red: 0x55, + green: 0x55, + blue: 0x55, + }, + Rgb { + red: 0xff, + green: 0x55, + blue: 0x55, + }, + Rgb { + red: 0x55, + green: 0xff, + blue: 0x55, + }, + Rgb { + red: 0xff, + green: 0xff, + blue: 0x55, + }, + Rgb { + red: 0x55, + green: 0x55, + blue: 0xff, + }, + Rgb { + red: 0xff, + green: 0x55, + blue: 0xff, + }, + Rgb { + red: 0x55, + green: 0xff, + blue: 0xff, + }, + Rgb { + red: 0xff, + green: 0xff, + blue: 0xff, + }, +]; + +#[derive(Clone, Copy)] +enum EscapeState { + Ground, + Escape, + Csi { + params: [u16; 8], + count: usize, + current: u16, + }, +} + +pub struct Tty { + framebuffer: Framebuffer, + cursor_x: usize, + cursor_y: usize, + saved_cursor_x: usize, + saved_cursor_y: usize, + columns: usize, + rows: usize, + scroll_top: usize, + scroll_bottom: usize, + fg: Rgb, + bg: Rgb, + bold: bool, + escape_state: EscapeState, +} + +impl Tty { + pub fn new(framebuffer: Framebuffer) -> Self { + let columns = framebuffer.width() / GLYPH_WIDTH; + let rows = framebuffer.height() / GLYPH_HEIGHT; + + Self { + framebuffer, + cursor_x: 0, + cursor_y: 0, + saved_cursor_x: 0, + saved_cursor_y: 0, + columns, + rows, + scroll_top: 0, + scroll_bottom: rows.saturating_sub(1), + fg: FG, + bg: BG, + bold: false, + escape_state: EscapeState::Ground, + } + } + + pub fn clear(&mut self) { + self.framebuffer.clear(self.bg); + self.cursor_x = 0; + self.cursor_y = 0; + self.reset_scroll_region(); + } + + pub fn columns(&self) -> usize { + self.columns + } + + pub fn rows(&self) -> usize { + self.rows + } + + pub fn reset(&mut self) { + self.reset_colors(); + self.reset_scroll_region(); + self.cursor_x = 0; + self.cursor_y = 0; + self.saved_cursor_x = 0; + self.saved_cursor_y = 0; + self.escape_state = EscapeState::Ground; + } + + pub fn write_bytes(&mut self, bytes: &[u8]) { + for byte in bytes { + self.put_char(*byte); + } + } + + pub fn put_char(&mut self, ch: u8) { + if self.handle_escape(ch) { + return; + } + + match ch { + b'\r' => self.cursor_x = 0, + b'\n' => self.newline(), + b'\t' => self.tab(), + 0x08 => self.backspace(), + 0x7f => self.backspace(), + ch => { + if self.cursor_x >= self.columns { + self.newline(); + } + + self.draw_char(ch, self.cursor_x, self.cursor_y, self.fg, self.bg); + self.cursor_x += 1; + } + } + } + + pub fn backspace(&mut self) { + if self.cursor_x == 0 { + return; + } + + self.cursor_x -= 1; + self.draw_char(b' ', self.cursor_x, self.cursor_y, self.fg, self.bg); + } + + fn newline(&mut self) { + self.cursor_x = 0; + if self.cursor_y == self.scroll_bottom { + self.scroll_one_line(); + } else if self.cursor_y + 1 >= self.rows { + self.cursor_y = self.rows.saturating_sub(1); + } else { + self.cursor_y += 1; + } + } + + fn scroll_one_line(&mut self) { + let top = self.scroll_top * GLYPH_HEIGHT; + let bottom = (self.scroll_bottom + 1) * GLYPH_HEIGHT; + self.framebuffer + .scroll_region_up(top, bottom, GLYPH_HEIGHT, self.bg); + } + + fn handle_escape(&mut self, ch: u8) -> bool { + match self.escape_state { + EscapeState::Ground => { + if ch == 0x1b { + self.escape_state = EscapeState::Escape; + true + } else { + false + } + } + EscapeState::Escape => { + self.escape_state = if ch == b'[' { + EscapeState::Csi { + params: [0; 8], + count: 0, + current: 0, + } + } else { + match ch { + b'7' => self.save_cursor(), + b'8' => self.restore_cursor(), + b'c' => self.reset_terminal(), + _ => {} + } + EscapeState::Ground + }; + true + } + EscapeState::Csi { + mut params, + mut count, + mut current, + } => { + match ch { + b'0'..=b'9' => { + current = current + .saturating_mul(10) + .saturating_add((ch - b'0') as u16); + self.escape_state = EscapeState::Csi { + params, + count, + current, + }; + } + b';' => { + if count < params.len() { + params[count] = current; + count += 1; + } + self.escape_state = EscapeState::Csi { + params, + count, + current: 0, + }; + } + b'?' => { + self.escape_state = EscapeState::Csi { + params, + count, + current, + }; + } + b'm' => { + if count < params.len() { + params[count] = current; + count += 1; + } + self.apply_sgr(¶ms[..count]); + self.escape_state = EscapeState::Ground; + } + b'@' | b'A' | b'B' | b'C' | b'D' | b'G' | b'H' | b'F' | b'L' | b'M' | b'P' + | b'X' | b'd' | b'f' | b'J' | b'K' | b'r' | b's' | b'u' | b'h' | b'l' => { + if count < params.len() { + params[count] = current; + count += 1; + } + self.apply_csi(ch, ¶ms[..count]); + self.escape_state = EscapeState::Ground; + } + _ => self.escape_state = EscapeState::Ground, + } + true + } + } + } + + fn tab(&mut self) { + let next_tab = (self.cursor_x + 8) & !7; + self.cursor_x = next_tab.min(self.columns.saturating_sub(1)); + } + + fn apply_csi(&mut self, command: u8, params: &[u16]) { + match command { + b'A' => self.cursor_y = self.cursor_y.saturating_sub(csi_param(params, 0, 1)), + b'B' => self.cursor_y = (self.cursor_y + csi_param(params, 0, 1)).min(self.rows - 1), + b'C' => self.cursor_x = (self.cursor_x + csi_param(params, 0, 1)).min(self.columns - 1), + b'D' => self.cursor_x = self.cursor_x.saturating_sub(csi_param(params, 0, 1)), + b'G' => { + let col = csi_param(params, 0, 1).saturating_sub(1); + self.cursor_x = col.min(self.columns - 1); + } + b'H' | b'f' => { + let row = csi_param(params, 0, 1).saturating_sub(1); + let col = csi_param(params, 1, 1).saturating_sub(1); + self.cursor_y = row.min(self.rows - 1); + self.cursor_x = col.min(self.columns - 1); + } + b'F' => { + self.cursor_y = self.rows - 1; + self.cursor_x = 0; + } + b'd' => { + let row = csi_param(params, 0, 1).saturating_sub(1); + self.cursor_y = row.min(self.rows - 1); + } + b'J' => self.erase_display(csi_param(params, 0, 0)), + b'K' => self.erase_line(csi_param(params, 0, 0)), + b'X' => self.erase_chars(csi_param(params, 0, 1)), + b's' => self.save_cursor(), + b'u' => self.restore_cursor(), + b'r' => self.set_scroll_region(params), + b'@' | b'L' | b'M' | b'P' => {} + b'h' | b'l' => self.set_private_mode(params, command == b'h'), + _ => {} + } + } + + fn set_private_mode(&mut self, params: &[u16], _enabled: bool) { + for param in params { + match *param { + 47 | 1047 | 1049 => { + self.reset_scroll_region(); + self.cursor_x = 0; + self.cursor_y = 0; + } + 25 => {} + _ => {} + } + } + } + + fn set_scroll_region(&mut self, params: &[u16]) { + let top = csi_param(params, 0, 1).saturating_sub(1); + let bottom = if params.len() >= 2 && params[1] != 0 { + params[1] as usize - 1 + } else { + self.rows.saturating_sub(1) + }; + if top < bottom && bottom < self.rows { + self.scroll_top = top; + self.scroll_bottom = bottom; + self.cursor_x = 0; + self.cursor_y = top; + } else { + self.reset_scroll_region(); + } + } + + fn reset_scroll_region(&mut self) { + self.scroll_top = 0; + self.scroll_bottom = self.rows.saturating_sub(1); + } + + fn erase_display(&mut self, mode: usize) { + match mode { + 0 => { + self.erase_line(0); + let y = (self.cursor_y + 1) * GLYPH_HEIGHT; + self.framebuffer.fill_rect( + 0, + y, + self.framebuffer.width(), + self.framebuffer.height(), + self.bg, + ); + } + 1 => { + self.erase_line(1); + self.framebuffer.fill_rect( + 0, + 0, + self.framebuffer.width(), + self.cursor_y * GLYPH_HEIGHT, + self.bg, + ); + } + 2 | 3 => self.clear(), + _ => {} + } + } + + fn erase_line(&mut self, mode: usize) { + let y = self.cursor_y * GLYPH_HEIGHT; + match mode { + 0 => self.framebuffer.fill_rect( + self.cursor_x * GLYPH_WIDTH, + y, + (self.columns - self.cursor_x) * GLYPH_WIDTH, + GLYPH_HEIGHT, + self.bg, + ), + 1 => self.framebuffer.fill_rect( + 0, + y, + (self.cursor_x + 1) * GLYPH_WIDTH, + GLYPH_HEIGHT, + self.bg, + ), + 2 => { + self.framebuffer + .fill_rect(0, y, self.columns * GLYPH_WIDTH, GLYPH_HEIGHT, self.bg) + } + _ => {} + } + } + + fn erase_chars(&mut self, count: usize) { + let cells = count.min(self.columns.saturating_sub(self.cursor_x)); + self.framebuffer.fill_rect( + self.cursor_x * GLYPH_WIDTH, + self.cursor_y * GLYPH_HEIGHT, + cells * GLYPH_WIDTH, + GLYPH_HEIGHT, + self.bg, + ); + } + + fn save_cursor(&mut self) { + self.saved_cursor_x = self.cursor_x; + self.saved_cursor_y = self.cursor_y; + } + + fn restore_cursor(&mut self) { + self.cursor_x = self.saved_cursor_x.min(self.columns - 1); + self.cursor_y = self.saved_cursor_y.min(self.rows - 1); + } + + fn reset_terminal(&mut self) { + self.reset_colors(); + self.reset_scroll_region(); + self.clear(); + } + + fn apply_sgr(&mut self, params: &[u16]) { + if params.is_empty() { + self.reset_colors(); + return; + } + + for param in params { + match *param { + 0 => self.reset_colors(), + 1 => self.bold = true, + 22 => { + self.bold = false; + self.fg = FG; + } + 30..=37 => { + let index = (*param - 30) as usize; + self.fg = if self.bold { + ANSI_BRIGHT_COLORS[index] + } else { + ANSI_COLORS[index] + }; + } + 39 => self.fg = FG, + 40..=47 => self.bg = ANSI_COLORS[(*param - 40) as usize], + 49 => self.bg = BG, + 90..=97 => self.fg = ANSI_BRIGHT_COLORS[(*param - 90) as usize], + 100..=107 => self.bg = ANSI_BRIGHT_COLORS[(*param - 100) as usize], + _ => {} + } + } + } + + fn reset_colors(&mut self) { + self.fg = FG; + self.bg = BG; + self.bold = false; + } + + fn draw_char(&mut self, ch: u8, cell_x: usize, cell_y: usize, fg: Rgb, bg: Rgb) { + let glyph_base = ch as usize * GLYPH_HEIGHT; + let pixel_x = cell_x * GLYPH_WIDTH; + let pixel_y = cell_y * GLYPH_HEIGHT; + + for row in 0..GLYPH_HEIGHT { + let bitmap = HANKAKU[glyph_base + row]; + for col in 0..GLYPH_WIDTH { + let mask = 0x80 >> col; + let color = if bitmap & mask != 0 { fg } else { bg }; + self.framebuffer + .put_pixel(pixel_x + col, pixel_y + row, color); + } + } + } +} + +fn csi_param(params: &[u16], index: usize, default: usize) -> usize { + let value = params.get(index).copied().unwrap_or(0) as usize; + if value == 0 { default } else { value } +} diff --git a/src/user/mod.rs b/src/user/mod.rs new file mode 100644 index 0000000..77e4dd1 --- /dev/null +++ b/src/user/mod.rs @@ -0,0 +1,221 @@ +pub mod program; +pub mod stdio; + +use core::arch::asm; + +use crate::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; + +const USER_STACK_SIZE: usize = 16 * 4096; +const USER_ARG_AREA_SIZE: usize = 4096; +const AT_NULL: u64 = 0; +const AT_PHDR: u64 = 3; +const AT_PHENT: u64 = 4; +const AT_PHNUM: u64 = 5; +const AT_PAGESZ: u64 = 6; +const AT_BASE: u64 = 7; +const AT_FLAGS: u64 = 8; +const AT_ENTRY: u64 = 9; +const AT_UID: u64 = 11; +const AT_EUID: u64 = 12; +const AT_GID: u64 = 13; +const AT_EGID: u64 = 14; +const AT_PLATFORM: u64 = 15; +const AT_HWCAP: u64 = 16; +const AT_CLKTCK: u64 = 17; +const AT_SECURE: u64 = 23; +const AT_RANDOM: u64 = 25; +const AT_HWCAP2: u64 = 26; +const AT_EXECFN: u64 = 31; + +#[repr(C, align(16))] +struct UserStack([u8; USER_STACK_SIZE]); + +static mut USER_STACK: UserStack = UserStack([0; USER_STACK_SIZE]); +static mut USER_ARG_AREA: [u8; USER_ARG_AREA_SIZE] = [0; USER_ARG_AREA_SIZE]; +static mut SAVED_USER_STACK: UserStack = UserStack([0; USER_STACK_SIZE]); +static mut SAVED_USER_ARG_AREA: [u8; USER_ARG_AREA_SIZE] = [0; USER_ARG_AREA_SIZE]; + +#[derive(Clone, Copy)] +pub struct UserAux { + pub entry: u64, + pub phdr: u64, + pub phent: u64, + pub phnum: u64, +} + +pub unsafe fn enter() -> ! { + let entry = program::zeroos_user_main as *const () as usize as u64; + let stack_top = (&raw const USER_STACK).cast::() as u64 + USER_STACK_SIZE as u64; + + unsafe { + enter_raw(entry, stack_top); + } +} + +pub unsafe fn enter_elf_with_args(entry: u64, args: &[&[u8]], aux: UserAux) -> ! { + let stack_top = unsafe { build_initial_stack(args, aux) }; + + unsafe { + enter_raw(entry, stack_top); + } +} + +pub unsafe fn prepare_elf_stack(args: &[&[u8]], aux: UserAux) -> u64 { + unsafe { build_initial_stack(args, aux) } +} + +pub unsafe fn snapshot_current_image() { + unsafe { + core::ptr::copy_nonoverlapping( + (&raw const USER_STACK).cast::(), + (&raw mut SAVED_USER_STACK).cast::(), + USER_STACK_SIZE, + ); + core::ptr::copy_nonoverlapping( + (&raw const USER_ARG_AREA).cast::(), + (&raw mut SAVED_USER_ARG_AREA).cast::(), + USER_ARG_AREA_SIZE, + ); + } +} + +pub unsafe fn restore_current_image() { + unsafe { + core::ptr::copy_nonoverlapping( + (&raw const SAVED_USER_STACK).cast::(), + (&raw mut USER_STACK).cast::(), + USER_STACK_SIZE, + ); + core::ptr::copy_nonoverlapping( + (&raw const SAVED_USER_ARG_AREA).cast::(), + (&raw mut USER_ARG_AREA).cast::(), + USER_ARG_AREA_SIZE, + ); + } +} + +unsafe fn enter_raw(entry: u64, stack_top: u64) -> ! { + unsafe { + asm!( + "push {user_ss}", + "push {user_rsp}", + "pushfq", + "or qword ptr [rsp], 0x200", + "push {user_cs}", + "push {entry}", + "xor rax, rax", + "xor rbx, rbx", + "xor rcx, rcx", + "xor rdx, rdx", + "xor rbp, rbp", + "xor rsi, rsi", + "xor rdi, rdi", + "xor r8, r8", + "xor r9, r9", + "xor r10, r10", + "xor r11, r11", + "xor r12, r12", + "xor r13, r13", + "xor r14, r14", + "xor r15, r15", + "iretq", + user_ss = in(reg) USER_DATA_SELECTOR as u64, + user_rsp = in(reg) stack_top, + user_cs = in(reg) USER_CODE_SELECTOR as u64, + entry = in(reg) entry, + options(noreturn), + ); + } +} + +unsafe fn build_initial_stack(args: &[&[u8]], aux: UserAux) -> u64 { + let stack_base = (&raw mut USER_STACK).cast::() as usize; + let mut sp = stack_base + USER_STACK_SIZE; + let arg_base = (&raw mut USER_ARG_AREA).cast::(); + let argc = args.len().min(16); + let mut arg_ptrs = [0u64; 16]; + let mut arg_offset = 0usize; + + for index in 0..argc { + let arg = args[index]; + let remaining = USER_ARG_AREA_SIZE.saturating_sub(arg_offset); + if remaining == 0 { + break; + } + let copy_len = arg.len().min(remaining - 1); + unsafe { + core::ptr::copy_nonoverlapping(arg.as_ptr(), arg_base.add(arg_offset), copy_len); + *arg_base.add(arg_offset + copy_len) = 0; + } + arg_ptrs[index] = unsafe { arg_base.add(arg_offset) as u64 }; + arg_offset += copy_len + 1; + } + + let random_ptr = unsafe { arg_base.add(arg_offset) as u64 }; + let random = [ + 0x37, 0x91, 0x42, 0xa5, 0xc3, 0x5e, 0x10, 0x77, 0x21, 0x6d, 0x9b, 0xe0, 0x4c, 0x18, 0xf2, + 0xaa, + ]; + unsafe { + core::ptr::copy_nonoverlapping(random.as_ptr(), arg_base.add(arg_offset), random.len()); + } + arg_offset += random.len(); + + let platform = b"x86_64"; + let platform_ptr = unsafe { arg_base.add(arg_offset) as u64 }; + unsafe { + core::ptr::copy_nonoverlapping(platform.as_ptr(), arg_base.add(arg_offset), platform.len()); + *arg_base.add(arg_offset + platform.len()) = 0; + } + + let execfn_ptr = arg_ptrs[0]; + let aux_pairs = [ + (AT_PHDR, aux.phdr), + (AT_PHENT, aux.phent), + (AT_PHNUM, aux.phnum), + (AT_PAGESZ, 4096), + (AT_BASE, 0), + (AT_FLAGS, 0), + (AT_ENTRY, aux.entry), + (AT_UID, 0), + (AT_EUID, 0), + (AT_GID, 0), + (AT_EGID, 0), + (AT_PLATFORM, platform_ptr), + (AT_HWCAP, 0), + (AT_CLKTCK, 100), + (AT_SECURE, 0), + (AT_RANDOM, random_ptr), + (AT_HWCAP2, 0), + (AT_EXECFN, execfn_ptr), + ]; + + let push_count = 1 + argc + 1 + 1 + aux_pairs.len() * 2 + 2; + if push_count % 2 != 0 { + push_u64(&mut sp, 0); + } + + // Linux/x86-64 process entry stack: + // argc, argv[], NULL, envp NULL, auxv AT_NULL. + push_u64(&mut sp, 0); // AT_NULL value + push_u64(&mut sp, AT_NULL); // AT_NULL type + for &(kind, value) in aux_pairs.iter().rev() { + push_u64(&mut sp, value); + push_u64(&mut sp, kind); + } + push_u64(&mut sp, 0); // envp terminator + push_u64(&mut sp, 0); // argv terminator + for index in (0..argc).rev() { + push_u64(&mut sp, arg_ptrs[index]); + } + push_u64(&mut sp, argc as u64); + + sp as u64 +} + +fn push_u64(sp: &mut usize, value: u64) { + *sp -= core::mem::size_of::(); + unsafe { + (*sp as *mut u64).write(value); + } +} diff --git a/src/user/program.rs b/src/user/program.rs new file mode 100644 index 0000000..406fcdc --- /dev/null +++ b/src/user/program.rs @@ -0,0 +1,13 @@ +use super::stdio; + +#[unsafe(no_mangle)] +pub extern "C" fn zeroos_user_main() -> ! { + stdio::puts(b"\x1b[92mHello, World!\x1b[0m"); + stdio::puts(b"\x1b[96mPOSIX stdio via int 0x80\x1b[0m"); + + loop { + unsafe { + core::arch::asm!("pause", options(nomem, nostack, preserves_flags)); + } + } +} diff --git a/src/user/stdio.rs b/src/user/stdio.rs new file mode 100644 index 0000000..77cc326 --- /dev/null +++ b/src/user/stdio.rs @@ -0,0 +1,119 @@ +use core::arch::asm; + +use crate::syscall::{ + STDIN_FILENO, STDOUT_FILENO, SYS_CLOSE, SYS_EXIT, SYS_FSTAT, SYS_GETPID, SYS_ISATTY, SYS_LSEEK, + SYS_OPEN, SYS_READ, SYS_WRITE, +}; + +pub fn write(fd: usize, buffer: &[u8]) -> isize { + int80_syscall3(SYS_WRITE, fd, buffer.as_ptr() as usize, buffer.len()) +} + +#[allow(dead_code)] +pub fn read(fd: usize, buffer: &mut [u8]) -> isize { + int80_syscall3(SYS_READ, fd, buffer.as_mut_ptr() as usize, buffer.len()) +} + +pub fn putchar(ch: u8) -> isize { + write(STDOUT_FILENO, &[ch]) +} + +pub fn puts(text: &[u8]) -> isize { + let written = write(STDOUT_FILENO, text); + let _ = putchar(b'\n'); + written +} + +#[allow(dead_code)] +pub fn close(fd: usize) -> isize { + int80_syscall3(SYS_CLOSE, fd, 0, 0) +} + +#[allow(dead_code)] +pub fn open(path: *const u8, flags: usize, mode: usize) -> isize { + int80_syscall3(SYS_OPEN, path as usize, flags, mode) +} + +#[allow(dead_code)] +pub fn fstat(fd: usize, stat: *mut u8) -> isize { + int80_syscall3(SYS_FSTAT, fd, stat as usize, 0) +} + +#[allow(dead_code)] +pub fn lseek(fd: usize, offset: usize, whence: usize) -> isize { + int80_syscall3(SYS_LSEEK, fd, offset, whence) +} + +#[allow(dead_code)] +pub fn isatty(fd: usize) -> isize { + int80_syscall3(SYS_ISATTY, fd, 0, 0) +} + +#[allow(dead_code)] +pub fn getpid() -> isize { + int80_syscall3(SYS_GETPID, 0, 0, 0) +} + +#[allow(dead_code)] +pub fn exit(code: isize) -> ! { + let _ = int80_syscall3(SYS_EXIT, code as usize, 0, 0); + loop { + unsafe { + core::arch::asm!("pause", options(nomem, nostack, preserves_flags)); + } + } +} + +#[allow(dead_code)] +pub fn getchar() -> Option { + let mut byte = [0u8; 1]; + if read(STDIN_FILENO, &mut byte) == 1 { + Some(byte[0]) + } else { + None + } +} + +#[allow(dead_code)] +#[allow(dead_code)] +fn syscall3(number: usize, arg0: usize, arg1: usize, arg2: usize) -> isize { + let ret: isize; + unsafe { + asm!( + "syscall", + inlateout("rax") number as isize => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + lateout("rcx") _, + lateout("r11") _, + options(nostack), + ); + } + ret +} + +#[allow(dead_code)] +pub fn int80_write(fd: usize, buffer: &[u8]) -> isize { + int80_syscall3(SYS_WRITE, fd, buffer.as_ptr() as usize, buffer.len()) +} + +#[allow(dead_code)] +pub fn int80_read(fd: usize, buffer: &mut [u8]) -> isize { + int80_syscall3(SYS_READ, fd, buffer.as_mut_ptr() as usize, buffer.len()) +} + +fn int80_syscall3(number: usize, arg0: usize, arg1: usize, arg2: usize) -> isize { + let ret: isize; + unsafe { + asm!( + "int 0x80", + inlateout("rax") number as isize => ret, + in("rdi") arg0, + in("rsi") arg1, + in("rdx") arg2, + options(nostack, preserves_flags), + ); + } + ret +}