├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── Vagrantfile ├── autobuild.sh ├── build.rs ├── build_config ├── configure.py ├── constants │ ├── 0_misc.toml │ ├── 1_boot.toml │ ├── 1_process.toml │ └── _options.toml ├── files │ ├── keycodes.json │ ├── keymap.json │ ├── pci_devices.json │ └── startup_services.json ├── initrd_files.txt ├── linker.ld ├── ninja_syntax.py └── validate_build.py ├── d7os.json ├── dbgenv_config ├── bochs_base ├── bochs_debug ├── bochs_net ├── bochs_normal └── slirp.conf ├── docs ├── SysCalls.md ├── TODO.md ├── building-bochs.md ├── capabilities.md ├── devel_troubleshooting.md ├── plan.md ├── sockets.md └── timekeeping.md ├── libs ├── d7abi │ ├── Cargo.toml │ ├── README.md │ ├── d7abi.json │ ├── linker.ld │ └── src │ │ ├── ipc │ │ ├── mod.rs │ │ └── protocol │ │ │ ├── keyboard.rs │ │ │ ├── mod.rs │ │ │ └── service.rs │ │ ├── kernel_constants.rs │ │ ├── lib.rs │ │ ├── process.rs │ │ ├── processor_info.rs │ │ └── syscall │ │ ├── mod.rs │ │ └── types.rs ├── d7boot │ ├── Cargo.toml │ ├── linker.ld │ └── src │ │ ├── ata_pio.rs │ │ ├── entry.asm │ │ └── lib.rs ├── d7initrd │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── bin │ │ └── mkimg.rs │ │ └── lib.rs ├── d7keymap │ ├── Cargo.toml │ ├── examples │ │ ├── keycodes.json │ │ └── keymap.json │ ├── generate.py │ └── src │ │ └── lib.rs ├── d7net │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── arp.rs │ │ ├── builder │ │ │ ├── ipv4_tcp.rs │ │ │ ├── ipv4_udp.rs │ │ │ └── mod.rs │ │ ├── checksum.rs │ │ ├── dhcp.rs │ │ ├── dns.rs │ │ ├── ethernet.rs │ │ ├── ethertype.rs │ │ ├── ip_addr.rs │ │ ├── ip_protocol.rs │ │ ├── ipv4.rs │ │ ├── lib.rs │ │ ├── mac.rs │ │ ├── tcp │ │ │ ├── mod.rs │ │ │ └── segment.rs │ │ └── udp.rs │ └── tests │ │ ├── arp_simple.rs │ │ └── dns.rs ├── d7pci │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── device.rs │ │ ├── lib.rs │ │ ├── scan.rs │ │ └── util.rs ├── elf2bin │ ├── Cargo.toml │ ├── README.md │ ├── rustfmt.toml │ └── src │ │ └── main.rs ├── libd7 │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── allocator.rs │ │ ├── env.rs │ │ ├── ipc │ │ ├── mod.rs │ │ ├── select.rs │ │ ├── send.rs │ │ ├── server.rs │ │ └── subscription.rs │ │ ├── lib.rs │ │ ├── net │ │ ├── mod.rs │ │ ├── tcp │ │ │ ├── mod.rs │ │ │ └── socket_ipc_protocol.rs │ │ └── udp.rs │ │ ├── process.rs │ │ ├── random.rs │ │ ├── service.rs │ │ ├── syscall.rs │ │ └── time.rs └── qemu_driver │ ├── Cargo.toml │ └── src │ └── main.rs ├── modules ├── daemon_console │ ├── Cargo.toml │ └── src │ │ ├── keyboard.rs │ │ ├── lib.rs │ │ ├── vga.rs │ │ └── virtual_console.rs ├── daemon_fatfs │ ├── Cargo.toml │ └── src │ │ ├── cache.rs │ │ ├── cursor.rs │ │ ├── disk.rs │ │ └── lib.rs ├── daemon_net │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── arp_handler.rs │ │ ├── dhcp_client.rs │ │ ├── dns_resolver.rs │ │ ├── interface.rs │ │ ├── lib.rs │ │ ├── ports.rs │ │ └── tcp_handler.rs ├── daemon_service │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── daemon_syslog │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── driver_ata_pio │ ├── Cargo.toml │ └── src │ │ ├── ata_pio.rs │ │ └── lib.rs ├── driver_ne2k │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── ne2k.rs ├── driver_pci │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── lib.rs ├── driver_ps2 │ ├── Cargo.toml │ └── src │ │ ├── keyboard.rs │ │ ├── lib.rs │ │ └── state.rs ├── driver_rtc │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── driver_rtl8139 │ ├── Cargo.toml │ └── src │ │ ├── dma.rs │ │ ├── lib.rs │ │ └── rtl8139.rs └── examplebin │ ├── Cargo.toml │ └── src │ └── lib.rs ├── rustfmt.toml ├── src ├── asm_misc │ ├── process_common.asm │ └── smp_ap_startup.asm ├── boot │ ├── stage0.asm │ ├── stage1.asm │ └── stage2.asm ├── cpuid.rs ├── driver │ ├── acpi │ │ ├── mod.rs │ │ └── tables │ │ │ ├── dsdt.rs │ │ │ ├── fadt.rs │ │ │ ├── madt.rs │ │ │ ├── mod.rs │ │ │ └── rsdt.rs │ ├── ioapic │ │ ├── io.rs │ │ ├── lapic.rs │ │ └── mod.rs │ ├── mod.rs │ ├── pic.rs │ ├── pit.rs │ ├── tsc.rs │ ├── uart.rs │ └── vga_buffer.rs ├── entry.asm ├── initrd.rs ├── interrupt │ ├── gdt.rs │ ├── handler.rs │ ├── idt.rs │ ├── macros.rs │ ├── mod.rs │ └── tss.rs ├── ipc │ ├── event_queue.rs │ ├── list.rs │ ├── mod.rs │ ├── result.rs │ ├── ring │ │ └── mod.rs │ └── topic.rs ├── main.rs ├── memory │ ├── allocators │ │ ├── dma_allocator.rs │ │ ├── mod.rs │ │ ├── stack_allocator.rs │ │ └── syscall_stack.rs │ ├── area.rs │ ├── constants.rs │ ├── map.rs │ ├── mod.rs │ ├── paging │ │ ├── mapper.rs │ │ └── mod.rs │ ├── phys │ │ ├── allocator.rs │ │ ├── list.rs │ │ └── mod.rs │ ├── prelude.rs │ ├── process_common_code.rs │ ├── rust_heap.rs │ ├── utils.rs │ └── virt │ │ ├── allocator.rs │ │ └── mod.rs ├── multitasking │ ├── elf_loader.rs │ ├── mod.rs │ ├── process.rs │ ├── queues.rs │ ├── scheduler.rs │ └── waitfor.rs ├── random.rs ├── services │ ├── initrd.rs │ └── mod.rs ├── signature.rs ├── smp │ ├── data.rs │ ├── mod.rs │ └── sleep.rs ├── syscall │ └── mod.rs ├── syslog.rs ├── time.rs └── util │ ├── elf_parser.rs │ └── mod.rs └── tools ├── bochs_debug_sourcelines.py ├── hex2str.py ├── image.py ├── logparser.py ├── pseudo_elf_loader.py ├── setbyte.py ├── stack.py ├── str2hex.py └── zeropad.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.rs eol=lf 4 | *.asm eol=lf 5 | *.md eol=lf 6 | *.conf eol=lf 7 | *.cfg eol=lf 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: CI 4 | "on": 5 | push: 6 | paths-ignore: 7 | - 'docs/**' 8 | - 'dbgenv_config/**' 9 | - '**.md' 10 | env: 11 | CARGO_TERM_COLOR: always 12 | RUST_BACKTRACE: 1 13 | KERNEL_FEATURES: self-test 14 | jobs: 15 | check: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Install APT packages 21 | run: | 22 | sudo apt-get update \ 23 | && sudo apt-get install -y nasm binutils ninja-build qemu-system 24 | 25 | - name: Install latest nightly 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: nightly 29 | override: true 30 | components: rustfmt, clippy, rust-src 31 | 32 | - name: Install tooling 33 | uses: actions-rs/cargo@v1 34 | with: 35 | command: fmt 36 | args: --check 37 | 38 | - uses: actions/setup-python@v2 39 | with: 40 | python-version: '3.8' 41 | 42 | - name: "Install pip packages" 43 | run: python3 -m pip install toml types-toml natsort mypy 44 | 45 | - name: Clone constcodegen 46 | uses: actions/checkout@v2 47 | with: 48 | repository: Dentosal/constcodegen 49 | path: ./constcodegen 50 | 51 | - uses: actions/cache@v3 52 | id: cache-constcodegen 53 | with: 54 | path: ~/.cargo/bin/constcodegen 55 | key: ${{ hashFiles('./constcodegen/Cargo.toml') }}-${{ hashFiles('./constcodegen/src/**') }} 56 | 57 | - name: Build constcodegen 58 | if: steps.cache-constcodegen.outputs.cache-hit != 'true' 59 | working-directory: ./constcodegen 60 | run: cargo install --path . --force 61 | 62 | - run: python3 -m mypy build_config/configure.py 63 | 64 | - run: python3 build_config/configure.py && ninja 65 | 66 | - name: Run cargo fmt --check 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: fmt 70 | args: --check 71 | 72 | - name: Run cargo clippy 73 | uses: actions-rs/cargo@v1 74 | with: 75 | command: clippy 76 | 77 | - name: Run cargo test 78 | uses: actions-rs/cargo@v1 79 | with: 80 | command: test 81 | 82 | - name: Run OS self-test 83 | run: | 84 | qemu-system-x86_64 -cpu max -smp 4 -m 4G -no-reboot -display none \ 85 | -drive file=build/disk.img,format=raw,if=ide \ 86 | -nic none \ 87 | -serial file:qemu.log 88 | grep "Self-test successful" qemu.log || exit 1 89 | timeout-minutes: 5 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build 2 | build.ninja 3 | .ninja_* 4 | .vagrant/ 5 | target/ 6 | build/ 7 | *.o 8 | *.bin 9 | *.dat 10 | 11 | # Cargo 12 | Cargo.lock 13 | 14 | # RLS 15 | rls/ 16 | 17 | # Temp files 18 | tmp/ 19 | temp/ 20 | 21 | # Dumps 22 | dump*.txt 23 | *.dump 24 | 25 | # Build debugging 26 | objdump.txt 27 | readelf.txt 28 | 29 | # Log files 30 | *.log 31 | *.pcap 32 | slirp-pktlog.txt 33 | 34 | # Python scripting stuff 35 | __pycache__/ 36 | *.pyc 37 | 38 | # Editors 39 | .vscode/ 40 | 41 | # Misc 42 | .DS_Store 43 | win_home 44 | old_*/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7os" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | # https://doc.rust-lang.org/nightly/cargo/reference/features.html#feature-resolver-version-2 9 | resolver = "2" 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [profile.release.package.d7os] 18 | debug-assertions = true 19 | overflow-checks = true 20 | 21 | [profile.release.package.allogator] 22 | debug-assertions = true 23 | overflow-checks = true 24 | 25 | [features] 26 | self-test = [] # Run automatic tests and shutdown 27 | 28 | [dependencies] 29 | spin = "0.9" 30 | bitflags = "1.3" 31 | bit_field = "0.10.0" 32 | volatile = "0.2.6" 33 | static_assertions = "1.1" 34 | pinecone = "0.2" 35 | log = "0.4" 36 | 37 | acpi = "4.1.0" 38 | aml = "0.16.1" 39 | 40 | [dependencies.cpuio] 41 | git = "https://github.com/Dentosal/cpuio-rs" 42 | 43 | [dependencies.hashbrown] 44 | version = "0.11" 45 | features = ["nightly", "inline-more", "serde"] 46 | 47 | [dependencies.serde] 48 | version = "1.0" 49 | default-features = false 50 | features = ["alloc", "derive"] 51 | 52 | [dependencies.sha2] 53 | version = "0.10" 54 | default-features = false 55 | features = ["force-soft"] 56 | 57 | [dependencies.ed25519-dalek] 58 | # Open PR https://github.com/dalek-cryptography/ed25519-dalek/pull/189 59 | git = "https://github.com/Dentosal/ed25519-dalek" 60 | branch = "update-deps" 61 | default-features = false 62 | features = ["u64_backend", "rand", "sha2-force-soft"] 63 | 64 | [dependencies.rand_core] 65 | version = "0.6" 66 | default-features = false 67 | features = ["alloc"] 68 | 69 | [dependencies.lazy_static] 70 | version = "1.4" 71 | features = ["spin_no_std"] 72 | 73 | [dependencies.x86_64] 74 | git = "https://github.com/Dentosal/x86_64" 75 | features = ["use-serde"] 76 | 77 | [dependencies.d7abi] 78 | version = "*" 79 | path = "libs/d7abi" 80 | 81 | [dependencies.allogator] 82 | git = "https://github.com/Dentosal/allogator" 83 | features = ["extra-checks"] 84 | 85 | [dependencies.d7initrd] 86 | version = "*" 87 | path = "libs/d7initrd" 88 | 89 | [dev-dependencies] 90 | rand = "0.8" 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2022 Hannes Karppila 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure(2) do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://atlas.hashicorp.com/search. 15 | config.vm.box = "bento/ubuntu-18.04" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Provider-specific configuration so you can fine-tune various 23 | # backing providers for Vagrant. These expose provider-specific options. 24 | config.vm.provider "virtualbox" do |vb| 25 | # Forward GDB port 26 | # config.vm.network "forwarded_port", guest: 1234, host: 1234 27 | 28 | # Customize the amount of memory on the VM: 29 | vb.memory = "4096" 30 | end 31 | 32 | # Install rust osdev toolkit and some standard utilities 33 | # these run as user vagrant instead of root 34 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 35 | sudo apt-get update 36 | sudo apt-get upgrade -y 37 | sudo apt-get autoremove -y 38 | sudo apt-get install python3.7 python3.7-dev python3-pip -y 39 | sudo apt-get install vim git nasm ninja-build -y 40 | #sudo apt-get install xorriso -y 41 | sudo apt-get install texinfo flex bison python-dev ncurses-dev -y 42 | sudo apt-get install cmake libssl-dev -y 43 | 44 | sudo python3.7 -m pip install --upgrade pip 45 | sudo python3.7 -m pip install requests toml natsort 46 | 47 | #curl -sf https://raw.githubusercontent.com/phil-opp/binutils-gdb/rust-os/build-rust-os-gdb.sh | sh 48 | 49 | if hash rustup; then 50 | rustup update 51 | else 52 | curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly -y 53 | fi 54 | rustup component add rust-src 55 | rustup component add rustfmt 56 | 57 | export PATH="$HOME/.cargo/bin:$HOME/.bin:$PATH" 58 | 59 | git clone https://github.com/Dentosal/constcodegen.git /tmp/constcodegen || true 60 | cd /tmp/constcodegen 61 | git pull 62 | PYTHON_SYS_EXECUTABLE=python3.7 cargo install --path . --force 63 | cd - 64 | 65 | echo "export PATH="$HOME/.cargo/bin:$PATH"; cd /vagrant" >> $HOME/.bashrc 66 | SHELL 67 | end 68 | -------------------------------------------------------------------------------- /autobuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Flags: 5 | # -c to compile only 6 | # -r to run only 7 | # -g to run in vargrant 8 | # -u to run "vagrant up" 9 | # -v to open in VirtualBox 10 | # -b to open in Bochs 11 | # -d to use additional debug options 12 | 13 | flag_vagrant=0 14 | flag_vagrant_up=0 15 | flag_vbox=0 16 | flag_bochs=0 17 | flag_qemu_s=0 18 | flag_debug=0 19 | flag_self_test=0 20 | flag_build_only=0 21 | flag_run_only=0 22 | 23 | while getopts 'abf:crguvbd' flag; do 24 | case "${flag}" in 25 | c) flag_build_only=1 ;; 26 | r) flag_run_only=1 ;; 27 | g) flag_vagrant=1 ;; 28 | u) flag_vagrant_up=1 ;; 29 | v) flag_vbox=1 ;; 30 | b) flag_bochs=1 ;; 31 | d) flag_debug=1 ;; 32 | *) error "Unexpected option ${flag}" ;; 33 | esac 34 | done 35 | 36 | if [ $flag_run_only -ne 1 ] 37 | then 38 | if [ $flag_vagrant_up -eq 1 ] 39 | then 40 | vagrant up 41 | fi 42 | if [ $flag_vagrant_up -eq 1 ] 43 | then 44 | vagrant ssh -c "cd /vagrant/ && ./autobuild -nc" 45 | else 46 | python3 build_config/configure.py 47 | ninja 48 | fi 49 | fi 50 | 51 | if [ $flag_build_only -eq 1 ] 52 | then 53 | exit 54 | fi 55 | 56 | qemu_flags='' 57 | 58 | if [ -d "/mnt/c/Windows" ]; then 59 | # This is Windows subsystem for Linux 60 | qemucmd='qemu-system-x86_64.exe' 61 | vboxcmd='VBoxManage.exe' 62 | else 63 | # Generic posix, assume kvm is available 64 | qemucmd='qemu-system-x86_64 --enable-kvm' 65 | vboxcmd='VirtualBox' 66 | fi 67 | 68 | 69 | if [ $flag_vbox -eq 1 ] 70 | then 71 | rm build/disk.vdi 72 | $vboxcmd convertfromraw build/disk.img build/disk.vdi --format vdi --uuid "63f64532-cad0-47f1-a002-130863cf16a7" 73 | 74 | if [ $flag_debug -eq 1 ] 75 | then 76 | $vboxcmd startvm "RustOS" --debug 77 | else 78 | $vboxcmd startvm "RustOS" 79 | fi 80 | elif [ $flag_bochs -eq 1 ] 81 | then 82 | if [ $flag_debug -eq 1 ] 83 | then 84 | bochs -q -f dbgenv_config/bochs_debug 85 | else 86 | bochs -q -f dbgenv_config/bochs_normal 87 | fi 88 | else 89 | # More qemu flags 90 | # -nic user,model=virtio 91 | # -nic user,model=virtio,id=u 92 | # -object filter-dump,id=f1,netdev=u1,file=dump.dat 93 | # -drive file=build/disk.img,format=raw,if=virtio 94 | # -cpu qemu64,+invtsc,+rdtscp,+tsc-deadline 95 | flags="-cpu max -smp 4 -m 4G -no-reboot -no-shutdown" 96 | flags="$flags -drive file=build/disk.img,format=raw,if=ide" 97 | flags="$flags -nic user,model=rtl8139,hostfwd=tcp::5555-:22" 98 | flags="$flags -monitor stdio -serial file:CON" 99 | 100 | if [ $flag_debug -eq 1 ] 101 | then 102 | flags="$flags -d int,in_asm,guest_errors" 103 | fi 104 | $qemucmd $flags 105 | fi 106 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | fn main() { 4 | let crate_root = Path::new(env!("CARGO_MANIFEST_DIR")); 5 | 6 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("Missing target_os"); 7 | 8 | if target_os != "none" { 9 | return; 10 | } 11 | 12 | let linker_args = [ 13 | &format!( 14 | "--script={}", 15 | crate_root.join("build_config/linker.ld").display() 16 | ), 17 | "-nmagic", 18 | "-zcommon-page-size=0x1000", 19 | "-zmax-page-size=0x1000", 20 | "-zstack-size=0x1000", 21 | &format!("{}", crate_root.join("build/kernel_entry.o").display()), 22 | ]; 23 | 24 | for arg in linker_args { 25 | println!("cargo:rustc-link-arg-bins={arg}"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /build_config/constants/0_misc.toml: -------------------------------------------------------------------------------- 1 | # GDT & TSS & IDT 2 | [[constant]] 3 | name = "GDT_ADDR" 4 | type = "PhysAddr" 5 | value = "0x4000" 6 | 7 | [[constant]] 8 | name = "GDT_SELECTOR_ZERO" 9 | type = "PhysAddr" 10 | value = "0x00" 11 | 12 | [[constant]] 13 | name = "GDT_SELECTOR_CODE" 14 | type = "PhysAddr" 15 | value = "0x08" 16 | 17 | [[constant]] 18 | name = "GDT_SELECTOR_DATA" 19 | type = "PhysAddr" 20 | value = "0x10" 21 | 22 | [[constant]] 23 | name = "TSS_ADDR" 24 | type = "PhysAddr" 25 | value = "0x6000" 26 | 27 | [[constant]] 28 | name = "IDT_ADDR" 29 | type = "PhysAddr" 30 | value = "0x0" 31 | 32 | [[constant]] 33 | name = "IDT_SIZE" 34 | type = "PhysAddr" 35 | value = "0x1000" 36 | 37 | 38 | # Kernel position and size 39 | [[constant]] 40 | name = "KERNEL_LOCATION" 41 | type = "PhysAddr" 42 | value = "0x100_0000" 43 | 44 | [[constant]] 45 | name = "KERNEL_SIZE_LIMIT" 46 | type = "size_bytes" 47 | value = "0x20_0000" # TODO: find a solution, or document and test properly 48 | 49 | [[constant]] 50 | name = "KERNEL_END" 51 | type = "PhysAddr" 52 | value = "(add KERNEL_LOCATION KERNEL_SIZE_LIMIT)" 53 | 54 | # Paging 55 | [[constant]] 56 | name = "PAGE_SIZE_BYTES" 57 | type = "u64" 58 | value = "0x200_000" 59 | 60 | # Page table location 61 | [[constant]] 62 | name = "PAGE_TABLES_LOCATION" 63 | type = "PhysAddr" 64 | value = "0x1000_0000" 65 | 66 | [[constant]] 67 | name = "PAGE_TABLES_SIZE_LIMIT" 68 | type = "size_bytes" 69 | value = "0x100_0000" 70 | 71 | [[constant]] 72 | name = "PAGE_TABLES_END" 73 | type = "PhysAddr" 74 | value = "(add PAGE_TABLES_LOCATION PAGE_TABLES_SIZE_LIMIT)" 75 | 76 | 77 | # Mark for allocators 78 | [[constant]] 79 | name = "MEMORY_RESERVED_BELOW" 80 | type = "PhysAddr" 81 | value = "PAGE_TABLES_END" 82 | 83 | # DMA memory region 84 | [[constant]] 85 | name = "DMA_MEMORY_START" 86 | type = "PhysAddr" 87 | value = "0x4_0000" 88 | 89 | [[constant]] 90 | name = "DMA_MEMORY_SIZE" 91 | type = "size_bytes" 92 | value = "0x4_0000" 93 | 94 | # Kernel stack for system calls 95 | [[constant]] 96 | name = "SYSCALL_STACK" 97 | type = "VirtAddr" 98 | value = "0x1100_0000" 99 | 100 | # Higher half virtual address space start 101 | # Used for global physical to virtual memory mapping 102 | 103 | [[constant]] 104 | name = "HIGHER_HALF_START" 105 | type = "VirtAddr" 106 | value = "0xffff_8000_0000_0000" 107 | -------------------------------------------------------------------------------- /build_config/constants/1_boot.toml: -------------------------------------------------------------------------------- 1 | [[constant]] 2 | name = "BOOT_KERNEL_LOADPOINT" 3 | type = "PhysAddr" 4 | value = "0x10_0000" 5 | 6 | [[constant]] 7 | name = "BOOT_TMP_MMAP_BUFFER" 8 | type = "PhysAddr" 9 | value = "0x2000" 10 | 11 | [[constant]] 12 | name = "BOOT_TMP_KERNEL_SPLIT_ADDR" 13 | type = "PhysAddr" 14 | value = "0x3000" 15 | 16 | [[constant]] 17 | name = "BOOT_TMP_KERNEL_END_ADDR" 18 | type = "PhysAddr" 19 | value = "0x3004" 20 | 21 | [[constant]] 22 | name = "KERNEL_ENTRY_POINT" 23 | type = "PhysAddr" 24 | value = "0x100_0000" 25 | 26 | # Number of sectors, including MBR (stage0) 27 | [[constant]] 28 | name = "BOOTLOADER_SECTOR_COUNT" 29 | type = "u64" 30 | value = "6" 31 | 32 | # disk sector size in bytes 33 | [[constant]] 34 | name = "BOOT_DISK_SECTOR_SIZE" 35 | type = "u64" 36 | value = "0x200" 37 | 38 | [[constant]] 39 | name = "BOOT_PAGE_TABLE_SECTION_START" 40 | type = "PhysAddr" 41 | value = "0x1_0000" 42 | 43 | [[constant]] 44 | name = "BOOT_PAGE_TABLE_P4" 45 | type = "PhysAddr" 46 | value = "BOOT_PAGE_TABLE_SECTION_START" 47 | 48 | [[constant]] 49 | name = "BOOT_PAGE_TABLE_P3" 50 | type = "PhysAddr" 51 | value = "(add BOOT_PAGE_TABLE_SECTION_START 0x1000)" 52 | 53 | [[constant]] 54 | name = "BOOT_PAGE_TABLE_P2" 55 | type = "PhysAddr" 56 | value = "(add BOOT_PAGE_TABLE_SECTION_START 0x2000)" 57 | 58 | [[constant]] 59 | name = "BOOT_PAGE_TABLE_SECTION_END" 60 | type = "PhysAddr" 61 | value = "(add BOOT_PAGE_TABLE_SECTION_START 0x2000)" -------------------------------------------------------------------------------- /build_config/constants/1_process.toml: -------------------------------------------------------------------------------- 1 | # Process virtual memory area 2 | [[constant]] 3 | name = "PROCESS_PROCESSOR_INFO_TABLE" 4 | type = "VirtAddr" 5 | value = "0x8000" 6 | 7 | [[constant]] 8 | name = "PROCESS_COMMON_CODE" 9 | type = "VirtAddr" 10 | value = "0x20_0000" 11 | 12 | [[constant]] 13 | name = "PROCESS_STACK" 14 | type = "VirtAddr" 15 | value = "0x40_0000" 16 | 17 | [[constant]] 18 | name = "PROCESS_STACK_SIZE_PAGES" 19 | type = "u64" 20 | value = "2" 21 | 22 | [[constant]] 23 | name = "PROCESS_STACK_SIZE_BYTES" 24 | type = "size_bytes" 25 | value = "(mul PAGE_SIZE_BYTES PROCESS_STACK_SIZE_PAGES)" 26 | 27 | [[constant]] 28 | name = "PROCESS_STACK_END" 29 | type = "VirtAddr" 30 | value = "(add PROCESS_STACK PROCESS_STACK_SIZE_BYTES)" 31 | 32 | [[constant]] 33 | name = "PROCESS_DYNAMIC_MEMORY" 34 | type = "VirtAddr" 35 | value = "0x100_0000_0000" -------------------------------------------------------------------------------- /build_config/constants/_options.toml: -------------------------------------------------------------------------------- 1 | [codegen] 2 | enabled = ["nasm", "rust", "python", "dummy_json"] 3 | 4 | [lang.nasm] 5 | file_ext = ".asm" 6 | template = "%define $name $value" 7 | comment = "; $comment" 8 | format.boolean = ["1", "0"] 9 | 10 | [lang.nasm.type.PhysAddr] 11 | format.integer = { radix = "hex", underscores = 4 } 12 | 13 | [lang.nasm.type.VirtAddr] 14 | format.integer = { radix = "hex", underscores = 4 } 15 | 16 | [lang.nasm.type.size_bytes] 17 | format.integer = { radix = "hex", underscores = 4 } 18 | 19 | [lang.rust] 20 | file_ext = ".rs" 21 | template = "pub const $name: $type = $value;" 22 | import = "use $import;" 23 | comment = "// $comment" 24 | format.boolean = ["true", "false"] 25 | formatter = ["rustfmt"] 26 | 27 | [lang.rust.type.PhysAddr] 28 | value_prefix = "unsafe {PhysAddr::new_unchecked(" 29 | value_suffix = ")}" 30 | format.integer = { radix = "hex", underscores = 4 } 31 | import = ["x86_64::PhysAddr"] 32 | 33 | [lang.rust.type.VirtAddr] 34 | value_prefix = "unsafe {VirtAddr::new_unsafe(" 35 | value_suffix = ")}" 36 | format.integer = { radix = "hex", underscores = 4 } 37 | import = ["x86_64::VirtAddr"] 38 | 39 | [lang.rust.type.size_bytes] 40 | name = "u64" 41 | format.integer = { radix = "hex", underscores = 4 } -------------------------------------------------------------------------------- /build_config/files/keycodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "5": "F1", 3 | "6": "F2", 4 | "4": "F3", 5 | "12": "F4", 6 | "3": "F5", 7 | "11": "F6", 8 | "10": "F8", 9 | "1": "F9", 10 | "9": "F10", 11 | "120": "F11", 12 | "7": "F12", 13 | "13": "Tab", 14 | "14": "Acute", 15 | "17": "LeftAlt", 16 | "18": "LeftShift", 17 | "20": "LeftCtrl", 18 | "69": "0", 19 | "22": "1", 20 | "30": "2", 21 | "38": "3", 22 | "37": "4", 23 | "46": "5", 24 | "54": "6", 25 | "61": "7", 26 | "62": "8", 27 | "70": "9", 28 | "28": "A", 29 | "50": "B", 30 | "33": "C", 31 | "35": "D", 32 | "36": "E", 33 | "43": "F", 34 | "52": "G", 35 | "51": "H", 36 | "67": "I", 37 | "59": "J", 38 | "66": "K", 39 | "75": "L", 40 | "58": "M", 41 | "49": "N", 42 | "68": "O", 43 | "77": "P", 44 | "21": "Q", 45 | "45": "R", 46 | "27": "S", 47 | "44": "T", 48 | "60": "U", 49 | "42": "V", 50 | "29": "W", 51 | "34": "X", 52 | "53": "Y", 53 | "26": "Z", 54 | "41": "Space", 55 | "65": "Comma", 56 | "73": "Period", 57 | "74": "Slash", 58 | "76": "Semicolon", 59 | "78": "Minus", 60 | "82": "Singlequote", 61 | "84": "LeftBracket", 62 | "85": "Equals", 63 | "88": "CapsLock", 64 | "89": "RightShift", 65 | "90": "Enter", 66 | "91": "RightBracket", 67 | "93": "Backslash", 68 | "102": "Backspace", 69 | "105": "Keypad_1", 70 | "107": "Keypad_4", 71 | "108": "Keypad_7", 72 | "112": "Keypad_0", 73 | "113": "Keypad_Period", 74 | "114": "Keypad_2", 75 | "115": "Keypad_5", 76 | "116": "Keypad_6", 77 | "117": "Keypad_8", 78 | "118": "Escape", 79 | "119": "NumberLock", 80 | "121": "Keypad_Plus", 81 | "122": "Keypad_3", 82 | "123": "Keypad_Minus", 83 | "124": "Keypad_Multiply", 84 | "125": "Keypad_9", 85 | "126": "ScrollLock", 86 | "131": "F7", 87 | "272": "Multimedia_WWW_Search", 88 | "273": "RightAlt", 89 | "276": "RightCtrl", 90 | "277": "Multimedia_PreviousTrack", 91 | "280": "Multimedia_WWW_Favourites", 92 | "287": "GUI_Left", 93 | "288": "Multimedia_WWW_Refresh", 94 | "289": "Multimedia_VolumeDown", 95 | "291": "Multimedia_Mute", 96 | "295": "GUI_Right", 97 | "296": "Multimedia_WWW_Stop", 98 | "299": "Multimedia_Calculator", 99 | "303": "Apps", 100 | "304": "Multimedia_WWW_Forward", 101 | "306": "Multimedia_VolumeUp", 102 | "308": "Multimedia_PlayPause", 103 | "311": "ACPI_Power", 104 | "312": "Multimedia_WWW_Back", 105 | "314": "Multimedia_WWW_Home", 106 | "315": "Multimedia_Stop", 107 | "319": "ACPI_Sleep", 108 | "320": "Multimedia_My_Computer", 109 | "328": "Multimedia_Email", 110 | "330": "Keypad_Divide", 111 | "333": "Multimedia_NextTrack", 112 | "336": "Multimedia_MediaSelect", 113 | "346": "Keypad_Enter", 114 | "350": "ACPI_Wake", 115 | "361": "End", 116 | "363": "CursorLeft", 117 | "364": "Home", 118 | "368": "Insert", 119 | "369": "Delete", 120 | "370": "CursorDown", 121 | "372": "CursorRight", 122 | "373": "CursorUp", 123 | "378": "PageDown", 124 | "381": "PageUp" 125 | } -------------------------------------------------------------------------------- /build_config/files/pci_devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "8086:1237": { 3 | "name": "Intel 440FX - 82441FX PMC [Natoma]" 4 | }, 5 | "8086:7000": { 6 | "name": "Intel 82371SB PIIX3 ISA [Natoma/Triton II]" 7 | }, 8 | "8086:7010": { 9 | "name": "Intel 82371SB PIIX3 IDE [Natoma/Triton II]" 10 | }, 11 | "8086:7113": { 12 | "name": "Intel 82371AB/EB/MB PIIX4 ACPI" 13 | }, 14 | "1234:1111": { 15 | "name": "QEMU Virtual Video Controller" 16 | }, 17 | "10ec:8029": { 18 | "shortname": "rtl8029", 19 | "name": "Bochs RTL8029 (NE2000)", 20 | "driver": { 21 | "from_initrd": true, 22 | "executable": "driver_ne2k" 23 | } 24 | }, 25 | "10ec:8139": { 26 | "shortname": "rtl8139", 27 | "name": "RTL-8139 PCI Fast Ethernet Adapter", 28 | "driver": { 29 | "from_initrd": true, 30 | "executable": "driver_rtl8139" 31 | } 32 | }, 33 | "1af4:1000": { 34 | "shortname": "virtio-net", 35 | "name": "Virtio network device" 36 | } 37 | } -------------------------------------------------------------------------------- /build_config/files/startup_services.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "driver_rtc", 4 | "description": "CMOS RTC driver", 5 | "requires": [], 6 | "from_initrd": true, 7 | "executable": "driver_rtc" 8 | }, 9 | { 10 | "name": "driver_ps2", 11 | "description": "PS/2 keyboard driver", 12 | "requires": [], 13 | "from_initrd": true, 14 | "executable": "driver_ps2" 15 | }, 16 | { 17 | "name": "driver_pci", 18 | "description": "PCI driver", 19 | "requires": [], 20 | "from_initrd": true, 21 | "executable": "driver_pci" 22 | }, 23 | { 24 | "name": "consoled", 25 | "description": "Text GUI on VGA console", 26 | "requires": ["driver_ps2"], 27 | "from_initrd": true, 28 | "executable": "consoled" 29 | }, 30 | { 31 | "name": "syslogd", 32 | "description": "System log daemon", 33 | "requires": ["consoled"], 34 | "from_initrd": true, 35 | "executable": "syslogd" 36 | }, 37 | { 38 | "name": "netd", 39 | "description": "Network daemon", 40 | "requires": [], 41 | "from_initrd": true, 42 | "executable": "netd" 43 | }, 44 | { 45 | "name": "example", 46 | "description": "Example user binary", 47 | "requires": ["consoled", "netd"], 48 | "from_initrd": true, 49 | "executable": "examplebin" 50 | } 51 | ] -------------------------------------------------------------------------------- /build_config/initrd_files.txt: -------------------------------------------------------------------------------- 1 | # Misc 2 | README.md=README.md 3 | 4 | # Kernel files 5 | p_commoncode=build/process_common.bin 6 | 7 | # Services 8 | serviced=build/modules/daemon_service.elf 9 | syslogd=build/modules/daemon_syslog.elf 10 | consoled=build/modules/daemon_console.elf 11 | netd=build/modules/daemon_net.elf 12 | 13 | # Drivers 14 | driver_ata_pio=build/modules/driver_ata_pio.elf 15 | driver_rtc=build/modules/driver_rtc.elf 16 | driver_ps2=build/modules/driver_ps2.elf 17 | driver_pci=build/modules/driver_pci.elf 18 | driver_ne2k=build/modules/driver_ne2k.elf 19 | driver_rtl8139=build/modules/driver_rtl8139.elf 20 | 21 | # Applications 22 | examplebin=build/modules/examplebin.elf 23 | 24 | # Configuration files 25 | startup_services.json=build_config/files/startup_services.json 26 | pci_devices.json=build_config/files/pci_devices.json 27 | keycodes.json=build_config/files/keycodes.json 28 | keymap.json=build_config/files/keymap.json 29 | -------------------------------------------------------------------------------- /build_config/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(_start) 3 | 4 | SECTIONS { 5 | . = 0x1000000; 6 | 7 | /* All sections are 0x200000 = 0x200_000 = 2MiB aligned, 8 | ** as that is the correct huge-page boundary alignment. 9 | */ 10 | 11 | . = ALIGN(0x200000); 12 | .text : { 13 | /* ensure that the kernel entry code is at the beginning */ 14 | KEEP(*(.entry)) 15 | *(.text .text.*) 16 | } 17 | 18 | . = ALIGN(0x200000); 19 | .rodata : { 20 | KEEP(*(.rodata .rodata.*)) 21 | } 22 | 23 | /* BSS contains stack, so in case of overflow (growing downwards) it 24 | ** tries to write to .rodata section, which is conveniently read-only. 25 | */ 26 | . = ALIGN(0x200000); 27 | .bss (NOLOAD): { 28 | *(.bss .bss.*) 29 | } 30 | 31 | . = ALIGN(0x200000); 32 | .data : { 33 | *(.data .data.*) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /build_config/validate_build.py: -------------------------------------------------------------------------------- 1 | #!python3 2 | 3 | from sys import argv 4 | from pathlib import Path 5 | 6 | max_size = int(argv[1]) 7 | cur_size = Path("build/kernel_stripped.elf").stat().st_size 8 | 9 | if cur_size > max_size: 10 | exit(f"Error: kernel image max size exceeded ({cur_size:#x} > {max_size:#x})") 11 | -------------------------------------------------------------------------------- /d7os.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 4 | "target-endian": "little", 5 | "target-pointer-width": "64", 6 | "target-c-int-width": "32", 7 | "arch": "x86_64", 8 | "os": "none", 9 | "executables": true, 10 | "linker-flavor": "ld.lld", 11 | "linker": "rust-lld", 12 | "features": "-mmx,-sse,+soft-float", 13 | "disable-redzone": true, 14 | "panic-strategy": "abort" 15 | } 16 | -------------------------------------------------------------------------------- /dbgenv_config/bochs_base: -------------------------------------------------------------------------------- 1 | # Base config to be included 2 | plugin_ctrl: unmapped=1, biosdev=1, speaker=0, extfpuirq=1, parallel=1, serial=1, iodebug=1, ne2k=1 3 | config_interface: textconfig 4 | display_library: x 5 | memory: host=2048, guest=4096 6 | boot: disk 7 | floppy_bootsig_check: disabled=0 8 | # no floppya 9 | # no floppyb 10 | ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 11 | ata0-master: type=disk, path="build/disk.img", mode=flat, model="Generic 1234", biosdetect=auto, translation=auto 12 | ata0-slave: type=none 13 | ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 14 | ata1-master: type=none 15 | ata1-slave: type=none 16 | ata2: enabled=0 17 | ata3: enabled=0 18 | pci: enabled=1, chipset=i440fx, slot1=ne2k 19 | vga: extension=vbe, update_freq=5, realtime=1 20 | cpu: count=1:4:1, ips=4000000, model=corei7_haswell_4770, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 21 | print_timestamps: enabled=0 22 | debugger_log: - 23 | magic_break: enabled=1 24 | port_e9_hack: enabled=1 25 | private_colormap: enabled=0 26 | # clock: sync=none, time0=local, rtc_sync=0 27 | clock: sync=realtime, time0=local, rtc_sync=1 28 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 29 | mouse: type=ps2, enabled=0, toggle=ctrl+mbutton 30 | parport1: enabled=1, file=none 31 | parport2: enabled=0 32 | com1: enabled=0 33 | com2: enabled=0 34 | com3: enabled=0 35 | com4: enabled=0 36 | -------------------------------------------------------------------------------- /dbgenv_config/bochs_debug: -------------------------------------------------------------------------------- 1 | #include ./dbgenv_config/bochs_base 2 | log: - 3 | logprefix: %t%e%d 4 | # See https://bochs.sourceforge.io/doc/docbook/user/howto.html#LOGOPTS-BY-DEVICE 5 | #debug: action=ignore, harddrv=report, pci=report 6 | debug: action=report, harddrv=ignore, pci=ignore, xgui=ignore, siminterface=ignore, memory=ignore, pit=ignore, pic=ignore, pit82c54=ignore, ioapic=ignore, cpu0=report, apic0=report, cpu1=ignore, apic1=ignore, cpu2=ignore, apic2=ignore, cpu3=ignore, apic3=ignore, dma=ignore 7 | info: action=report 8 | error: action=report 9 | panic: action=ask 10 | #include ./dbgenv_config/bochs_net 11 | -------------------------------------------------------------------------------- /dbgenv_config/bochs_net: -------------------------------------------------------------------------------- 1 | # ne2k: enabled=1, mac=B0:C5:AA:BB:CC:02, ethmod=vnet, script="" 2 | ne2k: enabled=1, mac=B0:C5:AA:BB:CC:02, ethmod=slirp, script="./dbgenv_config/slirp.conf" -------------------------------------------------------------------------------- /dbgenv_config/bochs_normal: -------------------------------------------------------------------------------- 1 | #include ./dbgenv_config/bochs_base 2 | log: - 3 | logprefix: %t%e%d 4 | debug: action=ignore 5 | info: action=report 6 | error: action=ask, XGUI=report, ne2k0=report 7 | panic: action=ask 8 | #include ./dbgenv_config/bochs_net 9 | -------------------------------------------------------------------------------- /dbgenv_config/slirp.conf: -------------------------------------------------------------------------------- 1 | # slirp config 2 | # The line above is mandatory 3 | hostfwd = tcp::8080-:8080 4 | hostfwd = udp::8080-:8080 5 | pktlog = slirp-pktlog.txt 6 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # Backlog 2 | 3 | * Reduce allow() lints in the kernel 4 | * Never map anything to virtual address zero, for processes at least (nullptr) 5 | * SMP support (multiple cores): 6 | * Move kernel to use new static mappings for physical memory access as much as possible 7 | * Scheduler rewrite 8 | * TLB Shootdown support 9 | * Userland for applications 10 | * Drivers as well, as much as possible, setup IO bitmaps in TSS to do this 11 | * Convert system calls from (len, ptr) to (ptr, len). 12 | * `exec` arguments 13 | * `fork` and friends 14 | * System call and IPC topic access control 15 | * See `capabilities.md` 16 | * Version check `d7abi` and `libd7` on process startup (include check in `libd7`) 17 | * As the programs are statically linked, they must be version-checked against the kernel 18 | * Proper, graphics-mode GUI 19 | * Support small pages for better memory control (requires lots of rewriting) 20 | * Filesystems 21 | * Virtual filesystem 22 | * https://github.com/pi-pi3/ext2-rs 23 | * https://github.com/omerbenamram/mft 24 | * Porting rustc 25 | * https://www.reddit.com/r/rust/comments/5ag60z/how_do_i_bootstrap_rust_to_crosscompile_for_a_new/d9gdjwf/ 26 | * Reimplement virtualbox support (create hard drive images) 27 | * Look into https://github.com/minexew/Shrine/blob/master/HwSupp/Pci.HC 28 | -------------------------------------------------------------------------------- /docs/building-bochs.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | Hypothesis: Slow startup is caused by bochs malloc+memcpy'ing large flat disk image to memory 4 | 5 | # Per OS installation 6 | 7 | ## Linux 8 | 9 | ### Optional tweak: pcap logging 10 | 11 | In `iodev/network/eth_vnet.cc` set `BX_ETH_VNET_PCAP_LOGGING 1` 12 | In `Makefile` add `-lpcap` to `LIBS` 13 | 14 | ### Configure 15 | 16 | ``` 17 | ./configure --enable-smp \ 18 | --enable-cpu-level=6 \ 19 | --enable-all-optimizations \ 20 | --enable-x86-64 \ 21 | --enable-vmx \ 22 | --enable-avx \ 23 | --enable-pci \ 24 | --enable-show-ips \ 25 | --enable-debugger \ 26 | --enable-disasm \ 27 | --enable-debugger-gui \ 28 | --enable-logging \ 29 | --enable-fpu \ 30 | --enable-3dnow \ 31 | --enable-sb16=dummy \ 32 | --enable-cdrom \ 33 | --enable-x86-debugger \ 34 | --enable-ne2000 \ 35 | --enable-iodebug \ 36 | --disable-plugins \ 37 | --disable-docbook \ 38 | --with-x --with-x11 --with-term --with-sdl2 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/capabilities.md: -------------------------------------------------------------------------------- 1 | # Capability-based security model 2 | 3 | In D7, access control is implemented through capabilities. Both the kernel and some services require the called to provide a capability that proves their right to it. For system calls, the access right is implicitly checked by the kernel, using pid and it's associated security context. If a service needs per-caller permissions, the authentication is done using a capability token. 4 | 5 | ## Security contexts 6 | 7 | For each process, kernel keeps a security context, which is essentially a set of capabilities. An alternative context, which granted to processes created with `exec`, is also provided. The interface somewhat resembles `pledge` syscall from BSD. 8 | 9 | A process with empty capability set can only perform the following operations: 10 | 11 | * Exit with an exit code 12 | * Read it's own PID 13 | * Read it's own security context 14 | * Yield it's scheduler slice 15 | * Read and write already-shared memory regions 16 | 17 | There is following operations available for the main security context (given that the process hasn't given up the associed capabilities): 18 | 19 | * Remove or reduce an existing access right 20 | * Gain addition right by redeeming a capability token 21 | 22 | For `exec` security context, the operations are: 23 | 24 | * Copy current process access rights here 25 | * Remove or reduce an existing access right 26 | 27 | ## Kernel-checked access rights 28 | 29 | Most system calls have an associated capability. These are tracked by the kernel itself. The IPC calls have more specific access controls: both subscriptions and sending are restricted by topic prefixes. This by itself provides enough security for most simple services. For instance, a network card can only be accessed by it's driver. In addition some calls, such as `exit` and memory mapping modification, are only available for the process itself. 30 | 31 | ## Service-checked access rights 32 | 33 | Sometimes having the kernel check the access rights to an IPC prefix isn't fine-grained enough. In these cases the program itself can keep issue capability tokens to callers. The permissions granted by the token can either be encoded into the token itself (if it fits), or kept separately encoding their identifier into the token. 34 | 35 | ## Capability tokens 36 | 37 | An authorization can be transferred across process bounaries by crafting a *capability token* and sending that to the other process. Capability is a cryptographically-signed message that contains the following information: 38 | 39 | * Process id that grants the capability (zero for kernel) (u64) 40 | * Capability that is granted (u64) 41 | * Kernel signature (64 bytes, currently ed25519) 42 | -------------------------------------------------------------------------------- /docs/devel_troubleshooting.md: -------------------------------------------------------------------------------- 1 | Troubleshooting help tips: 2 | 3 | * Check stack size at `src/entry.asm` 4 | * Check the amount of sectors loaded from the disk at `src/boot/boot_stage0.asm` 5 | * Check that `plan.md` is in sync with bootloader constants, FrameAllocator, and others 6 | 7 | 8 | ## Frequent problems with solutions 9 | 10 | ### IRQ is not firing? 11 | 12 | Check that PIC is not masking it 13 | 14 | 15 | # Emulators 16 | 17 | ## Bochs 18 | * Run in bochs with `trace on` 19 | * Stacktrace with `print-stack 100` 20 | * Page tables: `info tab` and `page 0x1234` 21 | 22 | ## 23 | * Stacktrace with `x /100gx $esp` -------------------------------------------------------------------------------- /docs/sockets.md: -------------------------------------------------------------------------------- 1 | # Network sockets 2 | 3 | Socket is created by writing a create request `/srv/net/newsocket`, an endpoint operated by `netd`. 4 | `netd` then creates the requested socket under `/srv/net/socket/$socketname` and returns `socketname`. 5 | The creation protocol can be found under [`d7protocol`](../libs/d7protocol/README.md). 6 | 7 | After this the socketname can be used to send and receive packets. -------------------------------------------------------------------------------- /docs/timekeeping.md: -------------------------------------------------------------------------------- 1 | # Timekeeping 2 | 3 | Several date-and-time related functionalities are needed by an OS and applications: 4 | * Scheduler timeslices: for sleeping and pre-emption 5 | * Showing current date and time to the user 6 | * Network applications, e.g. TLS certificate expiration 7 | * Filesystem timestamps, including cremovable media as well 8 | * Showing current date and time to the user 9 | * Applications: timeouts, delays and such 10 | 11 | ## Theory of clocks 12 | 13 | Clocks can have some of the following properties: 14 | * monotonic: the value never decreases 15 | * steady: increases linearly with physical time, monotonic 16 | * system-wide: value is same for all reading programs 17 | * wall-clock-time: convertible to the real world time 18 | 19 | Providing all of the properties in a single clock value is not practical: 20 | * wall-clock-time needs to be adjusted from an external source 21 | * system-wide clock is too expensive to use for interval-measuring 22 | 23 | ## Available clocks 24 | 25 | * Process-local steady clock 26 | * System-wide almost-monotonic clock 27 | * Wall-clock time (both monotonic and immediately ajdusting variants) 28 | 29 | ### Process-local steady clock 30 | 31 | The time is only usable within a single process, but is extremely cheap to read; it uses `TSC` so no system calls are required to get a value. 32 | 33 | An per-CPU configuration is stored into a shared memory region. It contains frequency of the `TSC` in Hz, as well as offset value which can be used to compare `TSC` values across CPUs. 34 | 35 | ### System-wide almost-monotonic clock 36 | 37 | This is a the process-local clock, but the exact monotocity-guarantee is lost when using it over process boundaries. However, it shouldn't go back more than a microseconds, and should generally be usable for inter-process comminication and system-wide timestamps. 38 | 39 | ### Wall-clock time 40 | 41 | Wall-clock time is converible to read world date-and-time values. There are both monotonic and immediately-adjusting variants available. The monotonic variant speeds up if this is before the immediately-adjusting variant, and slows down if it's before. 42 | 43 | The immediately-adjusting variant is the best available real-world time available to the system. If a network connection is available, it will be used to get the current timestamp, probably using `NTP`, `TIME` or even `HTTP`. If no network connection is available, the battery-backed `RTC` will be used. 44 | 45 | The wall-clock time is not available in the operating system. Instead, it's provided by the time service, which manages `RTC` and network time. 46 | 47 | ## Real-world timekeeping considerations 48 | 49 | ### Time zones and daylight savings 50 | 51 | The system time is always stored in UTC. A global setting in the time service is used to store current timezone and possible daylight savings settings, and conversion functions to local time are available. 52 | 53 | ### Dates, including leap years 54 | 55 | Timestamp-to-data conversions are available as functions. 56 | -------------------------------------------------------------------------------- /libs/d7abi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7abi" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [profile.dev] 9 | panic = "abort" 10 | 11 | [profile.release] 12 | panic = "abort" 13 | 14 | [dependencies] 15 | serde-big-array = "0.2.0" # Big array support for serde 16 | bitflags = "1.3" 17 | 18 | [dependencies.serde] 19 | version = "1.0" 20 | default-features = false 21 | features = ["alloc", "derive"] 22 | 23 | [dependencies.hashbrown] 24 | version = "0.11" 25 | features = ["nightly", "inline-more", "serde"] 26 | 27 | [dependencies.num_enum] 28 | git = "https://github.com/Dentosal/num_enum_simplified" 29 | 30 | [dependencies.x86_64] 31 | git = "https://github.com/Dentosal/x86_64" 32 | features = ["use-serde"] 33 | -------------------------------------------------------------------------------- /libs/d7abi/README.md: -------------------------------------------------------------------------------- 1 | d7abi - Data definitions for D7 system calls 2 | ============================================ 3 | 4 | This crate is shared between the user processes and the kernel, 5 | and contains data definitions for structures passed through 6 | syscalls and kernel-process IPC. 7 | 8 | Some useful traits are implemented for the data types, and 9 | some necessary and useful methods are included. 10 | 11 | In addition to Rust source code it also contains: 12 | * Linker script for creating ELF files 13 | * Json target definition file for `cargo xbuild` 14 | -------------------------------------------------------------------------------- /libs/d7abi/d7abi.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 4 | "target-endian": "little", 5 | "target-pointer-width": "64", 6 | "target-c-int-width": "32", 7 | "arch": "x86_64", 8 | "os": "none", 9 | "linker": "ld.lld", 10 | "linker-flavor": "ld", 11 | "features": "-mmx,-sse,+soft-float", 12 | "disable-redzone": true, 13 | "panic-strategy": "abort" 14 | } 15 | -------------------------------------------------------------------------------- /libs/d7abi/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(_start) 3 | 4 | SECTIONS { 5 | /* Load point for programs is 0x1_000_000 */ 6 | . = 0x1000000; 7 | 8 | /* 9 | All sections are 0x200000 = 0x200_000 = 2MiB aligned, 10 | as that is the correct huge-page boundary alignment. 11 | */ 12 | 13 | . = ALIGN(0x200000); 14 | .text : { 15 | *(.text .text.*) 16 | } 17 | 18 | . = ALIGN(0x200000); 19 | .rodata : { 20 | KEEP(*(.rodata .rodata.*)) 21 | } 22 | 23 | . = ALIGN(0x200000); 24 | .bss (NOLOAD) : { 25 | *(.bss .bss.*) 26 | } 27 | 28 | . = ALIGN(0x200000); 29 | .data : { 30 | *(.data .data.*) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /libs/d7abi/src/ipc/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::{string::String, vec::Vec}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | pub mod protocol; 5 | 6 | bitflags::bitflags! { 7 | #[derive(Default)] 8 | pub struct SubscriptionFlags: u64 { 9 | /// Topic filter is used as a prefix 10 | const PREFIX = (1 << 0); 11 | /// Subscription is reliable, see ipc docs for more info 12 | const RELIABLE = (1 << 1); 13 | /// First process sending to this subscription is marked 14 | /// as it's corresponding pipe pair. No messages from other 15 | /// processes will be accepted. If the sender is terminated 16 | /// all operations to this socket return an error. 17 | /// 18 | /// This can be used to release server resources if caller gets terminated, 19 | /// without needing to implement two-way communication. 20 | /// 21 | /// PIPE subscriptions are always EXACT and RELIABLE. 22 | const PIPE = (1 << 2) | Self::PREFIX.bits | Self::RELIABLE.bits; 23 | } 24 | } 25 | 26 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] 27 | pub struct SubscriptionId(u64); 28 | impl SubscriptionId { 29 | pub const fn from_u64(v: u64) -> Self { 30 | Self(v) 31 | } 32 | 33 | pub fn as_u64(self) -> u64 { 34 | self.0 35 | } 36 | 37 | pub fn next(self) -> Self { 38 | Self(self.0 + 1) 39 | } 40 | } 41 | 42 | /// Used to acknowledge a reliable message 43 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] 44 | pub struct AcknowledgeId(u64); 45 | impl AcknowledgeId { 46 | pub fn from_u64(v: u64) -> Self { 47 | Self(v) 48 | } 49 | 50 | pub fn as_u64(self) -> u64 { 51 | self.0 52 | } 53 | 54 | pub fn next(self) -> Self { 55 | Self(self.0 + 1) 56 | } 57 | } 58 | 59 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 60 | pub struct Message { 61 | /// Topic this message was sent to 62 | pub topic: String, 63 | /// The actual data on this message 64 | pub data: Vec, 65 | /// In case of reliable message, this is used to acknowledge the message. 66 | /// If this is none for a reliable message, then it's either: 67 | /// * sent by the kernel, and does not require an acknowledgement 68 | /// * sent as a reply, and does not require an acknowledgement 69 | pub ack_id: Option, 70 | } 71 | impl Message { 72 | pub fn needs_response(&self) -> bool { 73 | self.ack_id.is_some() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /libs/d7abi/src/ipc/protocol/keyboard.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub type KeyCode = u16; 4 | 5 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 6 | pub struct KeyboardEvent { 7 | /// Release or press 8 | pub release: bool, 9 | /// Keycode, i.e. index 10 | pub keycode: KeyCode, 11 | // TODO: Timestamp 12 | // pub timestamp: SystemTime, 13 | } 14 | -------------------------------------------------------------------------------- /libs/d7abi/src/ipc/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::process::{ProcessId, ProcessResult}; 4 | 5 | pub mod keyboard; 6 | pub mod service; 7 | 8 | #[derive(Debug, Clone, Serialize, Deserialize)] 9 | pub struct ProcessTerminated { 10 | pub pid: ProcessId, 11 | pub result: ProcessResult, 12 | } 13 | -------------------------------------------------------------------------------- /libs/d7abi/src/ipc/protocol/service.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use core::fmt; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] 6 | #[serde(transparent)] 7 | pub struct ServiceName(pub String); 8 | impl fmt::Display for ServiceName { 9 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 10 | write!(f, "{}", self.0) 11 | } 12 | } 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct Registration { 16 | /// Service name 17 | pub name: ServiceName, 18 | /// Oneshot services are considired running after they have completed successfully 19 | #[serde(default)] 20 | pub oneshot: bool, 21 | } 22 | -------------------------------------------------------------------------------- /libs/d7abi/src/kernel_constants.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | #![allow(clippy::unreadable_literal)] 3 | 4 | include!("../../../build/constants.rs"); 5 | -------------------------------------------------------------------------------- /libs/d7abi/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Lints 2 | #![forbid(private_in_public)] 3 | #![warn(bare_trait_objects)] 4 | #![deny(unused_must_use)] 5 | #![deny(unused_assignments)] 6 | #![deny(overflowing_literals)] 7 | #![deny(clippy::missing_safety_doc)] 8 | // no_std 9 | #![no_std] 10 | // Unstable features 11 | #![feature(integer_atomics)] 12 | #![feature(allocator_api)] 13 | 14 | #[macro_use] 15 | extern crate alloc; 16 | 17 | mod kernel_constants; 18 | mod syscall; 19 | 20 | pub mod ipc; 21 | pub mod process; 22 | pub mod processor_info; 23 | 24 | pub use self::kernel_constants::{PROCESS_DYNAMIC_MEMORY, PROCESS_STACK_END}; 25 | pub use self::syscall::*; 26 | -------------------------------------------------------------------------------- /libs/d7abi/src/process.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::num::NonZeroU64; 3 | use core::u64; 4 | use serde::{Deserialize, Serialize}; 5 | use x86_64::structures::idt::InterruptStackFrameValue; 6 | use x86_64::structures::idt::PageFaultErrorCode; 7 | use x86_64::VirtAddr; 8 | 9 | /// ProcessId is stores as `NonZeroU64`, so that `Option` 10 | /// still has uses only `size_of` bytes 11 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] 12 | #[serde(transparent)] 13 | pub struct ProcessId(NonZeroU64); 14 | impl ProcessId { 15 | /// Must be called only once 16 | pub const fn first() -> Self { 17 | Self(unsafe { NonZeroU64::new_unchecked(1) }) 18 | } 19 | 20 | /// Must be only called for an actual process ids 21 | /// Only to be used when deserializing from system call results and such 22 | pub fn from_u64(value: u64) -> Self { 23 | Self(NonZeroU64::new(value).expect("Zero ProcessId")) 24 | } 25 | 26 | /// Only to be used by the process scheduler 27 | pub fn next(self) -> Self { 28 | assert_ne!(self.0.get(), u64::MAX, "Kernel process id has no successor"); 29 | Self(NonZeroU64::new(self.0.get() + 1).expect("Overflow")) 30 | } 31 | 32 | pub const fn as_u64(self) -> u64 { 33 | self.0.get() 34 | } 35 | } 36 | impl fmt::Display for ProcessId { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | f.pad_integral(true, "", &format!("{}", self.0)) 39 | } 40 | } 41 | 42 | #[derive(Debug, Clone, Deserialize, Serialize)] 43 | pub enum ProcessResult { 44 | /// The process exited with a return code 45 | Completed(u64), 46 | /// The process was terminated because an error occurred 47 | Failed(Error), 48 | } 49 | 50 | #[derive(Debug, Clone, Deserialize, Serialize)] 51 | pub enum Error { 52 | /// Division by zero 53 | DivideByZero(InterruptStackFrameValue), 54 | /// Page fault 55 | PageFault(InterruptStackFrameValue, VirtAddr, PageFaultErrorCode), 56 | /// Unhandled interrupt without an error code 57 | Interrupt(u8, InterruptStackFrameValue), 58 | /// Unhandled interrupt with an error code 59 | InterruptWithCode(u8, InterruptStackFrameValue, u32), 60 | /// Invalid system call number 61 | SyscallNumber(u64), 62 | /// Invalid argument value passed to system call 63 | SyscallArgument, 64 | /// Invalid pointer passed to system call 65 | Pointer(VirtAddr), 66 | /// Owner process died 67 | ChainedTermination, 68 | } 69 | -------------------------------------------------------------------------------- /libs/d7abi/src/processor_info.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | /// An array of these is available for all processes, index of the array is processor id. 4 | /// They allow retrieving static information about processor cores 5 | /// without having to do system calls. A process can get it's cpu id, 6 | /// i.e. index in the processor core list, using `rdtscp`. 7 | #[derive(Debug, Clone, Copy)] 8 | #[repr(C, packed)] 9 | pub struct ProcessorInfo { 10 | /// TSC frequency in Hz, assumed to be invariant 11 | pub tsc_freq_hz: u64, 12 | /// TSC values on different CPUs can be compared 13 | /// using `tsc_offset + read_tsc()` 14 | pub tsc_offset: u64, 15 | } 16 | 17 | /// Reads entry for any processor. 18 | /// The cpu_id can be retrieved using `rdtscp`, and whn doing TSC-related 19 | /// arithmetic, gets the right entry for the returned timestamp. 20 | /// 21 | /// # Safety 22 | /// 23 | /// Must be only called in when process page tables are active, 24 | /// kernel mode doesn't have anything mapped to this address. 25 | /// 26 | /// `cpu_id` must be valid. 27 | pub unsafe fn read(cpu_id: u32) -> &'static ProcessorInfo { 28 | let ptr: *const ProcessorInfo = crate::kernel_constants::PROCESS_PROCESSOR_INFO_TABLE.as_ptr(); 29 | &*ptr.add(cpu_id as usize) 30 | } 31 | 32 | /// Reads entry for the current processor. 33 | /// Note that process switch can occur anywhere in usermode, 34 | /// and the data returned by the entry might not for the current cpu. 35 | /// 36 | /// # Safety 37 | /// 38 | /// Must be only called in when process page tables are active, 39 | /// kernel mode doesn't have anything mapped to this address. 40 | pub unsafe fn read_current() -> &'static ProcessorInfo { 41 | let rcx: u64; 42 | asm!( 43 | "rdtscp", 44 | out("rdx") _, 45 | out("rax") _, 46 | out("rcx") rcx, 47 | options(nomem, nostack) 48 | ); 49 | read(rcx as u32) 50 | } 51 | -------------------------------------------------------------------------------- /libs/d7abi/src/syscall/mod.rs: -------------------------------------------------------------------------------- 1 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | mod types; 5 | 6 | pub use self::types::*; 7 | 8 | #[derive(Debug, TryFromPrimitive)] 9 | #[allow(non_camel_case_types)] 10 | #[repr(u64)] 11 | pub enum SyscallNumber { 12 | exit = 0x00, 13 | get_pid = 0x01, 14 | debug_print = 0x02, 15 | exec = 0x30, 16 | random = 0x40, 17 | sched_yield = 0x50, 18 | sched_sleep_ns = 0x51, 19 | ipc_subscribe = 0x70, 20 | ipc_unsubscribe = 0x71, 21 | ipc_publish = 0x72, 22 | ipc_deliver = 0x73, 23 | ipc_deliver_reply = 0x74, 24 | ipc_receive = 0x75, 25 | ipc_acknowledge = 0x76, 26 | ipc_select = 0x77, 27 | kernel_log_read = 0x80, 28 | irq_set_handler = 0x84, 29 | mmap_physical = 0x90, 30 | dma_allocate = 0x92, 31 | dma_free = 0x93, 32 | mem_alloc = 0x94, 33 | mem_dealloc = 0x95, 34 | } 35 | 36 | #[derive(Debug, Copy, Clone, TryFromPrimitive, IntoPrimitive, Deserialize, Serialize)] 37 | #[allow(non_camel_case_types)] 38 | #[repr(u64)] 39 | pub enum SyscallErrorCode { 40 | unknown = 0, 41 | /// Requested operation is not supported yet 42 | unsupported, 43 | /// Not enough memory available for requested action 44 | out_of_memory, 45 | /// Empty list given, but now allowed 46 | empty_list_argument, 47 | /// Argument is too large to process 48 | too_large, 49 | /// System call done in nonblocking mode would block 50 | would_block, 51 | /// Invalid topic or topic filter 52 | ipc_invalid_topic, 53 | /// Mutually exclusive filter is already in use 54 | ipc_filter_exclusion, 55 | /// Reliable transfer failed: no targets selected 56 | ipc_delivery_no_target, 57 | /// Reliable transfer failed: target inbox is full 58 | ipc_delivery_target_full, 59 | /// Reliable transfer failed: target negative acknowledged 60 | ipc_delivery_target_nack, 61 | /// Attempt use unsubscribed id 62 | ipc_unsubscribed, 63 | /// Attempt to acknowledge a message again 64 | ipc_re_acknowledge, 65 | /// Someone else has already connected to this pipe 66 | ipc_pipe_reserved, 67 | /// Sender side process of the pipe has been terminated 68 | ipc_pipe_sender_terminated, 69 | /// Permission error 70 | ipc_permission_error, 71 | /// Invalid UTF-8 72 | invalid_utf8, 73 | /// Invalid alignment of a pointer 74 | ptr_unaligned, 75 | /// Invalid or unsupported memory protection flags given to mmap 76 | mmap_invalid_protection_flags, 77 | /// A specific aligment or size is required, but not respected 78 | mmap_incorrect_alignment, 79 | /// Operation is not allowed 80 | mmap_permission_error, 81 | } 82 | -------------------------------------------------------------------------------- /libs/d7abi/src/syscall/types.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | bitflags! { 4 | pub struct MemoryProtectionFlags: u8 { 5 | const READ = (1 << 0); 6 | const WRITE = (1 << 1); 7 | const EXECUTE = (1 << 2); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libs/d7boot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7boot" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies.cpuio] # Cpu port IO 18 | git = "https://github.com/Dentosal/cpuio-rs" 19 | -------------------------------------------------------------------------------- /libs/d7boot/linker.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(start) 3 | 4 | SECTIONS { 5 | . = 0x8000; 6 | 7 | /* ensure that the bootloader entry code is at the beginning */ 8 | .entry : ALIGN(0x8) { 9 | KEEP(*(.entry)) 10 | } 11 | 12 | .text : ALIGN(0x8) { 13 | *(.text .text.*) 14 | } 15 | 16 | .rodata : ALIGN(0x8) { 17 | KEEP(*(.rodata .rodata.*)) 18 | } 19 | 20 | .data : ALIGN(0x8) { 21 | *(.data .data.*) 22 | } 23 | 24 | .bss : ALIGN(0x8) { 25 | *(.bss .bss.*) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libs/d7boot/src/entry.asm: -------------------------------------------------------------------------------- 1 | [BITS 64] 2 | 3 | global start 4 | extern d7boot 5 | 6 | section .entry 7 | start: 8 | cli 9 | 10 | ; update segments 11 | mov dx, 0x10 12 | mov ss, dx ; stack segment 13 | mov ds, dx ; data segment 14 | mov es, dx ; extra segment 15 | mov fs, dx ; f-segment 16 | mov gs, dx ; g-segment 17 | 18 | ; set up stack 19 | mov rsp, stack_top 20 | 21 | ; jump to bootloader 22 | jmp d7boot 23 | 24 | ; reserve space for stack 25 | section .bss 26 | stack_bottom: 27 | resb (4096*100) 28 | stack_top: 29 | -------------------------------------------------------------------------------- /libs/d7initrd/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7initrd" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | static_assertions = "1.1" # Compile time assertions 9 | pinecone = "0.2" # Encoding format 10 | 11 | [dependencies.serde] # Serde 12 | version = "1.0" 13 | default-features = false 14 | features = ["alloc", "derive"] -------------------------------------------------------------------------------- /libs/d7initrd/README.md: -------------------------------------------------------------------------------- 1 | D7_StaticFS 2 | =========== 3 | 4 | Minimal read-optimized filesystem. Just a static file allocation table on disk. Writing is slow and unpleasant, but possible. All values are little-endian. 5 | 6 | ## Disk Layout 7 | 8 | MBR contains a 32bit LBA sector number. It's located just before the boot signature, at offset `0x1fa`. It is the first sector after kernel section. File table is located there. After the file table, there are files. 9 | 10 | ## File Table 11 | 12 | The file table begins with a simple 16-byte header. 13 | 14 | Offset | Size | Content 15 | -------|------|-------- 16 | 0 | 4 | Magic number 0xd7cafed7 17 | 4 | 4 | Length of file list in bytes 18 | 8 | 8 | Length of the whole initrd in bytes 19 | 20 | The header is followed by an array of file entries. 21 | -------------------------------------------------------------------------------- /libs/d7initrd/src/lib.rs: -------------------------------------------------------------------------------- 1 | // No std 2 | #![no_std] 3 | 4 | extern crate alloc; 5 | 6 | use alloc::string::String; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | pub const SECTOR_SIZE: u64 = 0x200; 10 | 11 | /// Offset in MBR: Separator between the kernel and the ramdisk 12 | pub const MBR_POSITION_S: u16 = 0x01f6; 13 | 14 | /// Offset in MBR: End of ramdisk (stage0 loads sectors until this) 15 | pub const MBR_POSITION_E: u16 = 0x01fa; 16 | 17 | pub const HEADER_MAGIC: u32 = 0xd7_ca_fe_d7; 18 | pub const HEADER_SIZE_BYTES: usize = 16; 19 | 20 | /// Convert file byte size to number of sectors required 21 | pub const fn to_sectors_round_up(p: u64) -> u64 { 22 | (p + SECTOR_SIZE - 1) / SECTOR_SIZE 23 | } 24 | 25 | static_assertions::const_assert_eq!(to_sectors_round_up(0), 0); 26 | static_assertions::const_assert_eq!(to_sectors_round_up(1), 1); 27 | static_assertions::const_assert_eq!(to_sectors_round_up(511), 1); 28 | static_assertions::const_assert_eq!(to_sectors_round_up(512), 1); 29 | static_assertions::const_assert_eq!(to_sectors_round_up(513), 2); 30 | 31 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 32 | pub struct FileEntry { 33 | /// Filename 34 | pub name: String, 35 | /// Size, in bytes 36 | pub size: u64, 37 | /// Offset from the start of the file list 38 | pub offset: u64, 39 | } 40 | impl FileEntry { 41 | pub fn size_sectors(&self) -> u64 { 42 | to_sectors_round_up(self.size) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/d7keymap/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7keymap" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [profile.dev] 9 | panic = "abort" 10 | 11 | [profile.release] 12 | panic = "abort" 13 | 14 | [dependencies.hashbrown] # HashMap for no_std contexts 15 | version = "0.11" 16 | features = ["nightly", "inline-more", "serde"] 17 | 18 | [dependencies.serde] # Serde 19 | version = "1.0" 20 | default-features = false 21 | features = ["alloc", "derive"] 22 | 23 | [dependencies.serde_json] # JSON support 24 | version = "1.0" 25 | default-features = false 26 | features = ["alloc"] -------------------------------------------------------------------------------- /libs/d7keymap/examples/keycodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "5": "F1", 3 | "6": "F2", 4 | "4": "F3", 5 | "12": "F4", 6 | "3": "F5", 7 | "11": "F6", 8 | "10": "F8", 9 | "1": "F9", 10 | "9": "F10", 11 | "120": "F11", 12 | "7": "F12", 13 | "13": "Tab", 14 | "14": "Acute", 15 | "17": "LeftAlt", 16 | "18": "LeftShift", 17 | "20": "LeftCtrl", 18 | "69": "0", 19 | "22": "1", 20 | "30": "2", 21 | "38": "3", 22 | "37": "4", 23 | "46": "5", 24 | "54": "6", 25 | "61": "7", 26 | "62": "8", 27 | "70": "9", 28 | "28": "A", 29 | "50": "B", 30 | "33": "C", 31 | "35": "D", 32 | "36": "E", 33 | "43": "F", 34 | "52": "G", 35 | "51": "H", 36 | "67": "I", 37 | "59": "J", 38 | "66": "K", 39 | "75": "L", 40 | "58": "M", 41 | "49": "N", 42 | "68": "O", 43 | "77": "P", 44 | "21": "Q", 45 | "45": "R", 46 | "27": "S", 47 | "44": "T", 48 | "60": "U", 49 | "42": "V", 50 | "29": "W", 51 | "34": "X", 52 | "53": "Y", 53 | "26": "Z", 54 | "41": "Space", 55 | "65": "Comma", 56 | "73": "Period", 57 | "74": "Slash", 58 | "76": "Semicolon", 59 | "78": "Minus", 60 | "82": "Singlequote", 61 | "84": "LeftBracket", 62 | "85": "Equals", 63 | "88": "CapsLock", 64 | "89": "RightShift", 65 | "90": "Enter", 66 | "91": "RightBracket", 67 | "93": "Backslash", 68 | "102": "Backspace", 69 | "105": "Keypad_1", 70 | "107": "Keypad_4", 71 | "108": "Keypad_7", 72 | "112": "Keypad_0", 73 | "113": "Keypad_Period", 74 | "114": "Keypad_2", 75 | "115": "Keypad_5", 76 | "116": "Keypad_6", 77 | "117": "Keypad_8", 78 | "118": "Escape", 79 | "119": "NumberLock", 80 | "121": "Keypad_Plus", 81 | "122": "Keypad_3", 82 | "123": "Keypad_Minus", 83 | "124": "Keypad_Multiply", 84 | "125": "Keypad_9", 85 | "126": "ScrollLock", 86 | "131": "F7", 87 | "272": "Multimedia_WWW_Search", 88 | "273": "RightAlt", 89 | "276": "RightCtrl", 90 | "277": "Multimedia_PreviousTrack", 91 | "280": "Multimedia_WWW_Favourites", 92 | "287": "GUI_Left", 93 | "288": "Multimedia_WWW_Refresh", 94 | "289": "Multimedia_VolumeDown", 95 | "291": "Multimedia_Mute", 96 | "295": "GUI_Right", 97 | "296": "Multimedia_WWW_Stop", 98 | "299": "Multimedia_Calculator", 99 | "303": "Apps", 100 | "304": "Multimedia_WWW_Forward", 101 | "306": "Multimedia_VolumeUp", 102 | "308": "Multimedia_PlayPause", 103 | "311": "ACPI_Power", 104 | "312": "Multimedia_WWW_Back", 105 | "314": "Multimedia_WWW_Home", 106 | "315": "Multimedia_Stop", 107 | "319": "ACPI_Sleep", 108 | "320": "Multimedia_My_Computer", 109 | "328": "Multimedia_Email", 110 | "330": "Keypad_Divide", 111 | "333": "Multimedia_NextTrack", 112 | "336": "Multimedia_MediaSelect", 113 | "346": "Keypad_Enter", 114 | "350": "ACPI_Wake", 115 | "361": "End", 116 | "363": "CursorLeft", 117 | "364": "Home", 118 | "368": "Insert", 119 | "369": "Delete", 120 | "370": "CursorDown", 121 | "372": "CursorRight", 122 | "373": "CursorUp", 123 | "378": "PageDown", 124 | "381": "PageU" 125 | } -------------------------------------------------------------------------------- /libs/d7keymap/examples/keymap.json: -------------------------------------------------------------------------------- 1 | { 2 | "modifiers": [ 3 | "LeftCtrl", 4 | "RightShift" 5 | ], 6 | "mapping": { 7 | "Space": {"text": " "}, 8 | "A": {"text": "a"}, 9 | "LeftShift+A": {"text": "A"}, 10 | "RightShift+A": {"text": "A"}, 11 | "RightAlt+E": {"text": "€"}, 12 | "Shift+Acute": {"buffer": "`"}, 13 | "Escape A": {"text": "Escape first, then A"}, 14 | "CapsLock": {"remap": "Escape"} 15 | } 16 | } -------------------------------------------------------------------------------- /libs/d7keymap/generate.py: -------------------------------------------------------------------------------- 1 | # This generates all lower and uppercase letters, digits and some other 2 | # usual characters giving a good starting point for building keymaps 3 | 4 | import json 5 | import string 6 | 7 | 8 | def main(): 9 | modifiers = [ 10 | "LeftShift", 11 | "RightShift", 12 | "LeftSuper", 13 | "RightSuper", 14 | "LeftMeta", 15 | "RightMeta", 16 | "LeftAlt", 17 | "RightAlt", 18 | "LeftCtrl", 19 | "RightCtrl", 20 | "LeftHyper", 21 | "RightHyper", 22 | ] 23 | 24 | keymap = { 25 | "Space": {"text": " "}, 26 | "Comma": {"text": ","}, 27 | "LeftShift+Comma": {"text": ";"}, 28 | "RightShift+Comma": {"text": ";"}, 29 | "Period": {"text": "."}, 30 | "LeftShift+Period": {"text": ":"}, 31 | "RightShift+Period": {"text": ":"}, 32 | } 33 | 34 | for letter in string.ascii_lowercase: 35 | keymap[letter.upper()] = {"text": letter} 36 | keymap["LeftShift+" + letter.upper()] = {"text": letter.upper()} 37 | keymap["RightShift+" + letter.upper()] = {"text": letter.upper()} 38 | 39 | for digit in string.digits: 40 | keymap[digit] = {"text": digit} 41 | 42 | print(json.dumps({"modifiers": modifiers, "mapping": keymap})) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /libs/d7keymap/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Features 2 | #![feature(allocator_api)] 3 | // No-std 4 | #![cfg_attr(not(test), no_std)] 5 | 6 | extern crate alloc; 7 | 8 | use core::hash::{Hash, Hasher}; 9 | use core::str::FromStr; 10 | 11 | use alloc::borrow::ToOwned; 12 | use alloc::string::String; 13 | use alloc::vec::Vec; 14 | use hashbrown::{HashMap, HashSet}; 15 | 16 | use serde::{de, Deserialize, Deserializer}; 17 | 18 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 19 | #[serde(transparent)] 20 | pub struct KeySymbol(String); 21 | impl KeySymbol { 22 | pub fn new(s: &str) -> Self { 23 | Self(s.to_owned()) 24 | } 25 | 26 | pub fn as_str(&self) -> &str { 27 | &self.0 28 | } 29 | } 30 | 31 | pub type KeyCode = u16; 32 | pub type KeyCodes = HashMap; 33 | 34 | #[derive(Debug, Clone, PartialEq, Eq)] 35 | pub struct Combination { 36 | pub modifiers: HashSet, 37 | pub main: KeySymbol, 38 | } 39 | impl Combination { 40 | pub fn matches(&self, current: &KeySymbol, pressed: &HashSet) -> bool { 41 | &self.modifiers == pressed && &self.main == current 42 | } 43 | } 44 | impl FromStr for Combination { 45 | type Err = String; 46 | fn from_str(s: &str) -> Result { 47 | let mut modifiers: Vec<_> = s.split("+").map(|m| KeySymbol(m.to_owned())).collect(); 48 | let main = modifiers.pop().unwrap(); 49 | Ok(Self { 50 | modifiers: modifiers.into_iter().collect(), 51 | main, 52 | }) 53 | } 54 | } 55 | impl Hash for Combination { 56 | fn hash(&self, state: &mut H) { 57 | self.main.hash(state); 58 | } 59 | } 60 | impl<'de> Deserialize<'de> for Combination { 61 | fn deserialize(deserializer: D) -> Result 62 | where D: Deserializer<'de> { 63 | let s = String::deserialize(deserializer)?; 64 | FromStr::from_str(&s).map_err(de::Error::custom) 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone, Deserialize)] 69 | #[serde(rename_all = "kebab-case")] 70 | pub struct KeyMap { 71 | /// Only these keys can be used as modifiers 72 | pub modifiers: HashSet, 73 | /// Mapping from key combinations to actions 74 | pub mapping: HashMap, 75 | } 76 | 77 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)] 78 | #[serde(rename_all = "kebab-case")] 79 | pub enum KeyAction { 80 | /// Product text (prefix from dead-key buffer if any and normalize) 81 | Text(String), 82 | /// Insert to dead-key buffer 83 | Buffer(String), 84 | /// Remap to another key symbol 85 | Remap(KeySymbol), 86 | /// Ignore this keypress 87 | Ignore, 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | use std::fs; 94 | 95 | #[test] 96 | fn test_keycodes() { 97 | let s = fs::read("examples/keycodes.json").unwrap(); 98 | let data: KeyCodes = serde_json::from_slice(&s).unwrap(); 99 | assert_eq!(data[&17], KeySymbol("LeftAlt".to_owned())); 100 | } 101 | 102 | #[test] 103 | fn test_keycombinations() { 104 | let s = fs::read("examples/keymap.json").unwrap(); 105 | let _data: KeyMap = serde_json::from_slice(&s).unwrap(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /libs/d7net/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7net" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | bitflags = "1.3" 9 | log = "0.4" 10 | 11 | [dependencies.hashbrown] # HashMap for no_std contexts 12 | version = "0.11" 13 | features = ["nightly", "inline-more", "serde"] 14 | 15 | [dependencies.serde] # Serde 16 | version = "1.0" 17 | default-features = false 18 | features = ["alloc", "derive"] 19 | 20 | [dependencies.num_enum] 21 | git = "https://github.com/Dentosal/num_enum_simplified" 22 | 23 | [dependencies.tcpstate] 24 | git = "https://github.com/Dentosal/tcpstate" 25 | 26 | [dev-dependencies] 27 | env_logger = "0.9.0" -------------------------------------------------------------------------------- /libs/d7net/README.md: -------------------------------------------------------------------------------- 1 | # `d7net` - Network stack data formats 2 | 3 | Supported protocols: Ethernet, ARP, IPv4, TCP 4 | Coming soon: UDP, IPv6, DHCP, DNS 5 | 6 | 7 | ## Current limitations 8 | 9 | * Error handling: Invalid data always panics 10 | 11 | ## Unsupported by design 12 | 13 | * ARP only supports MAC addresses as HW addresses 14 | * IPv4 Options fields are not supported 15 | * Only the most commonly used TCP Options fields are supported 16 | * TCP Urgency fields are not supported -------------------------------------------------------------------------------- /libs/d7net/src/builder/ipv4_tcp.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use crate::checksum::inet_checksum; 4 | use crate::ipv4; 5 | use crate::tcp; 6 | use crate::{IpProtocol, Ipv4Addr}; 7 | 8 | #[derive(Debug)] 9 | pub struct Builder { 10 | pub ipv4_header: ipv4::Header, 11 | pub tcp_header: tcp::SegmentHeader, 12 | pub payload: Vec, 13 | } 14 | impl Builder { 15 | /// TODO: fragmentation support 16 | pub fn new( 17 | src_ip: Ipv4Addr, dst_ip: Ipv4Addr, src_port: u16, dst_port: u16, sequence: u32, 18 | ack_number: u32, window_size: u16, flags: tcp::SegmentFlags, payload: Vec, 19 | ) -> Self { 20 | Self { 21 | ipv4_header: ipv4::Header { 22 | dscp_and_ecn: 0, 23 | payload_len: 0, 24 | identification: 0, 25 | flags_and_frament: 0, 26 | ttl: 64, 27 | protocol: IpProtocol::TCP, 28 | src_ip, 29 | dst_ip, 30 | }, 31 | tcp_header: tcp::SegmentHeader { 32 | src_port, 33 | dst_port, 34 | sequence, 35 | ack_number, 36 | flags, 37 | window_size, 38 | options: tcp::SegmentOptions::empty(), 39 | checksum: 0, 40 | offset: tcp::SegmentHeader::OFFSET_NO_OPTIONS, 41 | }, 42 | payload, 43 | } 44 | } 45 | 46 | pub fn build(mut self) -> Vec { 47 | self.tcp_header.checksum = 0; 48 | let mut cksm_buf = Vec::new(); 49 | cksm_buf.extend(&self.ipv4_header.src_ip.0); 50 | cksm_buf.extend(&self.ipv4_header.dst_ip.0); 51 | cksm_buf.push(0); 52 | cksm_buf.push(IpProtocol::TCP as u8); 53 | cksm_buf.extend(u16::to_be_bytes( 54 | (self.tcp_header.to_bytes().len() + self.payload.len()) as u16, 55 | )); 56 | cksm_buf.extend(&self.tcp_header.to_bytes()); 57 | cksm_buf.extend(&self.payload); 58 | self.tcp_header.checksum = inet_checksum(&cksm_buf); 59 | 60 | let mut result = Vec::new(); 61 | let tcp_header = self.tcp_header.to_bytes(); 62 | result.extend( 63 | &self 64 | .ipv4_header 65 | .to_bytes(tcp_header.len() + self.payload.len()), 66 | ); 67 | result.extend(&tcp_header); 68 | result.extend(&self.payload); 69 | result 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /libs/d7net/src/builder/ipv4_udp.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use crate::checksum::inet_checksum; 4 | use crate::ipv4; 5 | use crate::udp; 6 | use crate::{IpProtocol, Ipv4Addr}; 7 | 8 | pub struct Builder { 9 | pub ipv4_header: ipv4::Header, 10 | pub udp_header: udp::Header, 11 | pub payload: Vec, 12 | } 13 | impl Builder { 14 | /// TODO: fragmentation support 15 | pub fn new( 16 | src_ip: Ipv4Addr, dst_ip: Ipv4Addr, src_port: u16, dst_port: u16, payload: Vec, 17 | ) -> Self { 18 | Self { 19 | ipv4_header: ipv4::Header { 20 | dscp_and_ecn: 0, 21 | payload_len: 0, 22 | identification: 0, 23 | flags_and_frament: 0, 24 | ttl: 64, 25 | protocol: IpProtocol::UDP, 26 | src_ip, 27 | dst_ip, 28 | }, 29 | udp_header: udp::Header { 30 | src_port, 31 | dst_port, 32 | length: 0, // Filled in later 33 | checksum: 0, // Filled in later 34 | }, 35 | payload, 36 | } 37 | } 38 | 39 | pub fn build(mut self) -> Vec { 40 | self.udp_header.length = (8 + self.payload.len()) as u16; 41 | let mut cksm_buf = Vec::new(); 42 | cksm_buf.extend(&self.ipv4_header.src_ip.0); 43 | cksm_buf.extend(&self.ipv4_header.dst_ip.0); 44 | cksm_buf.push(0); 45 | cksm_buf.push(IpProtocol::UDP as u8); 46 | cksm_buf.extend(&u16::to_be_bytes(self.udp_header.length)); 47 | cksm_buf.extend(&self.udp_header.to_bytes()); 48 | cksm_buf.extend(&self.payload); 49 | self.udp_header.checksum = inet_checksum(&cksm_buf); 50 | 51 | let mut result = Vec::new(); 52 | let udp_header = self.udp_header.to_bytes(); 53 | result.extend( 54 | &self 55 | .ipv4_header 56 | .to_bytes(udp_header.len() + self.payload.len()), 57 | ); 58 | result.extend(&udp_header); 59 | result.extend(&self.payload); 60 | result 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libs/d7net/src/builder/mod.rs: -------------------------------------------------------------------------------- 1 | //! Easy-to-use packet builders with sensible defaults 2 | 3 | pub mod ipv4_tcp; 4 | pub mod ipv4_udp; 5 | -------------------------------------------------------------------------------- /libs/d7net/src/checksum.rs: -------------------------------------------------------------------------------- 1 | /// Standard internet checksum 2 | pub fn inet_checksum(data: &[u8]) -> u16 { 3 | let mut result: u16 = 0; 4 | for chunk in data.chunks(2) { 5 | let v = u16::from_be_bytes([chunk[0], if chunk.len() == 2 { chunk[1] } else { 0 }]); 6 | let (r, c) = result.overflowing_add(v); 7 | result = r; 8 | if c { 9 | let (r, c) = result.overflowing_add(1); 10 | result = r; 11 | if c { 12 | result += 1; 13 | } 14 | } 15 | } 16 | !result 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::*; 22 | 23 | #[test] 24 | fn test_inet_checksum() { 25 | let ipv4_header = vec![ 26 | 0x45, 0x00, 0x00, 0x73, 0x00, 0x00, 0x40, 0x00, 0x40, 0x11, // Checksum start 27 | 0x00, 0x00, // Checksum end 28 | 0xc0, 0xa8, 0x00, 0x01, 0xc0, 0xa8, 0x00, 0xc7, 29 | ]; 30 | let cksm = inet_checksum(&ipv4_header); 31 | println!("{:04x} {:04x}", cksm, 0xb861); 32 | assert_eq!(cksm, 0xb861); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/d7net/src/ethernet.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::{EtherType, MacAddr}; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 7 | pub struct Frame { 8 | pub header: FrameHeader, 9 | pub payload: Vec, 10 | } 11 | impl Frame { 12 | pub fn from_bytes(input: &[u8]) -> Self { 13 | Self { 14 | header: FrameHeader::from_bytes(&input[0..14]), 15 | payload: input[14..].to_vec(), 16 | } 17 | } 18 | 19 | pub fn to_bytes(self) -> Vec { 20 | let mut result = Vec::new(); 21 | result.extend(&self.header.to_bytes()); 22 | result.extend(&self.payload); 23 | // Return 24 | result 25 | } 26 | } 27 | 28 | /// https://en.wikipedia.org/wiki/Ethernet_frame#Frame_%E2%80%93_data_link_layer 29 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] 30 | pub struct FrameHeader { 31 | pub dst_mac: MacAddr, 32 | pub src_mac: MacAddr, 33 | pub ethertype: EtherType, 34 | } 35 | impl FrameHeader { 36 | pub fn from_bytes(input: &[u8]) -> Self { 37 | Self { 38 | dst_mac: MacAddr::from_bytes(&input[0..6]), 39 | src_mac: MacAddr::from_bytes(&input[6..12]), 40 | ethertype: EtherType::from_bytes(&input[12..14]), 41 | } 42 | } 43 | 44 | pub fn to_bytes(self) -> Vec { 45 | let mut result = Vec::new(); 46 | result.extend(&self.dst_mac.0); 47 | result.extend(&self.src_mac.0); 48 | result.extend(&self.ethertype.to_bytes()); 49 | // Return 50 | result 51 | } 52 | } 53 | 54 | #[cfg(test)] 55 | mod test { 56 | use super::*; 57 | 58 | #[test] 59 | fn test_parse_frame_header() { 60 | let example: Vec = vec![ 61 | 255, 255, 255, 255, 255, 255, // Dst MAC 62 | 1, 2, 3, 4, 5, 6, // Src MAC 63 | 8, 6, // EtherType 64 | ]; 65 | 66 | let frame_header = FrameHeader::from_bytes(&example); 67 | assert_eq!(frame_header, FrameHeader { 68 | dst_mac: MacAddr::from_bytes(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff]), 69 | src_mac: MacAddr::from_bytes(&[1, 2, 3, 4, 5, 6]), 70 | ethertype: EtherType::ARP, 71 | }); 72 | 73 | assert_eq!(frame_header.to_bytes(), example); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /libs/d7net/src/ethertype.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use num_enum::TryFromPrimitive; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// https://en.wikipedia.org/wiki/EtherType#Examples 6 | #[derive( 7 | Debug, 8 | Clone, 9 | Copy, 10 | PartialEq, 11 | Eq, 12 | PartialOrd, 13 | Ord, 14 | Hash, 15 | TryFromPrimitive, 16 | Deserialize, 17 | Serialize, 18 | )] 19 | #[repr(u16)] 20 | pub enum EtherType { 21 | Ipv4 = 0x0800, 22 | ARP = 0x0806, 23 | WakeOnLan = 0x0842, 24 | SLPP = 0x8102, 25 | Ipv6 = 0x86dd, 26 | EthernetFlowControl = 0x8808, 27 | EthernetSlowProtocol = 0x8809, 28 | } 29 | impl EtherType { 30 | pub fn from_bytes(bytes: &[u8]) -> Self { 31 | let n = u16::from_be_bytes([bytes[0], bytes[1]]); 32 | Self::try_from(n).unwrap_or_else(|_| panic!("Unknwn EtherType {:04x}", n)) 33 | } 34 | 35 | pub fn to_bytes(self) -> [u8; 2] { 36 | u16::to_be_bytes(self as u16) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libs/d7net/src/ipv4.rs: -------------------------------------------------------------------------------- 1 | //! https://en.wikipedia.org/wiki/IPv4#Packet_structure 2 | 3 | use alloc::vec::Vec; 4 | use core::convert::TryFrom; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::Ipv4Addr; 8 | 9 | pub use crate::ip_protocol::IpProtocol; 10 | 11 | #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] 12 | pub struct Packet { 13 | pub header: Header, 14 | pub payload: Vec, 15 | } 16 | impl Packet { 17 | pub fn from_bytes(input: &[u8]) -> Self { 18 | let header = Header::from_bytes(&input[..20]); 19 | Self { 20 | header, 21 | payload: input[20..20 + (header.payload_len as usize)].to_vec(), 22 | } 23 | } 24 | } 25 | 26 | /// Does not support Options field 27 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] 28 | pub struct Header { 29 | pub dscp_and_ecn: u8, 30 | pub payload_len: u16, 31 | pub identification: u16, 32 | pub flags_and_frament: u16, 33 | pub ttl: u8, 34 | pub protocol: IpProtocol, 35 | pub src_ip: Ipv4Addr, 36 | pub dst_ip: Ipv4Addr, 37 | } 38 | impl Header { 39 | pub fn from_bytes(input: &[u8]) -> Self { 40 | assert!(input[0] >> 4 == 4, "Version not Ipv4"); 41 | assert!(input[0] & 0xf == 5, "IHL != 5 (Options not supported)"); 42 | 43 | let total_len = u16::from_be_bytes([input[2], input[3]]); 44 | assert!(total_len >= 20, "Packet total_length too small"); 45 | 46 | // TODO: verify header checksum 47 | 48 | Self { 49 | dscp_and_ecn: input[1], 50 | payload_len: total_len - 20, 51 | identification: u16::from_be_bytes([input[4], input[5]]), 52 | flags_and_frament: u16::from_be_bytes([input[6], input[7]]), 53 | ttl: input[8], 54 | protocol: IpProtocol::try_from(input[9]).expect("Unknown IP protocol"), 55 | src_ip: Ipv4Addr::from_bytes(&input[12..16]), 56 | dst_ip: Ipv4Addr::from_bytes(&input[16..20]), 57 | } 58 | } 59 | 60 | pub fn to_bytes(self, payload_len: usize) -> Vec { 61 | let mut result = Vec::new(); 62 | result.push(0x45); // Version and IHL 63 | result.push(self.dscp_and_ecn); 64 | result.extend(&u16::to_be_bytes(20 + (payload_len as u16))); 65 | result.extend(&u16::to_be_bytes(self.identification)); 66 | result.extend(&u16::to_be_bytes(self.flags_and_frament)); 67 | result.push(self.ttl); 68 | result.push(self.protocol as u8); 69 | result.extend(&u16::to_be_bytes(0)); // Checksum 70 | result.extend(&self.src_ip.0); 71 | result.extend(&self.dst_ip.0); 72 | let checksum = crate::checksum::inet_checksum(&result); 73 | result[10..12].copy_from_slice(&u16::to_be_bytes(checksum)); 74 | result 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /libs/d7net/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Panics on invalid data 2 | 3 | #![cfg_attr(not(test), no_std)] 4 | #![feature(default_free_fn, duration_constants)] 5 | // Lints 6 | #![allow(incomplete_features)] 7 | 8 | #[macro_use] 9 | extern crate alloc; 10 | 11 | mod checksum; 12 | mod ethertype; 13 | mod ip_addr; 14 | mod ip_protocol; 15 | mod mac; 16 | 17 | pub mod arp; 18 | pub mod dhcp; 19 | pub mod dns; 20 | pub mod ethernet; 21 | pub mod ipv4; 22 | pub mod tcp; 23 | pub mod udp; 24 | 25 | pub mod builder; 26 | 27 | pub use self::ethertype::EtherType; 28 | pub use self::ip_addr::*; 29 | pub use self::ip_protocol::IpProtocol; 30 | pub use self::mac::MacAddr; 31 | -------------------------------------------------------------------------------- /libs/d7net/src/mac.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] 5 | pub struct MacAddr(pub [u8; 6]); 6 | 7 | impl MacAddr { 8 | pub const ZERO: Self = Self([0, 0, 0, 0, 0, 0]); 9 | pub const BROADCAST: Self = Self([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); 10 | 11 | pub fn from_bytes(bytes: &[u8]) -> Self { 12 | assert!(bytes.len() == 6); 13 | let mut data = [0; 6]; 14 | data.copy_from_slice(bytes); 15 | MacAddr(data) 16 | } 17 | } 18 | 19 | impl fmt::Debug for MacAddr { 20 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | write!( 22 | f, 23 | "MacAddr({:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})", 24 | self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], 25 | ) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libs/d7net/src/tcp/mod.rs: -------------------------------------------------------------------------------- 1 | mod segment; 2 | 3 | pub use segment::*; 4 | 5 | pub use tcpstate as state; 6 | -------------------------------------------------------------------------------- /libs/d7net/src/udp.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | #[derive(Debug)] 4 | pub struct Packet { 5 | pub header: Header, 6 | pub payload: Vec, 7 | } 8 | impl Packet { 9 | pub fn from_bytes(bytes: &[u8]) -> Self { 10 | let header = Header::from_bytes(&bytes[..8]); 11 | let payload = bytes[8..(header.length as usize)].to_vec(); 12 | Self { header, payload } 13 | } 14 | 15 | pub fn to_bytes(self) -> Vec { 16 | let mut result = self.header.to_bytes().to_vec(); 17 | result.extend(self.payload); 18 | assert!(result.len() == (self.header.length as usize)); 19 | result 20 | } 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct Header { 25 | pub src_port: u16, 26 | pub dst_port: u16, 27 | pub length: u16, 28 | pub checksum: u16, 29 | } 30 | impl Header { 31 | pub fn from_bytes(bytes: &[u8]) -> Self { 32 | let src_port = u16::from_be_bytes([bytes[0], bytes[1]]); 33 | let dst_port = u16::from_be_bytes([bytes[2], bytes[3]]); 34 | let length = u16::from_be_bytes([bytes[4], bytes[5]]); 35 | let checksum = u16::from_be_bytes([bytes[6], bytes[7]]); 36 | Self { 37 | src_port, 38 | dst_port, 39 | length, 40 | checksum, 41 | } 42 | } 43 | 44 | pub fn to_bytes(&self) -> [u8; 8] { 45 | let mut result = [0u8; 8]; 46 | result[0..2].copy_from_slice(&u16::to_be_bytes(self.src_port)); 47 | result[2..4].copy_from_slice(&u16::to_be_bytes(self.dst_port)); 48 | result[4..6].copy_from_slice(&u16::to_be_bytes(self.length)); 49 | result[6..8].copy_from_slice(&u16::to_be_bytes(self.checksum)); 50 | result 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libs/d7net/tests/arp_simple.rs: -------------------------------------------------------------------------------- 1 | use d7net::*; 2 | 3 | #[test] 4 | fn arp_simple() { 5 | let mac_addr: MacAddr = MacAddr::from_bytes(&[1, 2, 3, 4, 5, 6]); 6 | 7 | let mut packet = Vec::new(); 8 | 9 | // dst mac: broadcast 10 | packet.extend(&MacAddr::BROADCAST.0); 11 | 12 | // src mac: this computer 13 | packet.extend(&mac_addr.0); 14 | 15 | // ethertype: arp 16 | packet.extend(&EtherType::ARP.to_bytes()); 17 | 18 | // arp: HTYPE: ethernet 19 | packet.extend(&1u16.to_be_bytes()); 20 | 21 | // arp: PTYPE: ipv4 22 | packet.extend(&0x0800u16.to_be_bytes()); 23 | 24 | // arp: HLEN: 6 for mac addr 25 | packet.push(6); 26 | 27 | // arp: PLEN: 4 for ipv4 28 | packet.push(4); 29 | 30 | // arp: Opeeration: request 31 | packet.extend(&1u16.to_be_bytes()); 32 | 33 | // arp: SHA: our mac 34 | packet.extend(&mac_addr.0); 35 | 36 | // arp: SPA: our ip (hardcoded for now) 37 | packet.extend(&[192, 168, 10, 15]); 38 | 39 | // arp: THA: target mac, ignored 40 | packet.extend(&[0, 0, 0, 0, 0, 0]); 41 | 42 | // arp: TPA: target ip (bochs vnet router) 43 | packet.extend(&[192, 168, 10, 1]); 44 | 45 | // padding 46 | while packet.len() < 64 { 47 | packet.push(0); 48 | } 49 | 50 | let arpp = arp::Packet { 51 | ptype: EtherType::Ipv4, 52 | operation: arp::Operation::Request, 53 | src_hw: mac_addr, 54 | src_ip: Ipv4Addr::from_bytes(&[192, 168, 10, 15]), 55 | dst_hw: MacAddr::ZERO, 56 | dst_ip: Ipv4Addr::from_bytes(&[192, 168, 10, 1]), 57 | }; 58 | 59 | let ef = ethernet::Frame { 60 | header: ethernet::FrameHeader { 61 | dst_mac: MacAddr::BROADCAST, 62 | src_mac: mac_addr, 63 | ethertype: EtherType::ARP, 64 | }, 65 | payload: arpp.to_bytes(), 66 | }; 67 | 68 | let mut ef_packet = ef.to_bytes(); 69 | while ef_packet.len() < 64 { 70 | ef_packet.push(0); 71 | } 72 | 73 | assert_eq!(packet, ef_packet); 74 | } 75 | -------------------------------------------------------------------------------- /libs/d7net/tests/dns.rs: -------------------------------------------------------------------------------- 1 | use std::net::UdpSocket; 2 | 3 | use d7net::dns; 4 | 5 | #[test] 6 | fn test_udp_dns() -> std::io::Result<()> { 7 | let _ = env_logger::builder().is_test(true).try_init(); 8 | 9 | let socket = UdpSocket::bind("0.0.0.0:0")?; 10 | socket.connect("1.1.1.1:53")?; 11 | 12 | // A real domain 13 | 14 | let q = dns::make_question(1, "wikipedia.org", dns::QueryType::A); 15 | 16 | socket.send(&q)?; 17 | 18 | let mut buf = [0; 1024]; 19 | let (n, _src) = socket.recv_from(&mut buf)?; 20 | let buf = &mut buf[..n]; 21 | 22 | let reply = dns::parse_reply(&buf).expect("Resolution error"); 23 | assert_eq!(reply.req_id, 1); 24 | assert!(reply.records.expect("No such domain").len() >= 1); 25 | 26 | // Nonexistent domain 27 | 28 | let q = dns::make_question(1, "this-domain.does-not.exist.local", dns::QueryType::A); 29 | 30 | socket.send(&q)?; 31 | 32 | let mut buf = [0; 1024]; 33 | let (n, _src) = socket.recv_from(&mut buf)?; 34 | let buf = &mut buf[..n]; 35 | 36 | let reply = dns::parse_reply(&buf).expect("Resolution error"); 37 | assert!(reply.records.is_err()); 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /libs/d7pci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7pci" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | edition = "2018" 6 | 7 | [dependencies.serde] 8 | version = "1.0" 9 | default-features = false 10 | features = ["alloc", "derive"] 11 | -------------------------------------------------------------------------------- /libs/d7pci/README.md: -------------------------------------------------------------------------------- 1 | # `d7pci` - PCI device driver and data types 2 | -------------------------------------------------------------------------------- /libs/d7pci/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | #[macro_use] 4 | extern crate alloc; 5 | 6 | mod device; 7 | mod scan; 8 | mod util; 9 | 10 | pub use self::device::*; 11 | pub use self::scan::list_devices; 12 | -------------------------------------------------------------------------------- /libs/d7pci/src/scan.rs: -------------------------------------------------------------------------------- 1 | use super::device::{Device, DeviceClass, DeviceLocation}; 2 | use super::util::pci_read_device; 3 | 4 | use alloc::vec::Vec; 5 | 6 | // http://wiki.osdev.org/PCI#PCI_Device_Structure 7 | 8 | fn get_vendor_id(loc: DeviceLocation) -> u16 { 9 | (pci_read_device(loc, 0x0) & 0x0000FFFF) as u16 10 | } 11 | 12 | fn get_device_id(loc: DeviceLocation) -> u16 { 13 | ((pci_read_device(loc, 0x0) & 0xFFFF0000) >> 16) as u16 14 | } 15 | 16 | fn get_devclass(loc: DeviceLocation) -> DeviceClass { 17 | let d = pci_read_device(loc, 0x8); 18 | DeviceClass( 19 | ((d & 0xFF000000) >> 24) as u8, 20 | ((d & 0x00FF0000) >> 16) as u8, 21 | ((d & 0x0000FF00) >> 8) as u8, 22 | ) 23 | } 24 | 25 | fn get_header_type(loc: DeviceLocation) -> u8 { 26 | ((pci_read_device(loc, 0xC) & 0x00FF0000) >> 16) as u8 27 | } 28 | 29 | fn get_secondary_bus(loc: DeviceLocation) -> u8 { 30 | ((pci_read_device(loc, 0x18) & 0x0000FF00) >> 8) as u8 31 | } 32 | 33 | fn check_device(bus: u8, dev: u8) -> Vec { 34 | let mut result = Vec::new(); 35 | let vendor_id = get_vendor_id(DeviceLocation(bus, dev, 0)); 36 | if vendor_id != 0xFFFF { 37 | // Device exists 38 | result.extend(check_function(DeviceLocation(bus, dev, 0))); 39 | let header_type = get_header_type(DeviceLocation(bus, dev, 0)); 40 | if (header_type & 0x80) != 0 { 41 | // This is a multi-function device, so check remaining functions 42 | 43 | for f in 1..=8 { 44 | if get_vendor_id(DeviceLocation(bus, dev, f)) != 0xFFFF { 45 | result.extend(check_function(DeviceLocation(bus, dev, f))); 46 | } 47 | } 48 | } 49 | } 50 | result 51 | } 52 | 53 | fn check_function(loc: DeviceLocation) -> Vec { 54 | let mut result = Vec::new(); 55 | let dc = get_devclass(loc); 56 | result.push(Device::new(get_device_id(loc), get_vendor_id(loc), loc, dc)); 57 | 58 | if dc.0 == 0x06 && dc.1 == 0x04 { 59 | let secondary_bus: u8 = get_secondary_bus(loc); 60 | result.extend(check_bus(secondary_bus)); 61 | } 62 | result 63 | } 64 | 65 | fn check_bus(bus: u8) -> Vec { 66 | (0..=32) 67 | .map(|dev| check_device(bus, dev)) 68 | .flatten() 69 | .collect() 70 | } 71 | 72 | pub unsafe fn list_devices() -> Vec { 73 | let header_type = get_header_type(DeviceLocation(0, 0, 0)); 74 | if (header_type & 0x80) == 0 { 75 | // A single PCI host controller 76 | check_bus(0) 77 | } else { 78 | let mut result = Vec::new(); 79 | // Multiple PCI host controllers 80 | for func in 0..=8 { 81 | if get_vendor_id(DeviceLocation(0, 0, func)) != 0xFFFF { 82 | break; 83 | } 84 | // TODO: explain, and just check_bus(bus) 85 | let bus: u8 = func; 86 | result.extend(check_bus(bus)); 87 | } 88 | 89 | result 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /libs/d7pci/src/util.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | use super::device::DeviceLocation; 4 | 5 | pub const CONFIG_ADDR: usize = 0xCF8; 6 | pub const CONFIG_DATA: usize = 0xCFC; 7 | 8 | /// From http://wiki.osdev.org/PCI#Configuration_Space_Access_Mechanism_.231 9 | unsafe fn pci_read_u32(bus: u8, slot: u8, func: u8, offset: u8) -> u32 { 10 | assert!(offset % 4 == 0, "offset must be 4-byte aligned"); 11 | 12 | let address: u32 = (((bus as u32) << 16) 13 | | ((slot as u32) << 11) 14 | | ((func as u32) << 8) 15 | | (offset as u32) 16 | | (0x80000000u32)) as u32; 17 | 18 | /* write out the address */ 19 | asm!("out dx, eax", in("dx") CONFIG_ADDR, in("eax") address, options(nostack, nomem)); 20 | let inp: u32; 21 | asm!("in eax, dx", in("dx") CONFIG_DATA, out("eax") inp, options(nostack, nomem)); 22 | inp 23 | } 24 | 25 | unsafe fn pci_write_u32(bus: u8, slot: u8, func: u8, offset: u8, value: u32) { 26 | assert!(offset % 4 == 0, "offset must be 4-byte aligned"); 27 | 28 | let address: u32 = (((bus as u32) << 16) 29 | | ((slot as u32) << 11) 30 | | ((func as u32) << 8) 31 | | (offset as u32) 32 | | (0x80000000u32)) as u32; 33 | 34 | /* write out the address */ 35 | asm!("out dx, eax", in("dx") CONFIG_ADDR, in("eax") address, options(nostack, nomem)); 36 | asm!("out dx, eax", in("dx") CONFIG_DATA, in("eax") value, options(nostack, nomem)); 37 | } 38 | 39 | pub(crate) fn pci_read_device(loc: DeviceLocation, offset: u8) -> u32 { 40 | unsafe { pci_read_u32(loc.0, loc.1, loc.2, offset) } 41 | } 42 | 43 | pub(crate) fn pci_write_device(loc: DeviceLocation, offset: u8, value: u32) { 44 | unsafe { pci_write_u32(loc.0, loc.1, loc.2, offset, value) } 45 | } 46 | -------------------------------------------------------------------------------- /libs/elf2bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elf2bin" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | num-traits = "0.2" 9 | -------------------------------------------------------------------------------- /libs/elf2bin/README.md: -------------------------------------------------------------------------------- 1 | `elf2bin` 2 | ========= 3 | 4 | Converts ELF file to a flat binary, discarding all loading and section information. 5 | -------------------------------------------------------------------------------- /libs/elf2bin/rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | match_block_trailing_comma = true 3 | use_field_init_shorthand = true 4 | use_try_shorthand = true 5 | -------------------------------------------------------------------------------- /libs/libd7/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libd7" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [profile.dev] 9 | panic = "abort" 10 | 11 | [profile.release] 12 | panic = "abort" 13 | 14 | [dependencies] 15 | spin = "0.9" 16 | pinecone = "0.2" 17 | log = "0.4" 18 | 19 | 20 | [dependencies.auto_enums] 21 | version = "0.7" 22 | features = ["transpose_methods"] 23 | 24 | [dependencies.hashbrown] 25 | version = "0.11" 26 | features = ["nightly", "inline-more", "serde"] 27 | 28 | [dependencies.serde] 29 | version = "1.0" 30 | default-features = false 31 | features = ["alloc", "derive"] 32 | 33 | [dependencies.chrono] 34 | version = "0.4" 35 | default-features = false 36 | features = ["alloc", "serde"] 37 | 38 | [dependencies.lazy_static] 39 | version = "1.4" 40 | features = ["spin_no_std"] 41 | 42 | [dependencies.x86_64] 43 | git = "https://github.com/Dentosal/x86_64" 44 | features = ["use-serde"] 45 | 46 | [dependencies.d7abi] 47 | path = "../d7abi" 48 | 49 | [dependencies.d7keymap] 50 | path = "../d7keymap" 51 | 52 | [dependencies.d7net] 53 | path = "../d7net" 54 | -------------------------------------------------------------------------------- /libs/libd7/README.md: -------------------------------------------------------------------------------- 1 | LibD7 - Rust-y abstractions for D7 system calls 2 | =============================================== 3 | 4 | This library contains: 5 | * Type-safe wrappers for system calls 6 | * Ergonomic IPC wrappers for common services 7 | * Panic implementation 8 | * Allocator implmentation 9 | * `_start` that fraps the `main` function 10 | * Useful stdlib macros like `println!` 11 | -------------------------------------------------------------------------------- /libs/libd7/src/env.rs: -------------------------------------------------------------------------------- 1 | //! Command line arguments 2 | 3 | use core::{slice, str}; 4 | 5 | use crate::d7abi::PROCESS_STACK_END; 6 | 7 | pub struct Args { 8 | index: usize, 9 | ptr: *const u8, 10 | } 11 | impl Iterator for Args { 12 | type Item = &'static str; 13 | 14 | fn next(&mut self) -> Option { 15 | if self.index >= argc() { 16 | return None; 17 | } 18 | 19 | let result; 20 | 21 | unsafe { 22 | let arg_len = arg_len(self.index); 23 | result = str::from_utf8(slice::from_raw_parts(self.ptr, arg_len as usize)).unwrap(); 24 | self.ptr = self.ptr.add(arg_len); 25 | } 26 | 27 | self.index += 1; 28 | Some(result) 29 | } 30 | } 31 | 32 | fn argc() -> usize { 33 | unsafe { 34 | let cursor = PROCESS_STACK_END.as_ptr::().sub(8); 35 | let argc: u64 = *(cursor as *const u64); 36 | argc as usize 37 | } 38 | } 39 | 40 | /// # Safety: index must be in bounds 41 | unsafe fn arg_len(index: usize) -> usize { 42 | let cursor = PROCESS_STACK_END.as_ptr::().sub(16); 43 | let len_per_arg_start = cursor as *const u64; 44 | let arg_len: u64 = *len_per_arg_start.sub(index); 45 | arg_len as usize 46 | } 47 | 48 | pub fn args() -> Args { 49 | unsafe { 50 | let cursor = PROCESS_STACK_END.as_ptr::().sub(8); 51 | let argc: u64 = *(cursor as *const u64); 52 | let argc = argc as usize; 53 | 54 | let len_per_arg_start = cursor as *const u64; 55 | let len_per_arg = |i| -> usize { 56 | let arg_len: u64 = *len_per_arg_start.sub(i + 1); 57 | arg_len as usize 58 | }; 59 | 60 | Args { 61 | index: 0, 62 | ptr: cursor 63 | .sub(argc * 8) 64 | .sub((0..argc).map(|i| len_per_arg(i)).sum()), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /libs/libd7/src/ipc/mod.rs: -------------------------------------------------------------------------------- 1 | pub use d7abi::ipc::*; 2 | 3 | mod select; 4 | mod send; 5 | mod server; 6 | mod subscription; 7 | 8 | pub use self::send::*; 9 | pub use self::server::*; 10 | pub use self::subscription::*; 11 | 12 | /// Any items that contain internal subscriptions must implement this, 13 | /// so that select! can use them 14 | pub trait InternalSubscription { 15 | fn sub_id(&self) -> SubscriptionId; 16 | } 17 | 18 | impl InternalSubscription for SubscriptionId { 19 | fn sub_id(&self) -> SubscriptionId { 20 | *self 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libs/libd7/src/ipc/send.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use crate::syscall::{self, SyscallResult}; 4 | 5 | /// Send an unreliable (fire-and-forget) message to a topic 6 | pub fn publish(topic: &str, message: &T) -> SyscallResult<()> { 7 | let data = pinecone::to_vec(message).unwrap(); 8 | syscall::ipc_publish(topic, &data) 9 | } 10 | 11 | /// Send a reliable message to a topic, and wait until receiver acknowledges it 12 | pub fn deliver(topic: &str, message: &T) -> SyscallResult<()> { 13 | let data = pinecone::to_vec(message).unwrap(); 14 | syscall::ipc_deliver(topic, &data) 15 | } 16 | 17 | /// Send a reliable message to a topic, but don't require acknowledgement 18 | pub fn deliver_reply(topic: &str, message: &T) -> SyscallResult<()> { 19 | let data = pinecone::to_vec(message).unwrap(); 20 | syscall::ipc_deliver_reply(topic, &data) 21 | } 22 | -------------------------------------------------------------------------------- /libs/libd7/src/net/tcp/socket_ipc_protocol.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::net::{NetworkError, SocketId}; 5 | use d7net::{tcp, SocketAddr}; 6 | 7 | #[derive(Debug, Serialize, Deserialize)] 8 | pub struct Bind(pub SocketAddr); 9 | 10 | #[derive(Debug, Serialize, Deserialize)] 11 | #[must_use] 12 | pub enum BindError { 13 | /// Caller-requested address already in use 14 | AlreadyInUse, 15 | /// Caller-requested address was not acceptable, 16 | /// e.g. binding to non-available IP 17 | NotAcceptable, 18 | /// Caller requested automatically assigned port, 19 | /// but no ports are available for that 20 | NoPortsAvailable, 21 | /// Caller cannot bind to this address 22 | PermissionDenied, 23 | } 24 | 25 | #[derive(Debug, Clone, Serialize, Deserialize)] 26 | pub enum Request { 27 | GetState, 28 | GetOption(OptionKey), 29 | SetOption(Option), 30 | Connect { 31 | to: SocketAddr, 32 | }, 33 | Listen { 34 | backlog: usize, 35 | }, 36 | Accept, 37 | Shutdown, 38 | Close, 39 | Abort, 40 | Recv(usize), 41 | Send(Vec), 42 | /// A special request used to indicate that this socket is 43 | /// no longer used, sent by the Drop impl. Must be replied 44 | /// with a success reply. 45 | Remove, 46 | } 47 | 48 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 49 | pub enum Reply { 50 | State(tcp::state::ConnectionState), 51 | Option(Option), 52 | Recv(Vec), 53 | Accept { addr: SocketAddr, id: SocketId }, 54 | NoData, 55 | } 56 | 57 | impl From<()> for Reply { 58 | fn from(_: ()) -> Self { 59 | Self::NoData 60 | } 61 | } 62 | 63 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 64 | pub enum Error { 65 | Network(NetworkError), 66 | Tcp(tcp::state::Error), 67 | } 68 | impl From for Error { 69 | fn from(error: tcp::state::Error) -> Self { 70 | Self::Tcp(error) 71 | } 72 | } 73 | impl From for Error { 74 | fn from(error: NetworkError) -> Self { 75 | Self::Network(error) 76 | } 77 | } 78 | 79 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 80 | pub enum OptionKey { 81 | NagleDelay, 82 | } 83 | 84 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 85 | pub enum Option { 86 | NagleDelay(core::time::Duration), 87 | } 88 | -------------------------------------------------------------------------------- /libs/libd7/src/net/udp.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use super::SocketAddr; 4 | use crate::ipc; 5 | 6 | pub struct UdpSocket { 7 | recv: ipc::UnreliableSubscription<>, 8 | send_topic: String, 9 | } 10 | impl UdpSocket { 11 | pub fn bind(addr: SocketAddr) -> SyscallResult { 12 | let socket = ipc::request("netd/newsocket/udp"); 13 | } 14 | } -------------------------------------------------------------------------------- /libs/libd7/src/process.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | pub use d7abi::process::{ProcessId, ProcessResult}; 3 | 4 | use crate::ipc; 5 | use crate::syscall::{self, SyscallResult}; 6 | 7 | /// A safe wrapper for a process 8 | #[derive(Debug, PartialEq, Eq, Hash)] 9 | pub struct Process { 10 | pid: ProcessId, 11 | } 12 | impl Process { 13 | pub fn spawn(path: &str, args: &[&str]) -> SyscallResult { 14 | let image: Vec = ipc::request("initrd/read", path)?; 15 | let pid = syscall::exec(&image, args)?; 16 | Ok(Process { pid }) 17 | } 18 | 19 | pub fn pid(&self) -> ProcessId { 20 | self.pid 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libs/libd7/src/random.rs: -------------------------------------------------------------------------------- 1 | use crate::syscall::random; 2 | 3 | /// Read cryptocraphically secure randomness from the kernel entropy pool 4 | pub fn crypto(buffer: &mut [u8]) { 5 | for c in buffer.chunks_mut(8) { 6 | let b = random(0).to_le_bytes(); 7 | c.copy_from_slice(&b[..c.len()]); 8 | } 9 | } 10 | 11 | /// Read cryptocraphically secure randomness from the kernel entropy pool 12 | pub fn crypto_arr() -> [u8; LEN] { 13 | let mut arr = [0u8; LEN]; 14 | crypto(&mut arr); 15 | arr 16 | } 17 | 18 | /// Get a random number quickly (not cryptographically secure) 19 | pub fn fast(buffer: &mut [u8]) { 20 | crypto(buffer); // TODO: speedup with userspace RDRAND and/or PRNG 21 | } 22 | 23 | /// Get a random number quickly (not cryptographically secure) 24 | pub fn fast_arr() -> [u8; LEN] { 25 | let mut arr = [0u8; LEN]; 26 | fast(&mut arr); 27 | arr 28 | } 29 | -------------------------------------------------------------------------------- /libs/libd7/src/service.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for interacting with serviced 2 | 3 | use alloc::borrow::ToOwned; 4 | use hashbrown::HashSet; 5 | 6 | use crate::ipc::protocol::service::{Registration, ServiceName}; 7 | 8 | pub fn register(name: &str, oneshot: bool) { 9 | crate::ipc::deliver("serviced/register", &Registration { 10 | name: ServiceName(name.to_owned()), 11 | oneshot, 12 | }) 13 | .unwrap(); 14 | } 15 | 16 | pub fn wait_for_one(name: &str) { 17 | let mut hs = HashSet::new(); 18 | hs.insert(ServiceName(name.to_owned())); 19 | crate::ipc::deliver("serviced/waitfor/any", &hs).unwrap(); 20 | } 21 | 22 | pub fn wait_for_any(names: T) 23 | where 24 | T: IntoIterator, 25 | T::Item: AsRef, 26 | { 27 | let mut hs = HashSet::new(); 28 | for name in names.into_iter() { 29 | hs.insert(ServiceName(name.as_ref().to_owned())); 30 | } 31 | crate::ipc::deliver("serviced/waitfor/any", &hs).unwrap(); 32 | } 33 | 34 | pub fn wait_for_all(names: T) 35 | where 36 | T: IntoIterator, 37 | T::Item: AsRef, 38 | { 39 | let mut hs = HashSet::new(); 40 | for name in names.into_iter() { 41 | hs.insert(ServiceName(name.as_ref().to_owned())); 42 | } 43 | crate::ipc::deliver("serviced/waitfor/all", &hs).unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /libs/qemu_driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "qemu_driver" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | tokio = {git = "https://github.com/tokio-rs/tokio", rev = "3ecaa6d91cef271b4c079a2e28bc3270280bcee6", features = ["process"]} 9 | -------------------------------------------------------------------------------- /modules/daemon_console/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_daemon_console" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | volatile = "0.2.6" 19 | unicode-segmentation = "1.6.0" 20 | 21 | [dependencies.hashbrown] 22 | version = "0.11" 23 | features = ["nightly", "inline-more", "serde"] 24 | 25 | [dependencies.unicode-normalization] 26 | git = "https://github.com/unicode-rs/unicode-normalization" 27 | default-features = false 28 | 29 | [dependencies.serde] 30 | version = "1.0" 31 | default-features = false 32 | features = ["alloc", "derive"] 33 | 34 | [dependencies.serde_json] 35 | version = "1.0" 36 | default-features = false 37 | features = ["alloc"] 38 | 39 | [dependencies.libd7] 40 | version = "*" 41 | path = "../../libs/libd7" 42 | 43 | [dependencies.d7keymap] 44 | version = "*" 45 | path = "../../libs/d7keymap" 46 | 47 | -------------------------------------------------------------------------------- /modules/daemon_console/src/keyboard.rs: -------------------------------------------------------------------------------- 1 | use alloc::borrow::ToOwned; 2 | use alloc::string::String; 3 | use alloc::vec::Vec; 4 | use hashbrown::HashSet; 5 | 6 | use d7keymap::{KeyAction, KeyCodes, KeyMap, KeySymbol}; 7 | use libd7::ipc::{self, protocol::keyboard::KeyboardEvent}; 8 | 9 | pub struct Keyboard { 10 | keycodes: KeyCodes, 11 | keymap: KeyMap, 12 | pub pressed_modifiers: HashSet, 13 | } 14 | impl Keyboard { 15 | pub fn new() -> Self { 16 | let keycodes_json: Vec = 17 | ipc::request("initrd/read", "keycodes.json".to_owned()).unwrap(); 18 | let keymap_json: Vec = ipc::request("initrd/read", "keymap.json".to_owned()).unwrap(); 19 | 20 | Self { 21 | keycodes: serde_json::from_slice(&keycodes_json).unwrap(), 22 | keymap: serde_json::from_slice(&keymap_json).unwrap(), 23 | pressed_modifiers: HashSet::new(), 24 | } 25 | } 26 | 27 | pub fn process_event(&mut self, event: KeyboardEvent) -> EventAction { 28 | if let Some(keysym) = self.keycodes.clone().get(&event.keycode) { 29 | if self.keymap.modifiers.contains(&keysym) { 30 | if event.release { 31 | self.pressed_modifiers.remove(keysym); 32 | } else { 33 | self.pressed_modifiers.insert(keysym.clone()); 34 | } 35 | EventAction::Ignore 36 | } else if !event.release { 37 | let result = self.process_keysym_press(&keysym); 38 | if let Some(action) = result { 39 | EventAction::KeyAction(action) 40 | } else { 41 | EventAction::Unmatched(keysym.clone(), self.pressed_modifiers.clone()) 42 | } 43 | } else { 44 | EventAction::Ignore 45 | } 46 | } else { 47 | EventAction::NoSuchSymbol 48 | } 49 | } 50 | 51 | pub fn process_keysym_press(&mut self, keysym: &KeySymbol) -> Option { 52 | for (k, v) in &self.keymap.mapping { 53 | if k.matches(keysym, &self.pressed_modifiers) { 54 | return Some(if let KeyAction::Remap(to) = v.clone() { 55 | self.process_keysym_press(&to)? 56 | } else { 57 | v.clone() 58 | }); 59 | } 60 | } 61 | 62 | None 63 | } 64 | } 65 | 66 | #[derive(Debug)] 67 | #[must_use] 68 | pub enum EventAction { 69 | KeyAction(KeyAction), 70 | Unmatched(KeySymbol, HashSet), 71 | Ignore, 72 | NoSuchSymbol, 73 | } 74 | -------------------------------------------------------------------------------- /modules/daemon_console/src/vga.rs: -------------------------------------------------------------------------------- 1 | use core::mem; 2 | use core::ptr::Unique; 3 | use volatile::Volatile; 4 | 5 | use libd7::{syscall, PhysAddr, VirtAddr}; 6 | 7 | const SCREEN_HEIGHT: usize = 25; 8 | const SCREEN_WIDTH: usize = 80; 9 | const HARDWARE_BUFFER_ADDR: u64 = 0xb8000; 10 | const HARDWARE_BUFFER_SIZE: u64 = mem::size_of::() as u64; 11 | 12 | /// Should be free to use. Check plan.md 13 | const VIRTUAL_ADDR: VirtAddr = unsafe { VirtAddr::new_unsafe(0x10_0000_0000) }; 14 | 15 | /// A VGA color 16 | #[allow(dead_code)] 17 | #[repr(u8)] 18 | pub enum Color { 19 | Black = 0, 20 | Blue = 1, 21 | Green = 2, 22 | Cyan = 3, 23 | Red = 4, 24 | Magenta = 5, 25 | Brown = 6, 26 | LightGray = 7, 27 | DarkGray = 8, 28 | LightBlue = 9, 29 | LightGreen = 10, 30 | LightCyan = 11, 31 | LightRed = 12, 32 | Pink = 13, 33 | Yellow = 14, 34 | White = 15, 35 | } 36 | 37 | /// Color of single cell, back- and foreground 38 | #[derive(Clone, Copy)] 39 | pub struct CellColor(u8); 40 | 41 | impl CellColor { 42 | pub const fn new(foreground: Color, background: Color) -> CellColor { 43 | CellColor((background as u8) << 4 | (foreground as u8)) 44 | } 45 | 46 | pub fn foreground(self) -> Color { 47 | unsafe { mem::transmute::(self.0 & 0xf) } 48 | } 49 | 50 | pub fn background(self) -> Color { 51 | unsafe { mem::transmute::((self.0 & 0xf0) >> 4) } 52 | } 53 | 54 | pub fn invert(self) -> CellColor { 55 | CellColor::new(self.background(), self.foreground()) 56 | } 57 | } 58 | 59 | /// Character cell: one character and color in screen 60 | #[derive(Clone, Copy)] 61 | #[repr(C, packed)] 62 | pub struct CharCell { 63 | pub character: u8, 64 | pub color: CellColor, 65 | } 66 | 67 | #[repr(C, packed)] 68 | pub struct Buffer { 69 | pub chars: [[Volatile; SCREEN_WIDTH]; SCREEN_HEIGHT], 70 | } 71 | impl Buffer { 72 | /// Clear screen 73 | pub fn clear(&mut self) { 74 | let color = CellColor::new(Color::White, Color::Black); 75 | for col in 0..SCREEN_WIDTH { 76 | for row in 0..SCREEN_HEIGHT { 77 | self.chars[row][col].write(CharCell { 78 | character: b' ', 79 | color, 80 | }); 81 | } 82 | } 83 | } 84 | } 85 | 86 | /// # Safety 87 | /// Must be only called once. Modifies kernel page tables. 88 | pub unsafe fn get_hardware_buffer() -> Unique { 89 | syscall::mmap_physical( 90 | // Assumes 2MiB pages, so that 0xb8000 falls on the first page 91 | PhysAddr::new(0), 92 | VIRTUAL_ADDR, 93 | HARDWARE_BUFFER_SIZE, 94 | syscall::MemoryProtectionFlags::READ | syscall::MemoryProtectionFlags::WRITE, 95 | ) 96 | .unwrap(); 97 | Unique::new_unchecked((VIRTUAL_ADDR + HARDWARE_BUFFER_ADDR).as_mut_ptr()) 98 | } 99 | -------------------------------------------------------------------------------- /modules/daemon_fatfs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_daemon_fatfs" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | bitflags = "1.3" 21 | lru = "0.7.2" 22 | 23 | [dependencies.fatfs] 24 | git = "https://github.com/rafalh/rust-fatfs" 25 | rev = "87fc1ed5074a32b4e0344fcdde77359ef9e75432" 26 | default-features = false 27 | features = ["alloc", "lfn", "unicode", "log_level_trace"] 28 | 29 | [dependencies.serde] 30 | version = "1.0" 31 | default-features = false 32 | features = ["alloc", "derive"] 33 | 34 | [dependencies.libd7] 35 | version = "*" 36 | path = "../../libs/libd7" 37 | -------------------------------------------------------------------------------- /modules/daemon_fatfs/src/cache.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use lru::LruCache; 3 | 4 | use crate::disk::Disk; 5 | 6 | pub struct DiskAccess { 7 | cache: Option>>, 8 | disk: Disk, 9 | } 10 | 11 | impl DiskAccess { 12 | pub fn new(disk: Disk, cache_size: usize) -> Self { 13 | Self { 14 | disk, 15 | cache: if cache_size != 0 { 16 | Some(LruCache::new(cache_size)) 17 | } else { 18 | None 19 | }, 20 | } 21 | } 22 | 23 | pub fn sector_size(&self) -> usize { 24 | self.disk.sector_size 25 | } 26 | 27 | pub fn read(&mut self, sector: u64) -> Vec { 28 | if let Some(cache) = &mut self.cache { 29 | if let Some(data) = cache.get(§or) { 30 | log::trace!("Cache hit"); 31 | return data.clone(); 32 | } else { 33 | log::trace!("Cache miss"); 34 | } 35 | } 36 | 37 | let data = (self.disk.read)(sector); 38 | assert_eq!(data.len(), self.sector_size()); 39 | if let Some(cache) = &mut self.cache { 40 | cache.put(sector, data.clone()); 41 | } 42 | data 43 | } 44 | 45 | pub fn write(&mut self, sector: u64, data: Vec) { 46 | assert_eq!(data.len(), self.sector_size()); 47 | if let Some(cache) = &mut self.cache { 48 | let _ = cache.put(sector, data.clone()); 49 | } 50 | let data = (self.disk.write)(sector, data); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /modules/daemon_fatfs/src/disk.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | pub struct Disk { 4 | pub sector_size: usize, 5 | pub read: fn(u64) -> Vec, 6 | pub write: fn(u64, Vec), 7 | } 8 | -------------------------------------------------------------------------------- /modules/daemon_fatfs/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![feature(no_more_cas)] 4 | #![deny(unused_must_use)] 5 | 6 | extern crate alloc; 7 | extern crate libd7; 8 | 9 | use alloc::vec::Vec; 10 | 11 | use libd7::{ipc, select, syscall}; 12 | 13 | use fatfs::{Read, Write}; 14 | 15 | mod cache; 16 | mod cursor; 17 | mod disk; 18 | 19 | use crate::cache::DiskAccess; 20 | use crate::cursor::DiskCursor; 21 | use crate::disk::Disk; 22 | 23 | #[no_mangle] 24 | fn main() -> ! { 25 | log::info!("daemon starting"); 26 | 27 | // Storage backends 28 | // TODO: backend registers to us, instead of active waiting 29 | libd7::service::wait_for_one("driver_ata_pio"); 30 | 31 | // Subscribe to client requests 32 | let server: ipc::Server<(), ()> = ipc::Server::exact("fatfs").unwrap(); 33 | 34 | // Inform serviced that we are running. 35 | libd7::service::register("daemon_fatfs", false); 36 | 37 | log::info!("daemon running"); 38 | 39 | let access = DiskAccess::new( 40 | Disk { 41 | sector_size: 0x200, 42 | read: |sector: u64| -> Vec { 43 | ipc::request("ata_pio/drive/1/read", (sector, 1u8)).expect("ata read") 44 | }, 45 | write: |sector: u64, data: Vec| { 46 | ipc::deliver("ata_pio/drive/1/write", &(sector, data)).expect("ata write") 47 | }, 48 | }, 49 | 2, 50 | ); 51 | let c = DiskCursor::new(access); 52 | let fs = fatfs::FileSystem::new(c, fatfs::FsOptions::new()).expect("open fs"); 53 | let mut cursor = fs.root_dir(); 54 | 55 | let mut result = Vec::new(); 56 | for entry in cursor.iter() { 57 | let entry = entry.expect("Entry"); 58 | result.push(entry.file_name()); 59 | } 60 | 61 | let mut cursor = fs.root_dir().open_dir("test/").expect("test/"); 62 | 63 | let mut result = Vec::new(); 64 | for entry in cursor.iter() { 65 | let entry = entry.expect("Entry"); 66 | result.push(entry.file_name()); 67 | } 68 | 69 | log::info!("{:?}", result); 70 | 71 | let mut file = fs.root_dir().create_file("example.txt").expect("open file"); 72 | file.write(b"Example text\n").expect("Write failed"); 73 | file.flush().expect("Close"); 74 | 75 | todo!(); 76 | // loop { 77 | // select! { 78 | // one(get_mac) => get_mac.handle(|()| Ok(device.mac_addr())).unwrap(), 79 | // } 80 | // } 81 | } 82 | -------------------------------------------------------------------------------- /modules/daemon_net/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_daemon_net" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | 20 | [dependencies.hashbrown] 21 | version = "0.11" 22 | features = ["nightly", "inline-more", "serde"] 23 | 24 | [dependencies.spin] 25 | version = "0.9" 26 | features = ["rwlock"] 27 | 28 | [dependencies.lazy_static] 29 | version = "1.4" 30 | features = ["spin_no_std"] 31 | 32 | [dependencies.libd7] 33 | version = "*" 34 | path = "../../libs/libd7" 35 | 36 | [dependencies.serde] 37 | version = "1.0" 38 | default-features = false 39 | features = ["alloc", "derive"] 40 | -------------------------------------------------------------------------------- /modules/daemon_net/README.md: -------------------------------------------------------------------------------- 1 | # `netd` - Networking daemon 2 | -------------------------------------------------------------------------------- /modules/daemon_net/src/arp_handler.rs: -------------------------------------------------------------------------------- 1 | use libd7::{ipc, net::d7net::*}; 2 | 3 | use crate::NET_STATE; 4 | 5 | pub fn handle_arp_packet(frame: ðernet::Frame, arp_packet: &arp::Packet) { 6 | // Update arp table 7 | if arp_packet.src_ip != Ipv4Addr::ZERO { 8 | println!( 9 | "ARP: Mark owner {:?} {:?}", 10 | arp_packet.src_ip, arp_packet.src_hw 11 | ); 12 | { 13 | let mut net_state = NET_STATE.write(); 14 | net_state 15 | .arp_table 16 | .insert(arp_packet.src_ip, arp_packet.src_hw); 17 | } 18 | 19 | if arp_packet.is_request() { 20 | // Reply to mac-targeted ARP packets if the corresponding interface has an ip 21 | if arp_packet.dst_hw != MacAddr::ZERO { 22 | let net_state = NET_STATE.read(); 23 | 24 | if let Some(intf) = net_state.interface(arp_packet.dst_hw) { 25 | if !intf.arp_probe_ok { 26 | return; 27 | } 28 | if let Some(ip) = intf.settings.ipv4 { 29 | if arp_packet.dst_ip == ip { 30 | println!("ARP: Replying"); 31 | 32 | let reply = (ethernet::Frame { 33 | header: ethernet::FrameHeader { 34 | dst_mac: frame.header.src_mac, 35 | src_mac: intf.mac_addr, 36 | ethertype: EtherType::ARP, 37 | }, 38 | payload: arp_packet.to_reply(intf.mac_addr, ip).to_bytes(), 39 | }) 40 | .to_bytes(); 41 | 42 | ipc::publish("nic/send", &reply).unwrap(); 43 | } 44 | } 45 | } 46 | } else if arp_packet.dst_ip != Ipv4Addr::ZERO { 47 | // Reply to ip-targeted ARP packets if the corresponding interface exists 48 | let net_state = NET_STATE.read(); 49 | for intf in &net_state.interfaces { 50 | if !intf.arp_probe_ok { 51 | continue; 52 | } 53 | if let Some(ip) = intf.settings.ipv4 { 54 | if arp_packet.dst_ip == ip { 55 | println!("ARP: Replying"); 56 | 57 | let reply = (ethernet::Frame { 58 | header: ethernet::FrameHeader { 59 | dst_mac: frame.header.src_mac, 60 | src_mac: intf.mac_addr, 61 | ethertype: EtherType::ARP, 62 | }, 63 | payload: arp_packet.to_reply(intf.mac_addr, ip).to_bytes(), 64 | }) 65 | .to_bytes(); 66 | 67 | ipc::publish("nic/send", &reply).unwrap(); 68 | break; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /modules/daemon_net/src/ports.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | // https://datatracker.ietf.org/doc/html/rfc6335#page-11 4 | 5 | use core::ops::RangeInclusive; 6 | 7 | use libd7::random; 8 | 9 | pub const RANGE_SYSTEM: RangeInclusive = 0..=1023; 10 | pub const RANGE_USER: RangeInclusive = 1024..=49151; 11 | pub const RANGE_DYNAMIC: RangeInclusive = 49152..=65535; 12 | 13 | pub fn random_dynamic_port() -> u16 { 14 | let a: [u8; 2] = random::fast_arr(); 15 | let v = u16::from_le_bytes(a); 16 | let i = v % (RANGE_DYNAMIC.len() as u16); 17 | RANGE_DYNAMIC.start() + i 18 | } 19 | 20 | // Some fixed ports that are used by builtin clients 21 | pub const FIXED_DNS_CLIENT: u16 = 54; 22 | -------------------------------------------------------------------------------- /modules/daemon_service/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_daemon_service" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | 20 | [dependencies.hashbrown] 21 | version = "0.11" 22 | features = ["nightly", "inline-more", "serde"] 23 | 24 | [dependencies.libd7] 25 | version = "*" 26 | path = "../../libs/libd7" 27 | 28 | [dependencies.serde] 29 | version = "1.0" 30 | default-features = false 31 | features = ["alloc", "derive"] 32 | 33 | [dependencies.serde_json] 34 | version = "1.0" 35 | default-features = false 36 | features = ["alloc"] 37 | -------------------------------------------------------------------------------- /modules/daemon_service/README.md: -------------------------------------------------------------------------------- 1 | # `serviced` 2 | 3 | Service manager -------------------------------------------------------------------------------- /modules/daemon_syslog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_daemon_syslog" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | 20 | [dependencies.serde] 21 | version = "1.0" 22 | default-features = false 23 | features = ["alloc", "derive"] 24 | 25 | [dependencies.libd7] 26 | version = "*" 27 | path = "../../libs/libd7" 28 | -------------------------------------------------------------------------------- /modules/daemon_syslog/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Syslog daemon. 2 | //! Combines kernel and service logs, writes to disk and console. 3 | //! 4 | //! TODO: more find-grained system calls, to only remove the data when it 5 | //! has been written on the disk. 6 | 7 | #![no_std] 8 | #![deny(unused_must_use)] 9 | 10 | #[macro_use] 11 | extern crate alloc; 12 | 13 | #[macro_use] 14 | extern crate libd7; 15 | 16 | use alloc::borrow::ToOwned; 17 | use alloc::collections::VecDeque; 18 | use alloc::string::String; 19 | use alloc::vec::Vec; 20 | 21 | use libd7::{ipc, process::ProcessId, select, syscall}; 22 | 23 | #[no_mangle] 24 | fn main() -> ! { 25 | println!("Syslog daemon starting"); 26 | 27 | let mut read_buffer = [0u8; 0x1_0000]; 28 | let mut send_buffer: String = String::new(); 29 | let mut line_buffer: Vec = Vec::new(); 30 | 31 | // Inform the serviced that we are up 32 | libd7::service::register("syslogd", false); 33 | 34 | loop { 35 | let count = syscall::kernel_log_read(&mut read_buffer).unwrap(); 36 | if count != 0 { 37 | for &byte in &read_buffer[..count] { 38 | if byte == b'\n' { 39 | send_buffer.push_str(core::str::from_utf8(&line_buffer).unwrap()); 40 | send_buffer.push('\n'); 41 | line_buffer.clear(); 42 | } else { 43 | line_buffer.push(byte); 44 | } 45 | } 46 | 47 | if send_buffer.len() > 0 { 48 | ipc::deliver("console/kernel_log", &send_buffer).unwrap(); 49 | send_buffer.clear(); 50 | } 51 | 52 | assert!(line_buffer.len() < 1000, "Line buffer overflow"); 53 | } 54 | 55 | // Sleep if there is no buffer left 56 | if count < read_buffer.len() { 57 | // TODO: increase poll frequency 58 | syscall::sched_sleep_ns(1_000_000_000).unwrap(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /modules/driver_ata_pio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_ata_pio" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | 20 | [dependencies.cpuio] 21 | git = "https://github.com/Dentosal/cpuio-rs" 22 | 23 | [dependencies.hashbrown] 24 | version = "0.7" 25 | features = ["nightly", "inline-more", "serde"] 26 | 27 | [dependencies.libd7] 28 | version = "*" 29 | path = "../../libs/libd7" -------------------------------------------------------------------------------- /modules/driver_ata_pio/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![deny(unused_must_use)] 4 | 5 | #[macro_use] 6 | extern crate alloc; 7 | 8 | extern crate libd7; 9 | 10 | use alloc::vec::Vec; 11 | use libd7::ipc::InternalSubscription; 12 | use libd7::{ipc, select}; 13 | 14 | mod ata_pio; 15 | 16 | #[no_mangle] 17 | fn main() -> ! { 18 | log::info!("driver starting"); 19 | 20 | let controller = ata_pio::AtaPio::new(); 21 | 22 | let drive_count = controller.drive_count(); 23 | assert!(drive_count > 0, "No drives found"); 24 | 25 | let drive_info: Vec = (0..drive_count) 26 | .map(|i| controller.capacity_sectors(i)) 27 | .collect(); 28 | 29 | log::info!("drives found {:?}", drive_info); 30 | 31 | let info: ipc::Server<(), Vec> = ipc::Server::exact("ata_pio/drives").unwrap(); 32 | let drive_read: Vec>> = (0..drive_count) 33 | .map(|i| ipc::Server::exact(&format!("ata_pio/drive/{}/read", i)).unwrap()) 34 | .collect(); 35 | let drive_write: Vec)>> = (0..drive_count) 36 | .map(|i| ipc::ReliableSubscription::exact(&format!("ata_pio/drive/{}/write", i)).unwrap()) 37 | .collect(); 38 | 39 | let read_sub_ids: Vec<_> = drive_read.iter().map(|s| s.sub_id()).collect(); 40 | let write_sub_ids: Vec<_> = drive_write.iter().map(|s| s.sub_id()).collect(); 41 | 42 | // Inform serviced that we are running. 43 | libd7::service::register("driver_ata_pio", false); 44 | 45 | loop { 46 | select! { 47 | any(read_sub_ids) -> i => { 48 | drive_read[i].handle(|(sector, count)| { 49 | // TODO: check that the sector index is valid 50 | Ok(unsafe {controller.read_lba(i, sector, count)}) 51 | }).unwrap(); 52 | }, 53 | any(write_sub_ids) -> i => { 54 | let (ack_ctx, (sector, data)) = drive_write[i].receive().unwrap(); 55 | // TODO: check that the sector index is valid 56 | unsafe {controller.write_lba(i, sector, &data)}; 57 | ack_ctx.ack().unwrap(); 58 | }, 59 | one(info) => { 60 | info.handle(|()| Ok(drive_info.clone())).unwrap(); 61 | } 62 | }; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /modules/driver_ne2k/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_ne2k" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | bitflags = "1.3" 21 | 22 | [dependencies.cpuio] 23 | git = "https://github.com/Dentosal/cpuio-rs" 24 | 25 | [dependencies.hashbrown] 26 | version = "0.11" 27 | features = ["nightly", "inline-more", "serde"] 28 | 29 | [dependencies.serde] 30 | version = "1.0" 31 | default-features = false 32 | features = ["alloc", "derive"] 33 | 34 | [dependencies.libd7] 35 | version = "*" 36 | path = "../../libs/libd7" 37 | 38 | [dependencies.d7pci] 39 | version = "*" 40 | path = "../../libs/d7pci" 41 | -------------------------------------------------------------------------------- /modules/driver_ne2k/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![allow(dead_code)] 4 | #![deny(unused_must_use)] 5 | 6 | #[macro_use] 7 | extern crate alloc; 8 | 9 | #[macro_use] 10 | extern crate libd7; 11 | 12 | use alloc::vec::Vec; 13 | 14 | use libd7::net::d7net::MacAddr; 15 | use libd7::{ipc, select, syscall}; 16 | 17 | mod ne2k; 18 | 19 | #[no_mangle] 20 | fn main() -> ! { 21 | syscall::debug_print("Ne2k driver starting"); 22 | 23 | // Make sure this is the only NIC driver running 24 | libd7::service::register("exclude/nic", false); 25 | 26 | // Get device info 27 | let pci_device: Option = ipc::request("pci/device", &"ne2k").unwrap(); 28 | 29 | // XXX: bochs ne2k workaround 30 | let pci_device = if let Some(d) = pci_device { 31 | Some(d) 32 | } else { 33 | ipc::request("pci/device", &"rtl8029").unwrap() 34 | }; 35 | 36 | let pci_device = pci_device.expect("PCI device resolution failed unexpectedly"); 37 | 38 | // Initialize the driver 39 | let mut device = unsafe { ne2k::Ne2k::new(pci_device) }; 40 | 41 | // Subscribe to hardware events 42 | // TODO: dynamic IRQ detection (in kernel?) 43 | let irq1 = ipc::UnreliableSubscription::<()>::exact(&"irq/17").unwrap(); 44 | let irq2 = ipc::UnreliableSubscription::<()>::exact(&format!("irq/{}", device.irq)).unwrap(); 45 | 46 | // Subscribe to client requests 47 | let get_mac: ipc::Server<(), MacAddr> = ipc::Server::exact("nic/ne2k/mac").unwrap(); 48 | let send = ipc::UnreliableSubscription::>::exact("nic/send").unwrap(); 49 | 50 | // Inform serviced that we are running. 51 | libd7::service::register("driver_ne2k", false); 52 | 53 | println!("ne2k Ready"); 54 | 55 | loop { 56 | select! { 57 | one(irq1) => { 58 | let _: () = irq1.receive().unwrap(); 59 | println!("ne2k: IRQ NOTIFY 1"); 60 | let received_packets = device.notify_irq(); 61 | for packet in received_packets { 62 | ipc::deliver("netd/received", &packet).unwrap(); 63 | } 64 | }, 65 | one(irq2) => { 66 | let _: () = irq2.receive().unwrap(); 67 | println!("ne2k: IRQ NOTIFY 2"); 68 | let received_packets = device.notify_irq(); 69 | for packet in received_packets { 70 | ipc::deliver("netd/received", &packet).unwrap(); 71 | } 72 | }, 73 | one(get_mac) => get_mac.handle(|()| Ok(device.mac_addr())).unwrap(), 74 | one(send) => { 75 | println!("ne2k: SEND PKT"); 76 | let packet: Vec = send.receive().unwrap(); 77 | device.send(&packet); 78 | println!("ne2k: PKT SENT"); 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /modules/driver_pci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_pci" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | 21 | [dependencies.cpuio] 22 | git = "https://github.com/Dentosal/cpuio-rs" 23 | 24 | [dependencies.hashbrown] 25 | version = "0.11" 26 | features = ["nightly", "inline-more", "serde"] 27 | 28 | [dependencies.serde] 29 | version = "1.0" 30 | default-features = false 31 | features = ["alloc", "derive"] 32 | 33 | [dependencies.serde_json] 34 | version = "1.0" 35 | default-features = false 36 | features = ["alloc"] 37 | 38 | [dependencies.libd7] 39 | version = "*" 40 | path = "../../libs/libd7" 41 | 42 | [dependencies.d7pci] 43 | version = "*" 44 | path = "../../libs/d7pci" 45 | -------------------------------------------------------------------------------- /modules/driver_pci/README.md: -------------------------------------------------------------------------------- 1 | # PCI driver 2 | 3 | Lists devices on startup, and starts appropriate drivers. -------------------------------------------------------------------------------- /modules/driver_ps2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_ps2" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | 21 | [dependencies.cpuio] 22 | git = "https://github.com/Dentosal/cpuio-rs" 23 | 24 | [dependencies.hashbrown] 25 | version = "0.11" 26 | features = ["nightly", "inline-more", "serde"] 27 | 28 | [dependencies.libd7] 29 | version = "*" 30 | path = "../../libs/libd7" -------------------------------------------------------------------------------- /modules/driver_ps2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![deny(unused_must_use)] 4 | 5 | extern crate alloc; 6 | extern crate libd7; 7 | 8 | use core::arch::asm; 9 | 10 | use libd7::{ipc, select, syscall}; 11 | 12 | mod keyboard; 13 | mod state; 14 | 15 | use self::keyboard::Keyboard; 16 | 17 | #[no_mangle] 18 | fn main() -> ! { 19 | syscall::debug_print("PS/2 driver starting"); 20 | 21 | // Interrupts must be disabled during initialization, 22 | // so this wont deadlock on not-terribly-slow computers, including Qemu 23 | let mut keyboard = unsafe { 24 | asm!("cli"); 25 | let mut k = Keyboard::new(); 26 | k.init(); 27 | asm!("sti"); 28 | k 29 | }; 30 | 31 | syscall::debug_print("PS/2 keyboard initialization complete"); 32 | 33 | // Subscribe to hardware events 34 | let irq = ipc::UnreliableSubscription::::exact("irq/keyboard").unwrap(); 35 | 36 | // Inform serviced that we are running 37 | libd7::service::register("driver_ps2", false); 38 | 39 | loop { 40 | select! { 41 | one(irq) => unsafe { 42 | let byte = irq.receive().unwrap(); 43 | if let Some(event) = keyboard.notify(byte) { 44 | ipc::publish("keyboard/event", &event).unwrap(); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /modules/driver_ps2/src/state.rs: -------------------------------------------------------------------------------- 1 | use libd7::d7abi::ipc::protocol::keyboard::KeyboardEvent; 2 | 3 | const RELEASED: u8 = 0xf0; 4 | const ALTERNATIVE: u8 = 0xe0; 5 | const ERROR_0: u8 = 0x00; 6 | const ERROR_1: u8 = 0xff; 7 | 8 | #[derive(Clone, Copy)] 9 | pub struct KeyboardState { 10 | /// Next keypress is a release 11 | pub next_is_release: bool, 12 | /// Next keypress from alternative set 13 | pub next_is_alternative: bool, 14 | } 15 | impl KeyboardState { 16 | pub const fn new() -> Self { 17 | Self { 18 | next_is_release: false, 19 | next_is_alternative: false, 20 | } 21 | } 22 | 23 | pub fn apply(&mut self, byte: u8) -> Option { 24 | if byte == RELEASED { 25 | self.next_is_release = true; 26 | None 27 | } else if byte == ALTERNATIVE { 28 | self.next_is_alternative = true; 29 | None 30 | } else if byte == ERROR_0 || byte == ERROR_1 { 31 | self.next_is_release = false; 32 | self.next_is_alternative = false; 33 | None 34 | } else { 35 | let keycode = (byte as u16) | ((self.next_is_alternative as u16) << 8); 36 | let event = KeyboardEvent { 37 | keycode, 38 | release: self.next_is_release, 39 | }; 40 | self.next_is_release = false; 41 | self.next_is_alternative = false; 42 | Some(event) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /modules/driver_rtc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_rtc" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | bitflags = "1.3" 21 | 22 | [dependencies.cpuio] 23 | git = "https://github.com/Dentosal/cpuio-rs" 24 | 25 | [dependencies.libd7] 26 | version = "*" 27 | path = "../../libs/libd7" 28 | -------------------------------------------------------------------------------- /modules/driver_rtl8139/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_driver_rtl8139" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies] 18 | log = "0.4" 19 | spin = "0.9" 20 | bitflags = "1.3" 21 | 22 | [dependencies.cpuio] 23 | git = "https://github.com/Dentosal/cpuio-rs" 24 | 25 | [dependencies.hashbrown] 26 | version = "0.11" 27 | features = ["nightly", "inline-more", "serde"] 28 | 29 | [dependencies.serde] 30 | version = "1.0" 31 | default-features = false 32 | features = ["alloc", "derive"] 33 | 34 | [dependencies.libd7] 35 | version = "*" 36 | path = "../../libs/libd7" 37 | 38 | [dependencies.d7pci] 39 | version = "*" 40 | path = "../../libs/d7pci" 41 | -------------------------------------------------------------------------------- /modules/driver_rtl8139/src/dma.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 2 | 3 | use libd7::{syscall, PhysAddr, VirtAddr}; 4 | 5 | static MAPPED: AtomicBool = AtomicBool::new(false); 6 | static VIRTUAL_ADDR: VirtAddr = unsafe { VirtAddr::new_unsafe(0x10_0000_0000) }; // Should be free 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 9 | pub struct DMARegion { 10 | pub phys: PhysAddr, 11 | pub virt: VirtAddr, 12 | } 13 | impl DMARegion { 14 | pub fn allocate(size_bytes: usize) -> Self { 15 | let phys = syscall::dma_allocate(size_bytes as u64).unwrap(); 16 | 17 | // Assumes that DMA block is on the first page. 18 | // Keep in sync with plan.md 19 | if !MAPPED.compare_and_swap(false, true, Ordering::SeqCst) { 20 | unsafe { 21 | syscall::mmap_physical( 22 | PhysAddr::new(0), 23 | VIRTUAL_ADDR, 24 | size_bytes as u64, 25 | syscall::MemoryProtectionFlags::READ | syscall::MemoryProtectionFlags::WRITE, 26 | ) 27 | .unwrap(); 28 | } 29 | } 30 | 31 | Self { 32 | phys, 33 | virt: VIRTUAL_ADDR + phys.as_u64(), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/driver_rtl8139/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![allow(unused_imports, dead_code)] 4 | #![deny(unused_must_use)] 5 | 6 | #[macro_use] 7 | extern crate alloc; 8 | 9 | #[macro_use] 10 | extern crate libd7; 11 | 12 | use alloc::vec::Vec; 13 | use hashbrown::HashMap; 14 | 15 | use libd7::net::d7net::MacAddr; 16 | use libd7::{ipc, process::ProcessId, select, syscall}; 17 | 18 | mod dma; 19 | mod rtl8139; 20 | 21 | #[no_mangle] 22 | fn main() -> ! { 23 | syscall::debug_print("RTL8139 driver starting"); 24 | 25 | // Make sure this is the only NIC driver running 26 | libd7::service::register("exclude/nic", false); 27 | 28 | // Get device info 29 | let pci_device: Option = ipc::request("pci/device", &"rtl8139").unwrap(); 30 | let pci_device = pci_device.expect("PCI device resolution failed unexpectedly"); 31 | 32 | // Initialize the driver 33 | let mut device = unsafe { rtl8139::RTL8139::new(pci_device) }; 34 | 35 | // Subscribe to hardware events 36 | // TODO: dynamic IRQ detection (in kernel?) 37 | let irq = ipc::UnreliableSubscription::<()>::exact(&"irq/11").unwrap(); 38 | // let irq = ipc::UnreliableSubscription::<()>::exact(&"irq/17").unwrap(); 39 | // let irq = ipc::UnreliableSubscription::::exact(&format!("irq/{}", device.irq)).unwrap(); 40 | 41 | // Subscribe to client requests 42 | let get_mac: ipc::Server<(), MacAddr> = ipc::Server::exact("nic/rtl8139/mac").unwrap(); 43 | let send = ipc::UnreliableSubscription::>::exact("nic/send").unwrap(); 44 | 45 | // Inform serviced that we are running. 46 | libd7::service::register("driver_rtl8139", false); 47 | 48 | println!("rtl Ready"); 49 | 50 | loop { 51 | select! { 52 | one(irq) => { 53 | let _: () = irq.receive().unwrap(); 54 | println!("rtl: IRQ NOTIFY"); 55 | let received_packets = device.notify_irq(); 56 | for packet in received_packets { 57 | ipc::deliver("netd/received", &packet).unwrap(); 58 | } 59 | }, 60 | one(get_mac) => get_mac.handle(|()| Ok(device.mac_addr())).unwrap(), 61 | one(send) => { 62 | let packet: Vec = send.receive().unwrap(); 63 | println!("rtl: SEND PKT"); 64 | device.send(&packet); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /modules/examplebin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d7_examplebin" 3 | version = "0.1.0" 4 | authors = ["Hannes Karppila "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | crate-type = ["staticlib"] 10 | 11 | [profile.dev] 12 | panic = "abort" 13 | 14 | [profile.release] 15 | panic = "abort" 16 | 17 | [dependencies.libd7] 18 | version = "*" 19 | path = "../../libs/libd7" 20 | -------------------------------------------------------------------------------- /modules/examplebin/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(allocator_api)] 3 | #![deny(unused_must_use)] 4 | 5 | #[macro_use] 6 | extern crate alloc; 7 | 8 | use alloc::string::String; 9 | use alloc::vec::Vec; 10 | use core::convert::TryInto; 11 | 12 | #[macro_use] 13 | extern crate libd7; 14 | 15 | use libd7::{ 16 | // console::Console, 17 | net::tcp, 18 | service, 19 | syscall, 20 | }; 21 | 22 | #[no_mangle] 23 | fn main() -> u64 { 24 | let pid = syscall::get_pid(); 25 | 26 | // Wait until netd is available 27 | println!("Wait for netd >"); 28 | service::wait_for_one("netd"); 29 | println!("Wait for netd <"); 30 | 31 | syscall::sched_sleep_ns(2_000_000_000).unwrap(); 32 | 33 | if let Err(err) = main_inner() { 34 | println!("Error: {:?}", err); 35 | return 1; 36 | } 37 | 38 | return 0; 39 | } 40 | 41 | fn main_inner() -> Result<(), tcp::Error> { 42 | println!("Connect"); 43 | let socket = tcp::Stream::connect("example.org:80")?; 44 | println!("Send request"); 45 | socket.send(b"GET / HTTP/1.1\r\nHost: example.org\r\nConnection: close\r\n\r\n")?; 46 | println!("Shutdown"); 47 | socket.shutdown()?; 48 | println!("Read response"); 49 | let mut fetched = String::new(); 50 | let mut buffer = [0; 1024]; 51 | loop { 52 | let n = socket.recv(&mut buffer)?; 53 | fetched.push_str(core::str::from_utf8(&buffer[..n]).expect("Invalid utf-8")); 54 | if n < buffer.len() { 55 | break; 56 | } 57 | } 58 | 59 | println!("reply {}", fetched); 60 | socket.close()?; 61 | 62 | Ok(()) 63 | 64 | // // Console 65 | // let mut console = Console::open( 66 | // "/dev/console", 67 | // "/mnt/staticfs/keycode.json", 68 | // "/mnt/staticfs/keymap.json", 69 | // ) 70 | // .unwrap(); 71 | // loop { 72 | // syscall::debug_print(&format!("Input test:")); 73 | // let line = console.read_line().unwrap(); 74 | // syscall::debug_print(&format!("Line {:?}", line)); 75 | // if line == "exit" { 76 | // break; 77 | // } 78 | // } 79 | 80 | // 0 81 | } 82 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | version = "Two" 2 | unstable_features = true 3 | match_block_trailing_comma = true 4 | use_field_init_shorthand = true 5 | where_single_line = true 6 | use_try_shorthand = true 7 | overflow_delimited_expr = true 8 | fn_params_layout = "Compressed" 9 | -------------------------------------------------------------------------------- /src/asm_misc/smp_ap_startup.asm: -------------------------------------------------------------------------------- 1 | ; SMP AP startup trampoline 2 | ; This is where other cores start their execution. 3 | ; Stack must not be used here. 4 | 5 | %include "build/constants.asm" 6 | 7 | %define CODE_SEG 0x0008 8 | %define DATA_SEG 0x0010 9 | 10 | [BITS 16] 11 | [ORG 0x2000] ; todo: constantify? 12 | 13 | ap_startup: 14 | jmp 0x0000:.flush_cs ; reload CS to zero 15 | .flush_cs: 16 | 17 | ; initialize segment registers 18 | xor ax, ax 19 | mov ds, ax 20 | mov es, ax 21 | mov ss, ax 22 | 23 | 24 | lidt [IDT] ; Load a zero length IDT 25 | 26 | ; Enter long mode. 27 | mov eax, 10100000b ; Set the PAE and PGE bit. 28 | mov cr4, eax 29 | 30 | mov edx, PAGE_TABLES_LOCATION ; Point CR3 at the PML4. 31 | mov cr3, edx 32 | 33 | mov ecx, 0xC0000080 ; Read from the EFER MSR. 34 | rdmsr 35 | or eax, 0x00000900 ; Set the LME & NXE bit. 36 | wrmsr 37 | 38 | mov ebx, cr0 ; Activate long mode by enabling 39 | or ebx,0x80000001 ; paging and protection simultaneously 40 | mov cr0, ebx 41 | 42 | lgdt [GDT.Pointer] ; Load GDT.Pointer defined below. 43 | 44 | jmp CODE_SEG:long_mode ; Load CS with 64 bit segment and flush the instruction cache 45 | 46 | 47 | ; Global Descriptor Table 48 | ALIGN 4 49 | GDT: 50 | .Null: 51 | dq 0x0000000000000000 ; Null Descriptor - should be present. 52 | 53 | .Code: 54 | dq 0x00209A0000000000 ; 64-bit code descriptor (exec/read). 55 | dq 0x0000920000000000 ; 64-bit data descriptor (read/write). 56 | 57 | ALIGN 4 58 | dw 0 ; Padding to make the "address of the GDT" field aligned on a 4-byte boundary 59 | 60 | .Pointer: 61 | dw $ - GDT - 1 ; 16-bit Size (Limit) of GDT. 62 | dd GDT ; 32-bit Base Address of GDT. (CPU will zero extend to 64-bit) 63 | 64 | 65 | ALIGN 4 66 | IDT: 67 | .Length dw 0 68 | .Base dd 0 69 | 70 | 71 | [BITS 64] 72 | long_mode: 73 | mov ax, DATA_SEG 74 | mov ds, ax 75 | mov es, ax 76 | mov fs, ax 77 | mov gs, ax 78 | mov ss, ax 79 | 80 | mov rcx, 1 ; set rcx = 1 to mark AP cpu 81 | jmp 0x100_0000 ; TODO: constantify -------------------------------------------------------------------------------- /src/driver/acpi/tables/dsdt.rs: -------------------------------------------------------------------------------- 1 | //! https://wiki.osdev.org/DSDT 2 | 3 | -------------------------------------------------------------------------------- /src/driver/acpi/tables/fadt.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use core::mem; 3 | use core::slice; 4 | use x86_64::PhysAddr; 5 | 6 | use crate::memory; 7 | 8 | use super::{is_legacy_acpi, rsdt_get, SDTHeader}; 9 | 10 | // TODO: rename fields to snake_case 11 | #[allow(non_snake_case)] 12 | #[derive(Debug, Copy, Clone)] 13 | #[repr(C, packed)] 14 | struct Fadt { 15 | header: SDTHeader, 16 | firmware_ctrl: u32, 17 | dsdt: u32, 18 | /// field used in ACPI 1.0; no longer in use, for compatibility only 19 | reserved: u8, 20 | PreferredPowerManagementProfile: u8, 21 | SCI_Interrupt: u16, 22 | SMI_CommandPort: u32, 23 | AcpiEnable: u8, 24 | AcpiDisable: u8, 25 | S4BIOS_REQ: u8, 26 | PSTATE_Control: u8, 27 | PM1aEventBlock: u32, 28 | PM1bEventBlock: u32, 29 | PM1aControlBlock: u32, 30 | PM1bControlBlock: u32, 31 | PM2ControlBlock: u32, 32 | PMTimerBlock: u32, 33 | GPE0Block: u32, 34 | GPE1Block: u32, 35 | PM1EventLength: u8, 36 | PM1ControlLength: u8, 37 | PM2ControlLength: u8, 38 | PMTimerLength: u8, 39 | GPE0Length: u8, 40 | GPE1Length: u8, 41 | GPE1Base: u8, 42 | CStateControl: u8, 43 | WorstC2Latency: u16, 44 | WorstC3Latency: u16, 45 | FlushSize: u16, 46 | FlushStride: u16, 47 | DutyOffset: u8, 48 | DutyWidth: u8, 49 | DayAlarm: u8, 50 | MonthAlarm: u8, 51 | Century: u8, 52 | /// reserved in ACPI 1.0; used since ACPI 2.0+ 53 | BootArchitectureFlags: u16, 54 | reserved2: u8, 55 | Flags: u32, 56 | ResetReg: GenericAddress, 57 | ResetValue: u8, 58 | reserved3: [u8; 3], 59 | /// 64bit pointers - Available on ACPI 2.0+ 60 | X_FirmwareControl: u64, 61 | x_dsdt: u64, 62 | X_PM1aEventBlock: GenericAddress, 63 | X_PM1bEventBlock: GenericAddress, 64 | X_PM1aControlBlock: GenericAddress, 65 | X_PM1bControlBlock: GenericAddress, 66 | X_PM2ControlBlock: GenericAddress, 67 | X_PMTimerBlock: GenericAddress, 68 | X_GPE0Block: GenericAddress, 69 | X_GPE1Block: GenericAddress, 70 | } 71 | 72 | /// https://wiki.osdev.org/FADT#GenericAddressStructure 73 | #[derive(Debug, Copy, Clone)] 74 | #[repr(C, packed)] 75 | struct GenericAddress { 76 | address_space: u8, 77 | bit_width: u8, 78 | bit_offset: u8, 79 | access_size: u8, 80 | address: u64, 81 | } 82 | 83 | pub fn init() { 84 | let fadt_ptr = rsdt_get(b"FACP").expect("FADT not found"); 85 | let addr = memory::phys_to_virt(fadt_ptr); 86 | let fadt: Fadt = unsafe { *addr.as_ptr() }; 87 | 88 | let legacy = is_legacy_acpi(); 89 | let dsdt_phys = if legacy { 90 | fadt.dsdt as u64 91 | } else { 92 | fadt.x_dsdt 93 | }; 94 | 95 | let vptr = memory::phys_to_virt(PhysAddr::new(dsdt_phys)); 96 | let dsdt_header: SDTHeader = unsafe { *vptr.as_ptr() }; 97 | 98 | let size = dsdt_header.length as usize; 99 | let _code: &[u8] = unsafe { slice::from_raw_parts(vptr.as_ptr(), size) }; 100 | 101 | // TODO: _code is AML code, and should be given to an interpreter 102 | } 103 | -------------------------------------------------------------------------------- /src/driver/acpi/tables/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use core::mem::size_of; 3 | use hashbrown::HashMap; 4 | 5 | use crate::memory::{self, prelude::*}; 6 | 7 | pub mod fadt; 8 | pub mod madt; 9 | mod rsdt; 10 | 11 | #[derive(Debug, Copy, Clone)] 12 | #[repr(C, packed)] 13 | pub struct SDTHeader { 14 | pub signature: [u8; 4], 15 | pub length: u32, 16 | pub revision: u8, 17 | pub checksum: u8, 18 | pub oem_id: [u8; 6], 19 | pub oem_table_id: [u8; 8], 20 | pub oem_revision: u32, 21 | pub creator_id: u32, 22 | pub creator_revision: u32, 23 | } 24 | 25 | static LEGAZY_ACPI: spin::Once = spin::Once::new(); 26 | static RSDT_ENTRIES: spin::Once> = spin::Once::new(); 27 | 28 | pub fn init_rsdt() { 29 | match unsafe { rsdt::get_rsdp_and_parse() } { 30 | Ok(result) => { 31 | let header_ptr_u64 = match result { 32 | rsdt::ParseResult::RSDT(ptr_u32) => ptr_u32 as u64, 33 | rsdt::ParseResult::XSDT(ptr_u64) => ptr_u64, 34 | }; 35 | let body_ptr_size = match result { 36 | rsdt::ParseResult::RSDT(_) => 4, 37 | rsdt::ParseResult::XSDT(_) => 8, 38 | }; 39 | LEGAZY_ACPI.call_once(|| match result { 40 | rsdt::ParseResult::RSDT(_) => true, 41 | rsdt::ParseResult::XSDT(_) => false, 42 | }); 43 | 44 | let vptr = memory::phys_to_virt(PhysAddr::new(header_ptr_u64)); 45 | let root_header: SDTHeader = unsafe { *vptr.as_ptr() }; 46 | 47 | assert_eq!(&root_header.signature, b"RSDT"); 48 | 49 | let entry_count = 50 | (root_header.length as usize - size_of::()) / body_ptr_size; 51 | 52 | // Loop through body pointers 53 | let mut result = HashMap::new(); 54 | for index in 0..entry_count { 55 | let vptr_ptr = memory::phys_to_virt(PhysAddr::new( 56 | header_ptr_u64 + (size_of::() + index * body_ptr_size) as u64, 57 | )); 58 | 59 | unsafe { 60 | let vptr = match body_ptr_size { 61 | 4 => { 62 | let p: u32 = *vptr_ptr.as_ptr(); 63 | PhysAddr::new(p as u64) 64 | }, 65 | 8 => { 66 | let p: u64 = *vptr_ptr.as_ptr(); 67 | PhysAddr::new(p) 68 | }, 69 | _ => unreachable!(), 70 | }; 71 | let header: SDTHeader = *memory::phys_to_virt(vptr).as_ptr(); 72 | log::trace!("RSDT {:?}", String::from_utf8_lossy(&header.signature)); 73 | result.insert(header.signature, vptr); 74 | }; 75 | } 76 | RSDT_ENTRIES.call_once(|| result); 77 | }, 78 | Err(error) => panic!("RSDT error {:?}", error), 79 | } 80 | } 81 | 82 | pub fn is_legacy_acpi() -> bool { 83 | *LEGAZY_ACPI.poll().expect("RSDT unintialized") 84 | } 85 | 86 | pub fn rsdt_get(key: &[u8; 4]) -> Option { 87 | RSDT_ENTRIES 88 | .poll() 89 | .expect("RSDT unintialized") 90 | .get(key) 91 | .cloned() 92 | } 93 | -------------------------------------------------------------------------------- /src/driver/mod.rs: -------------------------------------------------------------------------------- 1 | //! Hardware drivers 2 | 3 | #[macro_use] 4 | pub mod vga_buffer; 5 | 6 | pub mod acpi; 7 | pub mod ioapic; 8 | pub mod pic; 9 | pub mod pit; 10 | pub mod tsc; 11 | pub mod uart; 12 | -------------------------------------------------------------------------------- /src/driver/pit.rs: -------------------------------------------------------------------------------- 1 | //! https://wiki.osdev.org/Programmable_Interval_Timer 2 | //! Only used for short-timed sleeps, e.g. for measuring 3 | //! TSC/HPET/APICTimer speed 4 | 5 | #![allow(unused_variables)] 6 | 7 | use core::arch::asm; 8 | use core::sync::atomic::{AtomicU64, Ordering}; 9 | 10 | const PIT_CH0: u16 = 0x40; // Channel 0 data port (read/write) (PIC TIMER) 11 | const PIT_CH1: u16 = 0x41; // Channel 1 data port (read/write) (UNUSED) 12 | const PIT_CH2: u16 = 0x42; // Channel 2 data port (read/write) (PC SPEAKER) 13 | const PIT_REG: u16 = 0x43; // Mode/Command register (write only, a read is ignored) 14 | const PIT_CH2_CONTROL: u16 = 0x61; 15 | 16 | /// Frequency of the internal oscillator, in Hz 17 | const FREQ_HZ: u32 = 1_193_182; 18 | 19 | /// Returns the number of nanoseconds between ticks 20 | fn set_freq_and_start(target_freq_hz: u32) -> u64 { 21 | assert!(target_freq_hz >= 10, "Requested PIT frequency too low"); 22 | assert!( 23 | target_freq_hz < FREQ_HZ / 2, 24 | "Requested PIT frequency too high" 25 | ); 26 | let reload_value = FREQ_HZ / target_freq_hz; 27 | assert!(reload_value > 0); 28 | assert!(reload_value <= (u16::MAX as u32)); 29 | unsafe { 30 | // Channel 0, lobyte/hibyte, Rate generator, Binary mode 31 | cpuio::outb(0b00_11_010_0, PIT_REG); // command 32 | cpuio::outb(reload_value as u8, PIT_CH0); // low 33 | cpuio::outb((reload_value >> 8) as u8, PIT_CH0); // high 34 | } 35 | let actual_freq_hz = FREQ_HZ / reload_value; 36 | const MUL_NSEC: u64 = 1_000_000_000; 37 | MUL_NSEC / (actual_freq_hz as u64) 38 | } 39 | 40 | static ELAPSED_TICKS: AtomicU64 = AtomicU64::new(0); 41 | 42 | /// Sleeps specified number of nanoseconds as accurately as possible. 43 | /// Only usable during kernel initialization. 44 | /// Enables and disables exceptions to work. 45 | pub fn kernel_early_sleep_ns(ns: u64) { 46 | ELAPSED_TICKS.store(0, Ordering::SeqCst); 47 | let ns_per_tick = set_freq_and_start(1_000); 48 | let total_ticks = ns / ns_per_tick; 49 | unsafe { 50 | asm!("sti"); 51 | while ELAPSED_TICKS.load(Ordering::SeqCst) < total_ticks { 52 | asm!("xor rax, rax", lateout("rax") _); 53 | // asm!("hlt"); 54 | } 55 | asm!("cli"); 56 | } 57 | } 58 | 59 | #[inline] 60 | pub fn callback() { 61 | ELAPSED_TICKS.fetch_add(1, Ordering::SeqCst); 62 | } 63 | 64 | /// After the PIT is no longer used, this disables it 65 | /// Will cause a single interrupt as it uses one-shot mode 66 | pub fn disable() { 67 | log::debug!("Disabling PIT"); 68 | unsafe { 69 | // Channel 0, lobyte/hibyte, Interrupt On Terminal Count, Binary mode 70 | cpuio::outb(0b00_11_000_0, PIT_REG); // command 71 | cpuio::outb(0, PIT_CH0); // low 72 | cpuio::outb(0, PIT_CH0); // high 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/driver/tsc.rs: -------------------------------------------------------------------------------- 1 | //! Intel guarantees that TSC will not overflow within 10 years of last 2 | //! CPU reset (or counter reset). 3 | 4 | use core::arch::asm; 5 | 6 | /// Reset TSC to zero. 7 | /// This should be used between deadlines if TSC value is near overflow. 8 | /// # Warning 9 | /// Do not reset the counter if a deadline is currently being used. 10 | #[inline] 11 | pub fn reset() { 12 | unsafe { 13 | asm!("wrmsr", 14 | in("ecx") 0x10, // TSC MSR 15 | in("edx") 0u32, 16 | in("eax") 0u32, 17 | options(nostack, nomem) 18 | ) 19 | } 20 | } 21 | 22 | /// Read TSC value, serializing 23 | #[inline] 24 | pub fn read() -> u64 { 25 | let rdx: u64; 26 | let rax: u64; 27 | unsafe { 28 | asm!( 29 | "rdtscp", // Serializing read 30 | out("rdx") rdx, 31 | out("rax") rax, 32 | out("rcx") _, // processor id 33 | options(nomem, nostack) 34 | ) 35 | } 36 | 37 | (rdx << 32) | (rax & 0xffff_ffff) 38 | } 39 | 40 | /// Sets deadline 41 | #[inline] 42 | pub fn set_deadline(deadline: u64) { 43 | // log::trace!("Set deadline {} (current {})", deadline, read()); 44 | unsafe { 45 | asm!("wrmsr", 46 | in("ecx") 0x6e0, 47 | in("edx") (deadline >> 32) as u32, 48 | in("eax") deadline as u32, 49 | options(nostack, nomem) 50 | ) 51 | } 52 | } 53 | 54 | /// Cancels deadline and disarms timer 55 | #[inline] 56 | pub fn clear_deadline() { 57 | unsafe { 58 | asm!("wrmsr", 59 | in("ecx") 0x6e0, 60 | in("edx") 0u32, 61 | in("eax") 0u32, 62 | options(nostack, nomem) 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/driver/uart.rs: -------------------------------------------------------------------------------- 1 | //! https://wiki.osdev.org/Serial_Ports 2 | //! UART, output only, COM1 only 3 | 4 | use core::sync::atomic::{AtomicBool, Ordering}; 5 | use cpuio::{inb, inw, outb}; 6 | 7 | const COM1: u16 = 0x3f8; 8 | 9 | /// Returns true if serial exists and works. 10 | /// # Safety 11 | /// `port_base` must be valid 12 | #[must_use] 13 | unsafe fn init_serial(port_base: u16) -> bool { 14 | outb(0x00, port_base + 1); // Disable all interrupts 15 | outb(0x80, port_base + 3); // Enable DLAB (set baud rate divisor) 16 | outb(0x03, port_base + 0); // Set divisor to 3 (lo byte) 38400 baud 17 | outb(0x00, port_base + 1); // (hi byte) 18 | outb(0x03, port_base + 3); // 8 bits, no parity, one stop bit 19 | outb(0xc7, port_base + 2); // Enable FIFO, clear them, with 14-byte threshold 20 | outb(0x0b, port_base + 4); // IRQs enabled, RTS/DSR set 21 | outb(0x1b, port_base + 4); // Set in loopback mode, test the serial chip 22 | outb(0xae, port_base + 0); // Test serial chip (send byte 0xae and same returned) 23 | 24 | // Check if serial is faulty (i.e: not same byte as sent) 25 | if inb(port_base) != 0xae { 26 | return false; 27 | } 28 | 29 | // If serial is not faulty set it in normal operation mode 30 | // (not-loopback with IRQs enabled and OUT#1 and OUT#2 bits enabled) 31 | outb(0x0f, port_base + 4); 32 | true 33 | } 34 | 35 | unsafe fn is_transmit_empty(port_base: u16) -> bool { 36 | inb(port_base + 5) & 0x20 != 0 37 | } 38 | 39 | unsafe fn write_serial(port_base: u16, c: u8) { 40 | while !is_transmit_empty(port_base) {} 41 | outb(c, port_base); 42 | } 43 | 44 | static HAS_COM1: AtomicBool = AtomicBool::new(false); 45 | 46 | pub fn has_com1() -> bool { 47 | HAS_COM1.load(Ordering::SeqCst) 48 | } 49 | 50 | pub fn write_com1(c: u8) { 51 | unsafe { write_serial(COM1, c) } 52 | } 53 | 54 | pub fn init() { 55 | let has_com1 = unsafe { init_serial(COM1) }; 56 | log::debug!("COM1 enabled: {}", has_com1); 57 | HAS_COM1.store(has_com1, Ordering::SeqCst); 58 | } 59 | -------------------------------------------------------------------------------- /src/entry.asm: -------------------------------------------------------------------------------- 1 | [BITS 64] 2 | 3 | %include "build/constants.asm" 4 | 5 | global _start 6 | global endlabel 7 | extern rust_main 8 | extern rust_ap_get_stack 9 | extern rust_ap_main 10 | 11 | section .entry 12 | _start: 13 | ; clear segment registers 14 | xor ax, ax 15 | mov ss, ax 16 | mov ds, ax 17 | mov es, ax 18 | mov fs, ax 19 | mov gs, ax 20 | 21 | test rcx, rcx 22 | jnz .ap_cpu 23 | 24 | ; set up stack 25 | mov rsp, stack_top 26 | 27 | ; jump to kernel 28 | jmp rust_main 29 | 30 | .ap_cpu: 31 | ; Reuse old kernel stack in case the Rust code needs it. 32 | ; Shouldn't cause any issues, as kernel stack isn't ever freed, 33 | ; and multiple processor cores should always write same values 34 | ; here, as the execution is identical. 35 | mov rsp, ap_tmp_stack_top 36 | 37 | ; Get a new available stack, top at rax 38 | call rust_ap_get_stack 39 | mov rsp, rax ; Switch stacks 40 | 41 | jmp rust_ap_main 42 | 43 | section .bss 44 | 45 | ; reserve space for BSP stack 46 | stack_bottom: 47 | resb (4096*1000) 48 | ; I have had a couple of overflows with just 4096-sized stack. 49 | ; Might be a good idea to increase this even more. 50 | ; Might require even 4096*0x10000, however this will make zeroing out .bss very slow. 51 | stack_top: 52 | 53 | ; reserve space for tmp AP stack 54 | ap_tmp_stack_bottom: 55 | resb 4096*10 56 | ap_tmp_stack_top: 57 | -------------------------------------------------------------------------------- /src/initrd.rs: -------------------------------------------------------------------------------- 1 | //! Initial ramdisk driver 2 | 3 | use alloc::string::String; 4 | use alloc::vec::Vec; 5 | use hashbrown::HashMap; 6 | use x86_64::{PhysAddr, VirtAddr}; 7 | 8 | use d7initrd::{FileEntry, HEADER_MAGIC, HEADER_SIZE_BYTES}; 9 | 10 | use crate::memory::{self, phys_to_virt, prelude::*}; 11 | use crate::util::elf_parser::{self, ELFData, ELFHeader, ELFProgramHeader}; 12 | 13 | #[derive(Debug)] 14 | struct InitRD { 15 | /// Files by name. 16 | files: HashMap, 17 | /// A slice containing all files, concatenated. 18 | /// The lifetime is static, as these are never deallocated. 19 | slice: &'static [u8], 20 | } 21 | 22 | static INITRD: spin::Once = spin::Once::new(); 23 | 24 | pub fn init(elf_data: ELFData) { 25 | unsafe { 26 | // Get address 27 | let start_addr = PhysAddr::from_u64(page_align_up(elf_data.last_addr())); 28 | let header = phys_to_virt(start_addr); 29 | 30 | let hptr: *const u8 = header.as_ptr(); 31 | let magic = *(hptr.add(0) as *const u32); 32 | let size_flist = *(hptr.add(4) as *const u32); 33 | let size_total = *(hptr.add(8) as *const u64); 34 | 35 | assert_eq!(magic, HEADER_MAGIC, "InitRD magic mismatch"); 36 | assert!(size_flist as u64 > 0, "InitRD header empty"); 37 | assert!( 38 | size_flist as u64 + 8 < PAGE_SIZE_BYTES, 39 | "InitRD header too large" 40 | ); 41 | 42 | let header_bytes: &[u8] = core::slice::from_raw_parts(hptr.add(16), size_flist as usize); 43 | 44 | let file_list: Result, _> = pinecone::from_bytes(&header_bytes[..]); 45 | let file_list = file_list.expect("Could not deserialize staticfs file list"); 46 | log::trace!("Files {:?}", file_list); 47 | 48 | // Initialize 49 | let files_offset = HEADER_SIZE_BYTES + (size_flist as usize); 50 | let files_len = size_total as usize - files_offset; 51 | let p: *const u8 = header.as_ptr(); 52 | INITRD.call_once(move || InitRD { 53 | files: file_list.into_iter().map(|f| (f.name.clone(), f)).collect(), 54 | slice: core::slice::from_raw_parts(p.add(files_offset), files_len), 55 | }); 56 | } 57 | } 58 | 59 | pub fn read(name: &str) -> Option<&'static [u8]> { 60 | let rd: &InitRD = INITRD.poll().unwrap(); 61 | log::trace!("Read {:?} (found={})", name, rd.files.contains_key(name)); 62 | let entry = rd.files.get(name)?; 63 | let start = entry.offset as usize; 64 | let len = entry.size as usize; 65 | Some(&rd.slice[start..start + len]) 66 | } 67 | -------------------------------------------------------------------------------- /src/interrupt/gdt.rs: -------------------------------------------------------------------------------- 1 | use core::mem::size_of; 2 | use core::ptr; 3 | use core::sync::atomic::{AtomicU8, Ordering}; 4 | use x86_64::structures::gdt::SegmentSelector; 5 | use x86_64::structures::tss::TaskStateSegment; 6 | use x86_64::{PrivilegeLevel, VirtAddr}; 7 | 8 | pub use x86_64::structures::gdt::Descriptor; 9 | 10 | use crate::memory::constants::GDT_ADDR; 11 | 12 | pub const DOUBLE_FAULT_IST_INDEX: usize = 0; 13 | 14 | /// Max size is fixed so we can have an array of these 15 | const GDT_MAX_SIZE: usize = 8; 16 | 17 | pub struct GdtBuilder { 18 | addr: VirtAddr, 19 | next_entry: usize, 20 | } 21 | impl GdtBuilder { 22 | unsafe fn new(addr: VirtAddr) -> Self { 23 | Self { 24 | addr, 25 | next_entry: 1, // first entry is the null descriptor, so it is not free 26 | } 27 | } 28 | 29 | pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector { 30 | let base: *mut u64 = self.addr.as_mut_ptr(); 31 | let index = self.next_entry; 32 | unsafe { 33 | match entry { 34 | Descriptor::UserSegment(value) => { 35 | assert!(index + 1 < GDT_MAX_SIZE, "GDT full"); 36 | ptr::write(base.add(self.next_entry), value); 37 | self.next_entry += 1; 38 | }, 39 | Descriptor::SystemSegment(value_low, value_high) => { 40 | assert!(index + 2 < GDT_MAX_SIZE, "GDT full"); 41 | ptr::write(base.add(self.next_entry), value_low); 42 | ptr::write(base.add(self.next_entry + 1), value_high); 43 | self.next_entry += 2; 44 | }, 45 | }; 46 | } 47 | SegmentSelector::new(index as u16, PrivilegeLevel::Ring0) 48 | } 49 | 50 | pub unsafe fn load(self) { 51 | use core::mem::size_of; 52 | use x86_64::instructions::tables::{lgdt, DescriptorTablePointer}; 53 | 54 | let ptr = DescriptorTablePointer { 55 | base: self.addr, 56 | limit: (self.next_entry * size_of::() - 1) as u16, 57 | }; 58 | 59 | lgdt(&ptr); 60 | } 61 | } 62 | 63 | static USED_GDTS: AtomicU8 = AtomicU8::new(0); 64 | 65 | /// Adds to an array of immutable GDTs, one for each processor core 66 | pub fn create_new() -> GdtBuilder { 67 | let index = USED_GDTS.fetch_add(1, Ordering::SeqCst); 68 | let new_gdt_base = 69 | GDT_ADDR.as_u64() + (index as u64) * (GDT_MAX_SIZE * size_of::()) as u64; 70 | unsafe { GdtBuilder::new(VirtAddr::new(new_gdt_base)) } 71 | } 72 | -------------------------------------------------------------------------------- /src/interrupt/idt.rs: -------------------------------------------------------------------------------- 1 | use x86_64::PrivilegeLevel::{self, Ring0}; 2 | use x86_64::VirtAddr; 3 | 4 | // These constants MUST have defined with same values as those in build/constants.asm 5 | // They also MUST match the ones in plan.md 6 | // If a constant defined here doesn't exists in that file, then it's also fine 7 | const GDT_SELECTOR_CODE: u16 = 0x08; 8 | pub const ADDRESS: usize = 0; 9 | pub const VIRT_ADDRESS: VirtAddr = VirtAddr::new_truncate(ADDRESS as u64); 10 | pub const ENTRY_COUNT: usize = 0x100; 11 | 12 | /// http://wiki.osdev.org/IDT#Structure_AMD64 13 | #[derive(Debug, Clone, Copy)] 14 | #[repr(C, packed)] 15 | pub struct Descriptor { 16 | pointer_low: u16, 17 | gdt_selector: u16, 18 | ist_offset: u8, 19 | options: u8, 20 | pointer_middle: u16, 21 | pointer_high: u32, 22 | reserved: u32, 23 | } 24 | 25 | impl Descriptor { 26 | pub fn new( 27 | present: bool, pointer: u64, ring: PrivilegeLevel, ist_index: Option, 28 | ) -> Descriptor { 29 | let ist_offset = if let Some(i) = ist_index { i + 1 } else { 0 }; 30 | assert!(ist_offset < 0b1000); 31 | assert!(present || (pointer == 0 && ring == Ring0)); // pointer and ring must be 0 if not present 32 | // example options: present => 1, ring 0 => 00, interrupt gate => 0, interrupt gate => 1110 33 | let options: u8 = 34 | 0b0_00_0_1110 | ((ring as u8) << 5) | ((if present { 1 } else { 0 }) << 7); 35 | 36 | Descriptor { 37 | pointer_low: (pointer & 0xffff) as u16, 38 | gdt_selector: GDT_SELECTOR_CODE, 39 | ist_offset, 40 | options, 41 | pointer_middle: ((pointer & 0xffff_0000) >> 16) as u16, 42 | pointer_high: ((pointer & 0xffff_ffff_0000_0000) >> 32) as u32, 43 | reserved: 0, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/interrupt/tss.rs: -------------------------------------------------------------------------------- 1 | use core::mem::size_of; 2 | use core::sync::atomic::{AtomicU8, Ordering}; 3 | use x86_64::structures::tss::TaskStateSegment; 4 | use x86_64::VirtAddr; 5 | 6 | use crate::memory::constants::TSS_ADDR; 7 | 8 | static USED_TSS: AtomicU8 = AtomicU8::new(0); 9 | 10 | /// Adds to an array of immutable GDTs, one for each processor core 11 | pub fn store(tss: TaskStateSegment) -> &'static TaskStateSegment { 12 | let index = USED_TSS.fetch_add(1, Ordering::SeqCst); 13 | let new_base = TSS_ADDR.as_u64() + (index as u64) * (size_of::()) as u64; 14 | let ptr = new_base as *mut TaskStateSegment; 15 | unsafe { 16 | ptr.write(tss); 17 | &*ptr 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/ipc/event_queue.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | 3 | use crate::multitasking::{ExplicitEventId, WaitFor, SCHEDULER}; 4 | 5 | /// Queue with wakeup event blocking with max size 6 | #[derive(Debug)] 7 | pub struct EventQueue { 8 | queue: VecDeque, 9 | event: Option, 10 | limit: usize, 11 | } 12 | impl EventQueue { 13 | pub fn new(limit: usize) -> Self { 14 | Self { 15 | queue: VecDeque::new(), 16 | event: None, 17 | limit, 18 | } 19 | } 20 | 21 | pub fn is_empty(&self) -> bool { 22 | self.queue.is_empty() 23 | } 24 | 25 | /// Returns: 26 | /// * Ok(Some(event)) when push successful and event should be triggered 27 | /// * Ok(None) when push successful but no event 28 | /// * Err(()) when the buffer is full 29 | pub fn push(&mut self, item: T) -> Result, ()> { 30 | if self.queue.len() >= self.limit { 31 | Err(()) 32 | } else { 33 | self.queue.push_back(item); 34 | Ok(self.event.take()) 35 | } 36 | } 37 | 38 | /// Nonblocking, returns None if the queue is empty 39 | pub fn pop(&mut self) -> Option { 40 | self.queue.pop_front() 41 | } 42 | 43 | pub fn pop_or_event(&mut self) -> Result { 44 | self.queue.pop_front().ok_or_else(|| self.get_event()) 45 | } 46 | 47 | pub fn take_event(&mut self) -> Option { 48 | self.event.take() 49 | } 50 | 51 | fn get_event(&mut self) -> ExplicitEventId { 52 | *self.event.get_or_insert_with(WaitFor::new_event_id) 53 | } 54 | 55 | pub fn wait_for(&mut self) -> WaitFor { 56 | if self.queue.is_empty() { 57 | WaitFor::Event(self.get_event()) 58 | } else { 59 | WaitFor::None 60 | } 61 | } 62 | 63 | pub fn into_iter(self) -> impl Iterator { 64 | self.queue.into_iter() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ipc/ring/mod.rs: -------------------------------------------------------------------------------- 1 | //! IPC ring, an io_uring-inspired IPC and IO API 2 | 3 | -------------------------------------------------------------------------------- /src/memory/allocators/dma_allocator.rs: -------------------------------------------------------------------------------- 1 | //! DMA / VirtIO memory buffers (requiring "low" memory) 2 | 3 | use spin::Mutex; 4 | use x86_64::structures::paging as pg; 5 | use x86_64::{PhysAddr, VirtAddr}; 6 | 7 | use super::super::constants::{DMA_MEMORY_SIZE, DMA_MEMORY_START}; 8 | 9 | const DMA_BLOCK_SIZE: usize = 0x1000; 10 | const DMA_BLOCKS: usize = round_up_block(DMA_MEMORY_SIZE as usize); 11 | 12 | const fn round_up_block(s: usize) -> usize { 13 | (s + (DMA_BLOCK_SIZE - 1)) / DMA_BLOCK_SIZE 14 | } 15 | 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 17 | enum BlockState { 18 | Free, 19 | Used, 20 | } 21 | pub struct Allocator { 22 | /// Blocks 23 | blocks: [BlockState; DMA_BLOCKS], 24 | } 25 | 26 | impl Allocator { 27 | /// Unsafe, as the caller is responsibe that this is not intialized multiple times 28 | unsafe fn new() -> Self { 29 | Self { 30 | blocks: [BlockState::Free; DMA_BLOCKS], 31 | } 32 | } 33 | 34 | pub fn allocate(&mut self, size: usize) -> DMARegion { 35 | assert!(size != 0); 36 | 37 | let size_blocks = round_up_block(size) as usize; 38 | 39 | if size_blocks > self.blocks.len() { 40 | panic!("Not enough of DMA memory"); 41 | } 42 | 43 | 'outer: for start in 0..(self.blocks.len() - size_blocks) { 44 | for offset in 0..size_blocks { 45 | if self.blocks[start + offset] != BlockState::Free { 46 | continue 'outer; 47 | } 48 | } 49 | 50 | for offset in 0..size_blocks { 51 | self.blocks[start + offset] = BlockState::Used; 52 | } 53 | 54 | return DMARegion { 55 | start: DMA_MEMORY_START + start * DMA_BLOCK_SIZE, 56 | size_blocks, 57 | }; 58 | } 59 | 60 | panic!("Out of DMA memory"); 61 | } 62 | 63 | pub fn free(&mut self, _region: DMARegion) { 64 | todo!() 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 69 | pub struct DMARegion { 70 | pub start: PhysAddr, 71 | size_blocks: usize, 72 | } 73 | 74 | lazy_static::lazy_static! { 75 | pub static ref DMA_ALLOCATOR: Mutex = Mutex::new(unsafe{Allocator::new()}); 76 | } 77 | -------------------------------------------------------------------------------- /src/memory/allocators/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dma_allocator; 2 | pub mod stack_allocator; 3 | pub mod syscall_stack; 4 | 5 | pub use self::stack_allocator::Stack; 6 | -------------------------------------------------------------------------------- /src/memory/allocators/stack_allocator.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::Layout; 2 | use x86_64::structures::paging as pg; 3 | use x86_64::structures::paging::page_table::PageTableFlags as Flags; 4 | 5 | use crate::memory::{paging::PAGE_MAP, phys, prelude::*, virt}; 6 | 7 | #[derive(Debug)] 8 | pub struct Stack { 9 | pub top: VirtAddr, 10 | pub bottom: VirtAddr, 11 | } 12 | 13 | impl Stack { 14 | fn new(top: VirtAddr, bottom: VirtAddr) -> Stack { 15 | assert!(top > bottom); 16 | Stack { top, bottom } 17 | } 18 | } 19 | 20 | /// Allocates a new stack, including a guard page, and maps it. 21 | /// Requires that the kernel page table is active. 22 | /// The stacks allocated by this can never be deallocated. 23 | pub fn alloc_stack(size_in_pages: usize) -> Stack { 24 | assert!(size_in_pages > 0); 25 | // Allocate virtual addresses 26 | let v = virt::allocate(size_in_pages + 1); 27 | 28 | // Allocate and map the physical frames (not the guard page) 29 | let start_page = Page::from_start_address(v.start + PAGE_SIZE_BYTES).unwrap(); 30 | let end_page = 31 | Page::from_start_address(v.start + size_in_pages * PAGE_SIZE_BYTES as usize).unwrap(); 32 | 33 | let mut page_map = PAGE_MAP.lock(); 34 | for page in Page::range_inclusive(start_page, end_page) { 35 | let frame = phys::allocate(PAGE_LAYOUT) 36 | .expect("Could not allocate stack frame") 37 | .leak(); 38 | 39 | unsafe { 40 | page_map 41 | .map_to( 42 | PT_VADDR, 43 | page, 44 | PhysFrame::from_start_address_unchecked(frame.start()), 45 | Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, 46 | ) 47 | .flush(); 48 | } 49 | } 50 | 51 | // Create a new stack 52 | Stack::new( 53 | end_page.start_address() + Page::SIZE, 54 | start_page.start_address(), 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/memory/allocators/syscall_stack.rs: -------------------------------------------------------------------------------- 1 | //! Kernel stack for system calls 2 | 3 | use x86_64::structures::paging::PageTableFlags as Flags; 4 | 5 | use crate::memory::{paging::PAGE_MAP, phys, prelude::*, SYSCALL_STACK}; 6 | 7 | /// Creates and maps the system call stack. 8 | /// There is no need to zero the memory, as it will not be read, 9 | /// and it is inaccessible for user processes. 10 | pub unsafe fn init() { 11 | let frame = phys::allocate(PAGE_LAYOUT) 12 | .expect("Could not allocate frame") 13 | .leak(); 14 | 15 | let mut page_map = PAGE_MAP.try_lock().unwrap(); 16 | page_map 17 | .map_to( 18 | PT_VADDR, 19 | Page::from_start_address(SYSCALL_STACK).unwrap(), 20 | PhysFrame::from_start_address_unchecked(frame.start()), 21 | Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, 22 | ) 23 | .flush(); 24 | } 25 | -------------------------------------------------------------------------------- /src/memory/constants.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unreadable_literal)] 2 | //! Constants memory-related things 3 | 4 | use super::prelude::*; 5 | 6 | // Autogenerated part 7 | include!("../../build/constants.rs"); 8 | 9 | pub const HEAP_START: u64 = 0x4000_0000; // At 1 GiB 10 | pub const HEAP_SIZE: u64 = 0x640_0000; // 100 MiB heap 11 | -------------------------------------------------------------------------------- /src/memory/paging/mod.rs: -------------------------------------------------------------------------------- 1 | use spin::Mutex; 2 | use x86_64::registers::control::{Cr0, Cr0Flags}; 3 | use x86_64::registers::model_specific::{Efer, EferFlags}; 4 | use x86_64::structures::paging as pg; 5 | use x86_64::structures::paging::PageTableFlags as Flags; 6 | use x86_64::PhysAddr; 7 | 8 | use crate::driver::vga_buffer::VGA_BUFFER_PHYSADDR; 9 | use crate::interrupt::idt; 10 | use crate::util::elf_parser::ELFData; 11 | 12 | use super::prelude::*; 13 | use super::{constants, Page, PhysFrame}; 14 | 15 | mod mapper; 16 | 17 | pub use self::mapper::PageMap; 18 | 19 | pub unsafe fn enable_nxe() { 20 | Efer::update(|flags| flags.set(EferFlags::NO_EXECUTE_ENABLE, true)) 21 | } 22 | 23 | pub unsafe fn enable_write_protection() { 24 | Cr0::update(|flags| { 25 | flags.set(Cr0Flags::WRITE_PROTECT, true); 26 | }) 27 | } 28 | 29 | pub unsafe fn set_active_table(p4_addr: PhysAddr) { 30 | use x86_64::registers::control::{Cr3, Cr3Flags}; 31 | Cr3::write( 32 | pg::PhysFrame::::from_start_address(p4_addr).expect("Misaligned P4"), 33 | Cr3Flags::empty(), 34 | ); 35 | } 36 | 37 | /// Remap kernel and other necessary memory areas 38 | #[must_use] 39 | pub unsafe fn init(elf_metadata: ELFData) { 40 | log::debug!("Remapping kernel..."); 41 | 42 | // Create new page table 43 | let mut new_table = unsafe { PageMap::init(PT_VADDR, PT_PADDR, PT_VADDR) }; 44 | 45 | // Kernel code and data segments 46 | new_table.identity_map_elf(PT_VADDR, elf_metadata); 47 | 48 | // Identity map IDT, GDT, DMA buffers, and the VGA text buffer 49 | let idt_frame = PhysFrame::containing_address(PhysAddr::new(idt::ADDRESS as u64)); 50 | let vga_buffer_frame = PhysFrame::containing_address(VGA_BUFFER_PHYSADDR); 51 | assert_eq!(idt_frame, vga_buffer_frame); 52 | 53 | let dma_start = PhysFrame::containing_address(DMA_MEMORY_START); 54 | let dma_end = PhysFrame::containing_address(DMA_MEMORY_START + DMA_MEMORY_SIZE); 55 | assert_eq!(dma_start, dma_end); 56 | 57 | assert_eq!(idt_frame, dma_start); 58 | let lowmem_frame = idt_frame; 59 | 60 | unsafe { 61 | new_table 62 | .identity_map(PT_VADDR, lowmem_frame, Flags::PRESENT | Flags::WRITABLE) 63 | .ignore(); 64 | } 65 | 66 | log::debug!("Switching to new table..."); 67 | unsafe { 68 | new_table.activate(); 69 | } 70 | log::debug!("Switch done."); 71 | let mut pm = PAGE_MAP.try_lock().expect("Already locked"); 72 | *pm = new_table; 73 | } 74 | 75 | pub static PAGE_MAP: Mutex = Mutex::new(PageMap::DUMMY); 76 | -------------------------------------------------------------------------------- /src/memory/phys/mod.rs: -------------------------------------------------------------------------------- 1 | //! Kernel heap 2 | 3 | use core::alloc::Layout; 4 | use x86_64::{PhysAddr, VirtAddr}; 5 | 6 | use super::{area::PhysMemoryRange, phys_to_virt}; 7 | 8 | mod allocator; 9 | mod list; 10 | 11 | pub use self::allocator::*; 12 | pub use self::list::AllocationSet; 13 | 14 | /// A freeable allocation 15 | #[derive(Debug)] 16 | pub struct Allocation { 17 | pub(super) start: PhysAddr, 18 | pub(super) layout: Layout, 19 | } 20 | 21 | impl Drop for Allocation { 22 | fn drop(&mut self) { 23 | log::trace!("Drop-deallocate {:?}", self); 24 | 25 | // Safety: will only be called once 26 | unsafe { 27 | _deallocate(self); 28 | } 29 | 30 | let zero = PhysAddr::zero(); 31 | let old = core::mem::replace(&mut self.start, zero); 32 | if old == zero { 33 | panic!("Douple-drop!"); 34 | } 35 | } 36 | } 37 | 38 | impl Allocation { 39 | /// Leaks this allocation, making it impossible to deallocate 40 | pub fn leak(self) -> PhysMemoryRange { 41 | let result = PhysMemoryRange::range(self.start..self.start + self.layout.size()); 42 | core::mem::forget(self); 43 | result 44 | } 45 | 46 | pub unsafe fn phys_start(&self) -> PhysAddr { 47 | self.start 48 | } 49 | 50 | pub unsafe fn mapped_start(&self) -> VirtAddr { 51 | unsafe { phys_to_virt(self.start) } 52 | } 53 | 54 | pub unsafe fn from_mapped(start: *mut u8, layout: Layout) -> Self { 55 | Self { 56 | start: PhysAddr::new_unchecked(undo_offset_ptr(start) as u64), 57 | layout, 58 | } 59 | } 60 | 61 | pub fn size(&self) -> usize { 62 | self.layout.size() 63 | } 64 | 65 | pub fn read(&self) -> &[u8] { 66 | // Safety: tied to the lifetime of self 67 | unsafe { 68 | core::slice::from_raw_parts( 69 | self.mapped_start().as_ptr_unchecked(), 70 | self.layout.size() as usize, 71 | ) 72 | } 73 | } 74 | 75 | pub fn write(&mut self) -> &mut [u8] { 76 | // Safety: requires exclusive access of self, tied lifetime 77 | unsafe { 78 | core::slice::from_raw_parts_mut( 79 | self.mapped_start().as_mut_ptr_unchecked(), 80 | self.layout.size() as usize, 81 | ) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/memory/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use core::alloc::Layout; 2 | 3 | // Trait re-export 4 | pub use core::alloc::Allocator; 5 | 6 | use x86_64::structures::paging as pg; 7 | pub use x86_64::structures::paging::PageSize; 8 | 9 | pub use super::constants::*; 10 | pub use super::utils::*; 11 | 12 | // Address types 13 | pub use x86_64::{PhysAddr, VirtAddr}; 14 | 15 | // Page size 16 | pub type PageSizeType = pg::Size2MiB; 17 | pub const PAGE_SIZE_BYTES: u64 = 0x200_000; 18 | 19 | pub const MIN_PAGE_SIZE_BYTES: u64 = 0x1000; 20 | 21 | pub const MIN_PAGE_LAYOUT: Layout = unsafe { 22 | Layout::from_size_align_unchecked(MIN_PAGE_SIZE_BYTES as usize, MIN_PAGE_SIZE_BYTES as usize) 23 | }; 24 | pub const PAGE_LAYOUT: Layout = unsafe { 25 | Layout::from_size_align_unchecked(PAGE_SIZE_BYTES as usize, PAGE_SIZE_BYTES as usize) 26 | }; 27 | 28 | /// Convert bytes to pages, rounding up 29 | pub const fn to_pages_round_up(bytes: u64) -> u64 { 30 | (bytes + (PAGE_SIZE_BYTES - 1)) / PAGE_SIZE_BYTES 31 | } 32 | 33 | /// Page-align, roungin upwards 34 | pub const fn page_align_up(bytes: u64) -> u64 { 35 | to_pages_round_up(bytes) * PAGE_SIZE_BYTES 36 | } 37 | 38 | pub type Page = pg::Page; 39 | pub type PageRange = pg::page::PageRange; 40 | pub type PhysFrame = pg::PhysFrame; 41 | pub type PhysFrameRange = pg::frame::PhysFrameRange; 42 | pub type PhysFrameRangeInclusive = pg::frame::PhysFrameRangeInclusive; 43 | trait Mapper = pg::Mapper; 44 | // pub trait FrameAllocator = pg::FrameAllocator; 45 | pub use x86_64::structures::paging::FrameAllocator; 46 | 47 | /// Numeric value of `PT_PADDR` for static assertions 48 | pub const PT_PADDR_INT: u64 = 0x1000_0000; 49 | 50 | /// Physical address of the page table area 51 | /// This pointer itself points to P4 table. 52 | pub const PT_PADDR: PhysAddr = unsafe { PhysAddr::new_unchecked(PT_PADDR_INT) }; 53 | 54 | // Require P2 alignment 55 | static_assertions::const_assert!(PT_PADDR_INT % 0x1_000_000 == 0); 56 | 57 | /// Numeric value of `PT_VADDR` for static assertions 58 | pub const PT_VADDR_INT: u64 = 0x10_000_000; 59 | 60 | /// Page tables are mapped starting from this virtual address. 61 | /// This pointer itself points to P4 table. 62 | pub const PT_VADDR: VirtAddr = unsafe { VirtAddr::new_unsafe(PT_VADDR_INT) }; 63 | 64 | // Require P2 alignment 65 | static_assertions::const_assert!(PT_VADDR_INT % 0x1_000_000 == 0); 66 | 67 | /// Size of 2MiB huge page, in bytes 68 | pub const HUGE_PAGE_SIZE: u64 = 0x200_000; 69 | -------------------------------------------------------------------------------- /src/memory/process_common_code.rs: -------------------------------------------------------------------------------- 1 | use core::intrinsics::copy_nonoverlapping; 2 | use core::ptr; 3 | use x86_64::structures::paging::PageTableFlags as Flags; 4 | 5 | use crate::memory::{self, paging::PAGE_MAP, phys, phys_to_virt, prelude::*}; 6 | 7 | pub static mut COMMON_ADDRESS_PHYS: u64 = 0; // Temp value 8 | pub const COMMON_ADDRESS_VIRT: u64 = 0x20_0000; 9 | 10 | pub static mut PROCESS_IDT_PHYS_ADDR: u64 = 0; // Temp value 11 | 12 | unsafe fn load_common_code() { 13 | let common_addr = VirtAddr::new_unsafe(COMMON_ADDRESS_VIRT); 14 | 15 | let bytes = crate::initrd::read("p_commoncode").expect("p_commoncode missing from initrd"); 16 | assert!(bytes.len() <= (PAGE_SIZE_BYTES as usize)); 17 | 18 | let frame_backing = phys::allocate(PAGE_LAYOUT) 19 | .expect("Could not allocate frame") 20 | .leak(); 21 | 22 | let frame = PhysFrame::from_start_address(frame_backing.start()).unwrap(); 23 | 24 | let mut page_map = PAGE_MAP.try_lock().unwrap(); 25 | page_map 26 | .map_to( 27 | PT_VADDR, 28 | Page::from_start_address(common_addr).unwrap(), 29 | frame, 30 | Flags::PRESENT | Flags::WRITABLE | Flags::NO_EXECUTE, 31 | ) 32 | .flush(); 33 | 34 | let base: *mut u8 = common_addr.as_mut_ptr(); 35 | copy_nonoverlapping(bytes.as_ptr(), base, bytes.len()); 36 | 37 | page_map 38 | .map_to( 39 | PT_VADDR, 40 | Page::from_start_address(common_addr).unwrap(), 41 | frame, 42 | Flags::PRESENT, 43 | ) 44 | .flush(); 45 | 46 | COMMON_ADDRESS_PHYS = frame.start_address().as_u64(); 47 | } 48 | 49 | /// Create process descriptor tables 50 | /// These are shared for all processes 51 | unsafe fn create_process_dts() { 52 | use crate::interrupt::write_process_dts; 53 | 54 | // Find process_interrupt.table_start 55 | let p = COMMON_ADDRESS_VIRT as *const u64; 56 | let interrupt_table_start = VirtAddr::new_unsafe(ptr::read(p.offset(1))); 57 | 58 | // Allocate memory 59 | let paddr = phys::allocate(PAGE_LAYOUT) 60 | .expect("Could not allocate frame") 61 | .leak() 62 | .start(); 63 | 64 | PROCESS_IDT_PHYS_ADDR = paddr.as_u64(); 65 | 66 | write_process_dts(phys_to_virt(paddr), interrupt_table_start); 67 | } 68 | 69 | /// Must be called when disk driver (and staticfs) are available 70 | pub unsafe fn init() { 71 | load_common_code(); 72 | create_process_dts(); 73 | } 74 | -------------------------------------------------------------------------------- /src/memory/utils.rs: -------------------------------------------------------------------------------- 1 | use x86_64::{PhysAddr, VirtAddr}; 2 | 3 | use super::prelude::*; 4 | 5 | pub trait EitherAddr { 6 | fn as_u64(self) -> u64; 7 | fn from_u64(_: u64) -> Self; 8 | } 9 | impl EitherAddr for PhysAddr { 10 | fn as_u64(self) -> u64 { 11 | self.as_u64() 12 | } 13 | fn from_u64(addr: u64) -> Self { 14 | PhysAddr::new(addr) 15 | } 16 | } 17 | impl EitherAddr for VirtAddr { 18 | fn as_u64(self) -> u64 { 19 | self.as_u64() 20 | } 21 | fn from_u64(addr: u64) -> Self { 22 | VirtAddr::new(addr) 23 | } 24 | } 25 | 26 | /// Align up or down to page size 27 | pub fn page_align(address: T, upwards: bool) -> T { 28 | T::from_u64(page_align_u64(address.as_u64(), upwards)) 29 | } 30 | 31 | /// Align up or down to page size 32 | pub fn page_align_u64(address: u64, upwards: bool) -> u64 { 33 | if address % Page::SIZE == 0 { 34 | address 35 | } else if upwards { 36 | address + Page::SIZE - address % Page::SIZE 37 | } else { 38 | address - address % Page::SIZE 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/memory/virt/allocator.rs: -------------------------------------------------------------------------------- 1 | //! A first-fit virtual memory allocator 2 | 3 | use alloc::vec::Vec; 4 | use spin::Mutex; 5 | use x86_64::VirtAddr; 6 | 7 | use super::super::prelude::*; 8 | 9 | const START_ADDR: VirtAddr = unsafe { VirtAddr::new_unsafe(0x100_000_000) }; 10 | const END_ADDR: VirtAddr = unsafe { VirtAddr::new_unsafe(0x200_000_000) }; 11 | const SIZE: u64 = END_ADDR.as_u64() - START_ADDR.as_u64(); 12 | 13 | lazy_static::lazy_static! { 14 | static ref FREE_LIST: Mutex> = Mutex::new(vec![Block { 15 | start: START_ADDR, 16 | end: END_ADDR, 17 | }]); 18 | } 19 | 20 | /// Allocate contiguous virtual address block 21 | pub(super) fn allocate(size_pages: u64) -> VirtAddr { 22 | let mut free_blocks = FREE_LIST.lock(); 23 | 24 | let mut i = 0; 25 | while i < free_blocks.len() { 26 | let block_size = free_blocks[i].size_pages(); 27 | if block_size >= size_pages { 28 | return if block_size == size_pages { 29 | free_blocks.remove(i).start 30 | } else { 31 | let start = free_blocks[i].start; 32 | free_blocks[i].start += size_pages * PAGE_SIZE_BYTES; 33 | start 34 | }; 35 | } 36 | i += 1; 37 | } 38 | panic!("Out of virtual memory"); 39 | } 40 | 41 | pub(super) fn free(start: VirtAddr, size_pages: u64) { 42 | let mut free_blocks = FREE_LIST.lock(); 43 | 44 | let index = match free_blocks.binary_search_by(|block| block.start.cmp(&start)) { 45 | Ok(_) => { 46 | panic!("VirtAlloc: Double free: {:?} ({} pages)", start, size_pages); 47 | }, 48 | Err(i) => i, 49 | }; 50 | 51 | // TODO: check for overlapping regions and report errors 52 | 53 | let end: VirtAddr = start + size_pages * PAGE_SIZE_BYTES; 54 | 55 | if index > 0 && free_blocks[index - 1].end == start { 56 | free_blocks[index - 1].end += size_pages * PAGE_SIZE_BYTES; 57 | } else if index < free_blocks.len() && free_blocks[index].start == end { 58 | free_blocks[index].start = start; 59 | } else { 60 | free_blocks.insert(index, Block { start, end }); 61 | } 62 | } 63 | 64 | /// Range start..end, never empty 65 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 66 | struct Block { 67 | start: VirtAddr, 68 | end: VirtAddr, 69 | } 70 | impl Block { 71 | #[inline] 72 | pub fn size_bytes(&self) -> u64 { 73 | self.end.as_u64() - self.start.as_u64() 74 | } 75 | 76 | #[inline] 77 | pub fn size_pages(&self) -> u64 { 78 | self.size_bytes() / PAGE_SIZE_BYTES 79 | } 80 | 81 | #[inline] 82 | pub fn page_starts(&self) -> impl Iterator { 83 | (self.start.as_u64()..self.end.as_u64()) 84 | .step_by(PAGE_SIZE_BYTES as usize) 85 | .map(VirtAddr::new) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/memory/virt/mod.rs: -------------------------------------------------------------------------------- 1 | use x86_64::VirtAddr; 2 | 3 | use super::prelude::*; 4 | 5 | mod allocator; 6 | 7 | /// Allocate a contiguous virtual address block 8 | pub fn allocate(size_pages: usize) -> Allocation { 9 | let start = allocator::allocate(size_pages as u64); 10 | Allocation { 11 | start, 12 | end: start + size_pages * (PAGE_SIZE_BYTES as usize), 13 | } 14 | } 15 | 16 | /// An owned virtual address space allocation, freed on drop 17 | #[derive(Debug, PartialEq, Eq)] 18 | pub struct Allocation { 19 | pub start: VirtAddr, 20 | pub end: VirtAddr, 21 | } 22 | 23 | impl Drop for Allocation { 24 | fn drop(&mut self) { 25 | allocator::free(self.start, self.size_pages()) 26 | } 27 | } 28 | 29 | impl Allocation { 30 | #[inline] 31 | pub fn size_bytes(&self) -> u64 { 32 | self.end.as_u64() - self.start.as_u64() 33 | } 34 | 35 | #[inline] 36 | pub fn size_pages(&self) -> u64 { 37 | self.size_bytes() / PAGE_SIZE_BYTES 38 | } 39 | 40 | #[inline] 41 | pub fn page_starts(&self) -> impl Iterator { 42 | (self.start.as_u64()..self.end.as_u64()) 43 | .step_by(PAGE_SIZE_BYTES as usize) 44 | .map(VirtAddr::new) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/multitasking/elf_loader.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::ptr; 3 | 4 | use crate::memory::phys::OutOfMemory; 5 | use crate::memory::{self, phys, prelude::*, Page}; 6 | use crate::util::elf_parser::*; 7 | 8 | /// A loaded and validated elf image 9 | #[derive(Debug)] 10 | pub struct ElfImage { 11 | pub(super) header: ELFHeader, 12 | pub(super) sections: Vec<(ELFProgramHeader, Vec)>, 13 | } 14 | 15 | /// Loads a program from ELF ímage to physical memory. 16 | /// This function does not load the ELF to its p_vaddr, but 17 | /// rather returns a list of unmapped physical frames. 18 | /// 19 | /// This function internally uses TLB flushes. 20 | /// 21 | /// Requires that the kernel page tables are active. 22 | pub fn load_elf(image: &[u8]) -> Result { 23 | let elf = unsafe { 24 | parse_elf(image).expect("Invalid ELF image") // TODO: return error 25 | }; 26 | 27 | let mut frames = Vec::new(); 28 | for ph in elf.ph_table.iter().filter_map(|x| *x) { 29 | if ph.loadable() && ph.size_in_memory != 0 { 30 | let size_in_pages = page_align_u64(ph.size_in_memory, true) / PAGE_SIZE_BYTES; 31 | let mut section_frames = Vec::new(); 32 | for _ in 0..size_in_pages { 33 | let mut allocation = phys::allocate_zeroed(PAGE_LAYOUT)?; 34 | let area = allocation.write(); 35 | 36 | // Copy p_filesz bytes from p_offset to target 37 | let start = ph.offset as usize; 38 | let size = ph.size_in_file as usize; 39 | area[..size].copy_from_slice(&image[start..start + size]); 40 | 41 | section_frames.push(allocation); 42 | } 43 | 44 | // Append frames to the result 45 | frames.push((ph, section_frames)); 46 | } 47 | } 48 | 49 | Ok(ElfImage { 50 | header: elf.header, 51 | sections: frames, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/multitasking/mod.rs: -------------------------------------------------------------------------------- 1 | mod elf_loader; 2 | pub mod process; 3 | mod queues; 4 | mod scheduler; 5 | mod waitfor; 6 | 7 | pub use self::elf_loader::{load_elf, ElfImage}; 8 | pub use self::process::{Process, ProcessId}; 9 | pub use self::scheduler::{ProcessSwitch, Scheduler, SCHEDULER, SCHEDULER_ENABLED}; 10 | pub use self::waitfor::{ExplicitEventId, WaitFor}; 11 | -------------------------------------------------------------------------------- /src/services/initrd.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use d7abi::process::ProcessId; 3 | 4 | use crate::ipc::{DeliveryError, IpcResult, Manager, Message, Topic}; 5 | 6 | pub fn read(manager: &mut Manager, pid: ProcessId, message: Message) -> Result<(), DeliveryError> { 7 | let (reply_to, path): (String, String) = pinecone::from_bytes(&message.data) 8 | .expect("Invalid message: TODO: just reply client error"); 9 | 10 | let reply_to = Topic::new(&reply_to).ok_or_else(|| { 11 | log::warn!("Invalid reply_to topic name from {:?}", pid); 12 | DeliveryError::NegativeAcknowledgement 13 | })?; 14 | 15 | let data = crate::initrd::read(&path).ok_or_else(|| { 16 | log::warn!("Missing initrd {} file requested by {:?}", path, pid); 17 | DeliveryError::NegativeAcknowledgement 18 | })?; 19 | 20 | manager.kernel_deliver_reply(reply_to, data) 21 | } 22 | -------------------------------------------------------------------------------- /src/services/mod.rs: -------------------------------------------------------------------------------- 1 | //! IPC-accessible services hosted from the kernel for technical reasons. 2 | //! Only reliable connections accepted. 3 | 4 | use alloc::vec::Vec; 5 | use hashbrown::HashMap; 6 | use serde::Serialize; 7 | use spin::Mutex; 8 | 9 | use d7abi::process::ProcessId; 10 | 11 | use crate::ipc::{ 12 | AcknowledgeId, DeliveryError, IpcResult, Manager, Message, SubscriptionId, TopicFilter, IPC, 13 | }; 14 | 15 | mod initrd; 16 | 17 | pub fn init() { 18 | register_exact("initrd/read", initrd::read); 19 | } 20 | 21 | fn register(filter: TopicFilter, service: Service) { 22 | let mut ipc_manager = IPC.try_lock().unwrap(); 23 | let sub = ipc_manager 24 | .kernel_subscribe(filter) 25 | .expect("Could not register a service"); 26 | let mut services = SERVICES.try_lock().unwrap(); 27 | services.insert(sub, service); 28 | } 29 | 30 | fn register_exact(filter: &str, service: Service) { 31 | register( 32 | TopicFilter::try_new(filter, true).expect("Invalid filter"), 33 | service, 34 | ) 35 | } 36 | 37 | fn register_prefix(filter: &str, service: Service) { 38 | register( 39 | TopicFilter::try_new(filter, false).expect("Invalid filter"), 40 | service, 41 | ) 42 | } 43 | 44 | /// For now, scheduler and ipc are unavailable for services 45 | type Service = fn(&mut Manager, ProcessId, Message) -> Result<(), DeliveryError>; 46 | 47 | lazy_static::lazy_static! { 48 | static ref SERVICES: Mutex> = Mutex::new(HashMap::new()); 49 | } 50 | 51 | /// Return value used as the deliver/acknowledgement result 52 | pub fn incoming( 53 | manager: &mut Manager, pid: ProcessId, sub: SubscriptionId, mut message: Message, 54 | ) -> IpcResult<()> { 55 | let mut services = SERVICES.try_lock().unwrap(); 56 | let service = services 57 | .get_mut(&sub) 58 | .expect("No such subscription for the kernel services"); 59 | 60 | let _ack_id = message 61 | .ack_id 62 | .take() 63 | .expect("Incoming messages must be reliable"); 64 | 65 | IpcResult::new(service(manager, pid, message).map_err(|e| e.into())) 66 | } 67 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | //! Kernel-provided signatures. 2 | //! We expose a new submodule for each use case, so arguments 3 | //! can be typed correctly and given separate context. 4 | 5 | use crate::random; 6 | 7 | use spin::Once; 8 | 9 | use ed25519_dalek::Keypair; 10 | 11 | static KEYPAIR: Once = Once::new(); 12 | 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 14 | pub struct InvalidSignature; 15 | 16 | #[cfg(not(test))] 17 | #[allow(const_item_mutation)] 18 | fn get_keypair() -> &'static Keypair { 19 | KEYPAIR.call_once(|| Keypair::generate(&mut random::KRNG)) 20 | } 21 | 22 | #[cfg(test)] 23 | fn get_keypair() -> &'static Keypair { 24 | use rand::rngs::OsRng; 25 | let mut csprng = OsRng {}; 26 | KEYPAIR.call_once(|| Keypair::generate(&mut csprng)) 27 | } 28 | 29 | pub mod capability { 30 | use ed25519_dalek::{Digest, Sha512, Signature}; 31 | 32 | use super::{get_keypair, InvalidSignature}; 33 | 34 | const SIGNATURE_CONTEXT: &[u8] = b"d7os-kernel-capability"; 35 | 36 | pub fn sign(pid: u64, cap: u64) -> Signature { 37 | let mut prehashed: Sha512 = Sha512::new(); 38 | prehashed.update(&pid.to_le_bytes()); 39 | prehashed.update(&cap.to_le_bytes()); 40 | get_keypair() 41 | .sign_prehashed(prehashed, Some(SIGNATURE_CONTEXT)) 42 | .expect("Signing failed") 43 | } 44 | 45 | pub fn verify(pid: u64, cap: u64, signature: &Signature) -> Result<(), InvalidSignature> { 46 | let mut prehashed: Sha512 = Sha512::new(); 47 | prehashed.update(&pid.to_le_bytes()); 48 | prehashed.update(&cap.to_le_bytes()); 49 | get_keypair() 50 | .verify_prehashed(prehashed, Some(SIGNATURE_CONTEXT), signature) 51 | .map_err(|_| InvalidSignature) 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use ed25519_dalek::Signature; 58 | 59 | use super::capability; 60 | 61 | #[test] 62 | fn test_signature() { 63 | let s = capability::sign(5, 10); 64 | println!("{:?}", s); 65 | capability::verify(5, 10, &s).expect("Verify"); 66 | assert!(capability::verify(6, 10, &s).is_err()); 67 | 68 | let mut bytes = s.to_bytes(); 69 | bytes[0] = bytes[0].wrapping_add(1); 70 | let s2 = Signature::new(bytes); 71 | 72 | assert!(capability::verify(6, 10, &s2).is_err()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/smp/data.rs: -------------------------------------------------------------------------------- 1 | //! Data structures for working with multiple processors 2 | 3 | use alloc::vec::Vec; 4 | 5 | use super::{cpu_count, current_processor_id}; 6 | 7 | /// Stores data so that each CPU accesses their own version of it 8 | pub struct PerCpu { 9 | /// Invariant: fixed size after first allocation 10 | values: Vec>, 11 | } 12 | impl PerCpu { 13 | pub const fn new() -> Self { 14 | Self { values: Vec::new() } 15 | } 16 | 17 | fn initialized(&self) -> bool { 18 | self.values.capacity() != 0 19 | } 20 | 21 | fn initialize(&mut self) { 22 | self.values.reserve_exact(cpu_count() as usize); 23 | } 24 | 25 | pub fn set(&mut self, value: T) { 26 | if !self.initialized() { 27 | self.initialize(); 28 | } 29 | 30 | unsafe { 31 | let v = self 32 | .values 33 | .get_unchecked_mut(current_processor_id().0 as usize); 34 | *v = Some(value); 35 | } 36 | } 37 | 38 | pub fn get(&self) -> Option<&T> { 39 | if self.initialized() { 40 | unsafe { 41 | self.values 42 | .get_unchecked(current_processor_id().0 as usize) 43 | .as_ref() 44 | } 45 | } else { 46 | None 47 | } 48 | } 49 | 50 | pub fn get_mut(&mut self) -> Option<&mut T> { 51 | if self.initialized() { 52 | unsafe { 53 | self.values 54 | .get_unchecked_mut(current_processor_id().0 as usize) 55 | .as_mut() 56 | } 57 | } else { 58 | None 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | //! Time is measured using TSC on the BSP core. 2 | //! As the TSC might roll over in 10 years or so from boot, 3 | //! it might be good to reset TSC to zero when it's near wraparound, 4 | //! and then increment some global epoch variable. 5 | //! 6 | //! Other cores should very rarely need access to this time. 7 | //! The BSP core is the only core that moves tasks out of the sleep queue, 8 | //! so schduler times are stored in (future) TSC timestamps of the BSP. 9 | 10 | use crate::driver::tsc; 11 | use crate::smp::is_bsp; 12 | 13 | use core::time::Duration; 14 | 15 | /// Timestamp relative to the TSC of the BSP core. 16 | /// All functions are requiring read access to the TSC 17 | /// are only accessible on the BSP core and panic otherwise. 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 19 | pub struct BSPInstant(u64); 20 | 21 | impl BSPInstant { 22 | pub fn now() -> Self { 23 | assert!(is_bsp(), "BSPInstant is only usable from the BSP core"); 24 | Self(tsc::read()) 25 | } 26 | 27 | pub fn tsc_value(self) -> u64 { 28 | self.0 29 | } 30 | 31 | pub fn add_ticks(self, ticks: u64) -> Self { 32 | Self(self.0 + ticks) 33 | } 34 | 35 | pub fn add_ns(self, ns: u64) -> Self { 36 | Self(self.0 + crate::smp::sleep::ns_to_ticks(ns)) 37 | } 38 | 39 | pub fn try_ticks_from(self, earlier: BSPInstant) -> Option { 40 | if earlier <= self { 41 | Some(self.0 - earlier.0) 42 | } else { 43 | None 44 | } 45 | } 46 | 47 | /// Panics if times in wrong order 48 | pub fn ticks_from(self, earlier: BSPInstant) -> u64 { 49 | self.try_ticks_from(earlier) 50 | .expect("Timestamps in wrong order") 51 | } 52 | 53 | /// Panics if times in wrong order 54 | pub fn duration_from(self, earlier: BSPInstant) -> Duration { 55 | Duration::from_nanos(crate::smp::sleep::ticks_to_ns(self.ticks_from(earlier))) 56 | } 57 | 58 | pub fn ticks_since(self) -> u64 { 59 | Self::now().ticks_from(self) 60 | } 61 | 62 | pub fn duration_since(self) -> Duration { 63 | Self::now().duration_from(self) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod elf_parser; 2 | 3 | use cpuio::Port; 4 | 5 | macro_rules! raw_ptr { 6 | (u8 $ptr:expr ; $offset:expr) => ( 7 | *(($ptr as *const u8).offset($offset / 1)) 8 | ); 9 | (u16 $ptr:expr ; $offset:expr) => ( 10 | *(($ptr as *const u16).offset($offset / 2)) 11 | ); 12 | (u32 $ptr:expr ; $offset:expr) => ( 13 | *(($ptr as *const u32).offset($offset / 4)) 14 | ); 15 | (u64 $ptr:expr ; $offset:expr) => ( 16 | *(($ptr as *const u64).offset($offset / 8)) 17 | ); 18 | 19 | // default to index 0 20 | (u8 $ptr:expr) => ( 21 | raw_ptr!(u8 $ptr; 0x0) 22 | ); 23 | (u16 $ptr:expr) => ( 24 | raw_ptr!(u16 $ptr; 0x0) 25 | ); 26 | (u32 $ptr:expr) => ( 27 | raw_ptr!(u32 $ptr; 0x0) 28 | ); 29 | (u64 $ptr:expr) => ( 30 | raw_ptr!(u64 $ptr; 0x0) 31 | ); 32 | 33 | // default to u8 34 | ($ptr:expr, $offset:expr) => ( 35 | raw_ptr!(u8 $ptr; $offset) 36 | ); 37 | 38 | // default u8 at index 0 39 | ($ptr:expr) => ( 40 | raw_ptr!(u8 $ptr; 0x0) 41 | ); 42 | } 43 | 44 | macro_rules! dump_memory_at { 45 | ($ptr:expr) => (log::trace!("{:x} {:x} {:x} {:x}", raw_ptr!(u16 $ptr; 0), raw_ptr!(u16 $ptr; 2), raw_ptr!(u16 $ptr; 4), raw_ptr!(u16 $ptr; 6))); 46 | } 47 | 48 | macro_rules! int { 49 | ($num:expr) => {{ 50 | ::core::arch::asm!(concat!("int ", stringify!($num)), options(nostack)); 51 | }}; 52 | } 53 | 54 | macro_rules! bochs_magic_bp { 55 | () => {{ 56 | #![allow(unused_unsafe)] 57 | unsafe { 58 | ::core::arch::asm!("xchg bx, bx", options(nostack, nomem)); 59 | }; 60 | }}; 61 | } 62 | 63 | macro_rules! no_interrupts { 64 | ($block:expr) => { 65 | unsafe { 66 | ::core::arch::asm!("cli", options(nostack, nomem)); 67 | } 68 | $block; 69 | unsafe { 70 | ::core::arch::asm!("sti", options(nostack, nomem)); 71 | } 72 | }; 73 | } 74 | 75 | macro_rules! sizeof { 76 | ($t:ty) => {{ ::core::mem::size_of::<$t>() }}; 77 | } 78 | -------------------------------------------------------------------------------- /tools/hex2str.py: -------------------------------------------------------------------------------- 1 | import sys 2 | print "".join(map(lambda q: chr(int(q, 16)), " ".join(sys.argv[1:]).split())) 3 | -------------------------------------------------------------------------------- /tools/image.py: -------------------------------------------------------------------------------- 1 | import os 2 | os.system("open /Users/Hannes/VirtualBox\ VMs/RustOS/Logs/VBox.png") 3 | -------------------------------------------------------------------------------- /tools/logparser.py: -------------------------------------------------------------------------------- 1 | import os, sys, re 2 | class colors: 3 | HEADER = '\033[95m' 4 | OKBLUE = '\033[94m' 5 | OKGREEN = '\033[92m' 6 | WARNING = '\033[93m' 7 | FAIL = '\033[91m' 8 | ENDC = '\033[0m' 9 | BOLD = '\033[1m' 10 | UNDERLINE = '\033[4m' 11 | HIGHLIGHT = ["cafe", "beef", "dead", "feed", "f00d"] 12 | 13 | def apply_colors(s): 14 | res = "" 15 | for i in range(0, len(s), 4): 16 | if s[i:i+4] in HIGHLIGHT: 17 | res += colors.OKGREEN + s[i:i+4] + colors.ENDC 18 | else: 19 | res += s[i:i+4] 20 | return "".join(res) 21 | 22 | homedir=os.popen("echo $HOME").read().strip() 23 | with open(homedir + "/VirtualBox VMs/RustOS/Logs/VBox.log") as fo: 24 | content = fo.read().replace("\r\n", "\n").split("\n") 25 | 26 | ind = 0 27 | errors = True 28 | while not "!"*40 in content[ind]: 29 | ind += 1 30 | if ind > len(content)-1: 31 | print("No errors.") 32 | errors = False 33 | ind = 0 34 | break 35 | if errors: 36 | c=0 37 | while True: 38 | x = " ".join([i for i in content[ind].strip().split()[1:] if not "!!" in i]).strip() 39 | if x: 40 | print x 41 | break 42 | ind += 1 43 | c+=1 44 | if c > 10: 45 | print("Internal script error #1") 46 | sys.exit(2) 47 | while not "Guest state at power off" in content[ind]: 48 | ind += 1 49 | if ind > len(content) - 1: 50 | print("Virtual machine still running.") 51 | sys.exit(2) 52 | 53 | index = ind+2 54 | while not "{" in content[index]: 55 | for register, value in re.findall("([a-zA-Z0-9]+)\\s?=([0-9a-f]+)", content[index].split(" ",1)[1]): 56 | if register == "iopl": 57 | break 58 | print register+(" "*(4-len(register)))+"= "+apply_colors(value) 59 | index += 1 60 | -------------------------------------------------------------------------------- /tools/pseudo_elf_loader.py: -------------------------------------------------------------------------------- 1 | import sys 2 | if sys.version_info[0] != 3: 3 | exit("Py3 required.") 4 | 5 | import ast 6 | 7 | class MockRam(dict): 8 | def __missing__(self, addr): 9 | return None 10 | 11 | def b2i(l): 12 | return sum([a*0x100**i for i,a in enumerate(l)]) 13 | 14 | def i2b(i): 15 | b = [] 16 | while i: 17 | b.append(i%0x100) 18 | i //= 0x100 19 | return b 20 | 21 | 22 | def main(fn, mreq): 23 | with open(fn, "rb") as f: 24 | img = f.read() 25 | 26 | # verify image 27 | print("Verifying...") 28 | assert img[0:4] == bytes([0x7f, 0x45, 0x4c, 0x46]), "magic" 29 | assert img[4] == 0x2, "bitness" 30 | assert img[18] == 0x3e, "instruction set" 31 | assert img[5] == 0x1, "endianess" 32 | assert img[6] == 0x1, "version" 33 | assert img[54] == 0x38, "program header size" 34 | print("Verification ok.\n") 35 | 36 | print("Load point {:#x}".format(b2i(img[24:24+8]))) 37 | pht_pos = b2i(img[32:32+8]) 38 | pht_len = b2i(img[56:56+2]) 39 | print("Program header len={} pos={:#x}".format(pht_len, pht_pos)) 40 | 41 | ptr = pht_pos 42 | 43 | ram = MockRam() 44 | 45 | for index in range(pht_len): 46 | print("Header #{}:".format(index+1)) 47 | segment_type = img[ptr] 48 | if segment_type == 1: 49 | print(" This is a LOAD segment") 50 | 51 | flags = b2i(img[(ptr+4):(ptr+4)+4]) 52 | p_offset = b2i(img[(ptr+8):(ptr+8)+8]) 53 | p_vaddr = b2i(img[(ptr+16):(ptr+16)+8]) 54 | p_filesz = b2i(img[(ptr+32):(ptr+32)+8]) 55 | p_memsz = b2i(img[(ptr+40):(ptr+40)+8]) 56 | 57 | # clear 58 | for i in range(p_memsz): 59 | ram[p_vaddr+i] = 0 60 | 61 | # copy 62 | for i in range(p_filesz): 63 | ram[p_vaddr+i] = img[p_offset+i] 64 | 65 | if p_vaddr+i in mreq: 66 | print("{:#x}->{:#x}: {:#x}".format(p_offset+i, p_vaddr+i, ram[p_vaddr+i])) 67 | 68 | print(" Flags: {} ({:#b})".format("".join([(l*(flags&(1< function_name 13 | 14 | # Read objdump address to assembly mappings 15 | with Popen(["objdump", "-d", "-M", "intel", str(elf_file)], stdout=PIPE) as p: 16 | store_next_addr_as = None 17 | for line in p.stdout: 18 | line = line.strip(b" \t") 19 | columns = line.count(b"\t") 20 | 21 | if columns == 0: # Skip useless lines 22 | continue 23 | 24 | if columns == 1: # Skip second (overflown) line of raw bytes 25 | continue 26 | 27 | addr, _bytes, code = line.split(b"\t") 28 | addr = int(addr[:-1], 16) 29 | 30 | if name := store_next_addr_as: 31 | return_addr_names[addr] = name 32 | store_next_addr_as = False 33 | 34 | if m := re.match(br"call\s+[0-9a-f]+\s+<(.+)>", code): 35 | store_next_addr_as = m.group(1).decode() 36 | 37 | 38 | addrs = [] 39 | for line in sys.stdin: 40 | line = line.strip() 41 | if not line: 42 | break 43 | 44 | # Qemu 45 | if m := re.match(r"[0-9a-f]{16}: 0x([0-9a-f]{16}) 0x([0-9a-f]{16})", line): 46 | addrs.append(int(m.group(1), 16)) 47 | addrs.append(int(m.group(2), 16)) 48 | 49 | # Bochs 50 | elif m := re.match( 51 | r"\| STACK 0x[0-9a-f]{16} \[0x([0-9a-f]{8}):0x([0-9a-f]{8})\]", line 52 | ): 53 | addrs.append(int(m.group(1) + m.group(2), 16)) 54 | 55 | if not addrs: 56 | exit("No addresses read") 57 | 58 | for addr in addrs: 59 | if name := return_addr_names.get(addr): 60 | print(f"{hex(addr)[2:]:16} {name}") 61 | -------------------------------------------------------------------------------- /tools/str2hex.py: -------------------------------------------------------------------------------- 1 | import sys 2 | print("".join(map(lambda q: hex(ord(q))[2:], " ".join(sys.argv[1:]).strip()))) 3 | -------------------------------------------------------------------------------- /tools/zeropad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | import ast 5 | 6 | try: 7 | file_name, size_bytes = sys.argv[1:] 8 | except: 9 | print("Usage: ./{} file size_bytes".format(sys.argv[0])) 10 | sys.exit(1) 11 | 12 | size_bytes = int(ast.literal_eval(size_bytes)) 13 | 14 | file_size = os.path.getsize(file_name) 15 | assert 0 <= file_size <= size_bytes 16 | 17 | with open(file_name, mode="ba") as f: 18 | f.write(bytes([0]*(size_bytes - file_size))) 19 | --------------------------------------------------------------------------------