├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci-docs.yml │ ├── ci-linux.yml │ ├── ci-mac.yml │ ├── ci-windows.yml │ ├── housekeep.yml │ ├── main.yml │ ├── pr-build.yml │ └── pr-update.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CNAME ├── Cargo.lock ├── Cargo.toml ├── Info.plist ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── architecture.svg ├── build.py ├── bundle.icns ├── config ├── Cargo.toml └── src │ ├── env │ ├── mod.rs │ └── vm.rs │ ├── idps │ └── mod.rs │ ├── lib.rs │ └── qa │ └── mod.rs ├── docs ├── building.md ├── img │ └── favicon.ico └── index.md ├── entitlements.plist ├── gui ├── Cargo.toml ├── build.rs ├── obliteration.ico ├── resources.rc ├── src │ ├── data │ │ ├── mod.rs │ │ ├── part.rs │ │ └── prof.rs │ ├── gdb │ │ ├── client.rs │ │ ├── handler.rs │ │ ├── mod.rs │ │ └── state.rs │ ├── graphics │ │ ├── metal │ │ │ ├── engine.rs │ │ │ ├── mod.rs │ │ │ └── window.rs │ │ ├── mod.rs │ │ └── vulkan │ │ │ ├── engine.rs │ │ │ ├── mod.rs │ │ │ └── window.rs │ ├── hw │ │ ├── console │ │ │ ├── context.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── vmm │ │ │ ├── context.rs │ │ │ └── mod.rs │ ├── log │ │ ├── file.rs │ │ └── mod.rs │ ├── main.rs │ ├── panic │ │ └── mod.rs │ ├── profile │ │ ├── cpu.rs │ │ ├── display.rs │ │ └── mod.rs │ ├── settings │ │ └── mod.rs │ ├── setup │ │ ├── linux.rs │ │ ├── macos.rs │ │ ├── mod.rs │ │ └── windows.rs │ ├── ui │ │ ├── backend │ │ │ ├── mod.rs │ │ │ ├── wayland.rs │ │ │ ├── window.rs │ │ │ └── x11.rs │ │ ├── linux │ │ │ ├── dialogs.rs │ │ │ ├── mod.rs │ │ │ ├── modal.rs │ │ │ ├── wayland.rs │ │ │ └── x11.rs │ │ ├── macos │ │ │ ├── dialogs.rs │ │ │ ├── mod.rs │ │ │ ├── modal.rs │ │ │ └── view.rs │ │ ├── mod.rs │ │ ├── profile.rs │ │ └── windows │ │ │ ├── dialogs.rs │ │ │ ├── mod.rs │ │ │ └── modal.rs │ ├── util │ │ ├── channel.rs │ │ └── mod.rs │ ├── vfs │ │ └── mod.rs │ └── vmm │ │ ├── aarch64.rs │ │ ├── cpu │ │ ├── debug.rs │ │ └── mod.rs │ │ ├── kernel │ │ ├── mod.rs │ │ ├── note.rs │ │ └── segment.rs │ │ ├── mod.rs │ │ └── x86_64.rs └── ui │ ├── about.slint │ ├── assets │ ├── cpu-64-bit.svg │ ├── icon.png │ ├── logo.png │ ├── monitor.svg │ ├── slint-logo-small-dark.svg │ └── sony-playstation.svg │ ├── close-octagon-outline.svg │ ├── debug.slint │ ├── error.slint │ ├── lib.slint │ ├── main.slint │ ├── main │ ├── cpu.slint │ ├── display.slint │ ├── idps.slint │ ├── save.svg │ └── start.svg │ ├── new-profile.slint │ ├── settings.slint │ ├── settings │ └── graphics.slint │ ├── setup.slint │ ├── setup │ ├── conclusion.slint │ ├── firmware.slint │ ├── header.slint │ ├── intro.slint │ ├── nav.slint │ └── root.slint │ └── widgets │ └── tab.slint ├── kernel ├── .cargo │ └── config.toml ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── build.rs └── src │ ├── aarch64.rs │ ├── config │ ├── aarch64.rs │ ├── dipsw.rs │ ├── mod.rs │ └── x86_64.rs │ ├── context │ ├── aarch64.rs │ ├── arc.rs │ ├── local.rs │ ├── mod.rs │ └── x86_64.rs │ ├── dmem │ └── mod.rs │ ├── event │ ├── mod.rs │ └── ty.rs │ ├── imgact │ ├── mod.rs │ └── ps4 │ │ ├── abi.rs │ │ └── mod.rs │ ├── imgfmt │ ├── elf │ │ └── mod.rs │ └── mod.rs │ ├── lock │ ├── gutex │ │ ├── guard.rs │ │ └── mod.rs │ ├── mod.rs │ └── mutex │ │ └── mod.rs │ ├── main.rs │ ├── malloc │ ├── mod.rs │ └── vm.rs │ ├── proc │ ├── abi.rs │ ├── cell.rs │ ├── mod.rs │ ├── pid.rs │ ├── process.rs │ └── thread.rs │ ├── sched │ ├── mod.rs │ └── sleep.rs │ ├── signal │ └── mod.rs │ ├── subsystem │ └── mod.rs │ ├── trap │ ├── aarch64.rs │ ├── mod.rs │ ├── vm.rs │ └── x86_64.rs │ ├── uma │ ├── aarch64.rs │ ├── boxed.rs │ ├── bucket.rs │ ├── keg.rs │ ├── mod.rs │ ├── slab.rs │ ├── x86_64.rs │ └── zone.rs │ ├── vm │ ├── mod.rs │ ├── object.rs │ ├── page.rs │ └── stats.rs │ └── x86_64.rs ├── legacy └── src │ ├── arch │ └── mod.rs │ ├── arnd.rs │ ├── budget │ └── mod.rs │ ├── dev │ ├── camera.rs │ ├── dce.rs │ ├── deci.rs │ ├── dipsw.rs │ ├── dmem.rs │ ├── gc.rs │ ├── hid.rs │ ├── hmd.rs │ ├── mod.rs │ ├── random.rs │ ├── rng.rs │ ├── sbl_srv.rs │ └── ttyconsole.rs │ ├── dmem │ ├── blockpool.rs │ └── mod.rs │ ├── ee │ └── mod.rs │ ├── errno.rs │ ├── event │ ├── mod.rs │ └── ty.rs │ ├── fs │ ├── dev │ │ ├── cdev.rs │ │ ├── dirent.rs │ │ ├── mod.rs │ │ └── vnode.rs │ ├── dirent.rs │ ├── file.rs │ ├── host │ │ ├── file.rs │ │ ├── mod.rs │ │ └── vnode.rs │ ├── ioctl.rs │ ├── mod.rs │ ├── mount.rs │ ├── null │ │ ├── hash.rs │ │ ├── mod.rs │ │ └── vnode.rs │ ├── path.rs │ ├── perm.rs │ ├── stat.rs │ ├── tmp │ │ ├── mod.rs │ │ └── node.rs │ ├── uio.rs │ └── vnode.rs │ ├── idt │ ├── entry.rs │ └── mod.rs │ ├── imgact │ ├── mod.rs │ └── orbis │ │ ├── dynamic.rs │ │ ├── info.rs │ │ ├── library.rs │ │ ├── mod.rs │ │ ├── module.rs │ │ ├── program.rs │ │ ├── reloc.rs │ │ ├── symbol.rs │ │ └── ty.rs │ ├── ipmi │ ├── cmd.rs │ └── mod.rs │ ├── kqueue │ └── mod.rs │ ├── main.rs │ ├── namedobj │ └── mod.rs │ ├── net │ ├── mod.rs │ ├── proto │ │ ├── inet │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── unix │ │ │ └── mod.rs │ └── socket.rs │ ├── osem │ └── mod.rs │ ├── process │ ├── active.rs │ ├── appinfo.rs │ ├── binary.rs │ ├── cpuset.rs │ ├── filedesc.rs │ ├── group.rs │ ├── mod.rs │ ├── pcb.rs │ ├── pid.rs │ ├── proc.rs │ ├── rlimit.rs │ ├── session.rs │ ├── signal.rs │ ├── thread.rs │ └── zombie.rs │ ├── rcmgr │ └── mod.rs │ ├── regmgr │ ├── key.rs │ └── mod.rs │ ├── rtld │ ├── mem.rs │ ├── mod.rs │ ├── module.rs │ └── resolver.rs │ ├── sched │ ├── mod.rs │ └── runq.rs │ ├── shm │ └── mod.rs │ ├── signal │ ├── mod.rs │ └── set.rs │ ├── syscalls │ ├── error.rs │ ├── input.rs │ ├── mod.rs │ └── output.rs │ ├── sysctl │ └── mod.rs │ ├── sysent │ └── mod.rs │ ├── time │ └── mod.rs │ ├── ucred │ ├── auth.rs │ ├── id.rs │ ├── mod.rs │ └── privilege.rs │ ├── umtx │ └── mod.rs │ └── vm │ ├── iter.rs │ ├── mod.rs │ ├── page.rs │ ├── stack.rs │ ├── storage.rs │ └── vm.rs ├── lib ├── aarch64 │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ └── src │ │ ├── exception.rs │ │ └── lib.rs ├── bitflag │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── mask.rs ├── hv │ ├── Cargo.toml │ └── src │ │ ├── aarch64.rs │ │ ├── lib.rs │ │ ├── linux │ │ ├── aarch64.rs │ │ ├── cpu.rs │ │ ├── ffi.rs │ │ ├── mod.rs │ │ ├── run.rs │ │ └── x86_64.rs │ │ ├── macos │ │ ├── cpu.rs │ │ ├── mapper.rs │ │ └── mod.rs │ │ ├── ram │ │ ├── aarch64.rs │ │ ├── builder.rs │ │ ├── lock.rs │ │ ├── mapper.rs │ │ ├── mod.rs │ │ ├── unix.rs │ │ ├── windows.rs │ │ └── x86_64.rs │ │ ├── windows │ │ ├── cpu.rs │ │ ├── mod.rs │ │ └── partition.rs │ │ └── x86_64.rs ├── krt │ ├── Cargo.toml │ └── src │ │ ├── config │ │ └── mod.rs │ │ ├── console │ │ ├── mod.rs │ │ └── vm.rs │ │ ├── lib.rs │ │ └── panic │ │ ├── mod.rs │ │ └── vm.rs └── x86-64 │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ └── src │ ├── lib.rs │ ├── msr.rs │ └── segment.rs ├── macros ├── Cargo.toml └── src │ ├── bitflag.rs │ ├── elf.rs │ ├── enum_conversions.rs │ ├── errno.rs │ ├── lib.rs │ └── vpath.rs └── mkdocs.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.svg binary 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | labels: [] 8 | - package-ecosystem: cargo 9 | directory: "/src" 10 | schedule: 11 | interval: daily 12 | labels: [] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci-docs.yml: -------------------------------------------------------------------------------- 1 | name: CI (Docs) 2 | on: 3 | workflow_call: 4 | jobs: 5 | build: 6 | name: Documentation 7 | runs-on: ubuntu-24.04 8 | steps: 9 | - name: Check out repository 10 | uses: actions/checkout@v4 11 | - name: Install system packages 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install -y mkdocs 15 | - name: Disable PEP 668 16 | run: pip config set global.break-system-packages true 17 | - name: Install Python packages 18 | run: pip install mdx_truly_sane_lists 19 | - name: Update Rust 20 | run: rustup update stable 21 | - name: Build MkDocs 22 | run: mkdocs build 23 | - name: Build Kernel APIs 24 | run: | 25 | cargo doc -p obkrnl --all-features --document-private-items --no-deps 26 | mv target/doc site/crates 27 | - name: Upload artifacts 28 | uses: actions/upload-pages-artifact@v3 29 | with: 30 | path: site 31 | -------------------------------------------------------------------------------- /.github/workflows/ci-linux.yml: -------------------------------------------------------------------------------- 1 | name: CI (Linux) 2 | on: 3 | workflow_call: 4 | jobs: 5 | build: 6 | name: Linux 7 | runs-on: ubuntu-22.04 # Lowest version that support Vulkan 1.3. 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v4 11 | - name: Install System Packages 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install -y libvulkan-dev 15 | - name: Update Rust 16 | run: rustup update stable 17 | - name: Add additional Rust targets 18 | run: rustup target add x86_64-unknown-none 19 | - name: Lint Rust sources 20 | run: | 21 | cargo clippy --package bitflag --no-deps --target x86_64-unknown-none -- -D warnings 22 | cargo clippy --package config --no-deps --target x86_64-unknown-none -- -D warnings 23 | cargo clippy --package hv --no-deps -- -D warnings 24 | cargo clippy --package krt --no-deps --target x86_64-unknown-none -- -D warnings 25 | cargo clippy --package macros --no-deps -- -D warnings 26 | cargo clippy --package obkrnl --no-deps --target x86_64-unknown-none -- -D warnings 27 | cargo clippy --package x86-64 --no-deps --target x86_64-unknown-none -- -D warnings 28 | - name: Build 29 | run: ./build.py -r 30 | - name: Run tests 31 | run: cargo test 32 | - name: Create distribution tarball 33 | run: | 34 | ln -sr dist/bin/obliteration dist/obliteration 35 | mv dist obliteration 36 | tar -cvf obliteration.tar obliteration 37 | - name: Upload artifacts 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: obliteration-linux-amd64 41 | path: obliteration.tar 42 | -------------------------------------------------------------------------------- /.github/workflows/ci-mac.yml: -------------------------------------------------------------------------------- 1 | name: CI (Mac) 2 | on: 3 | workflow_call: 4 | jobs: 5 | build: 6 | name: Mac M1 7 | runs-on: macos-14 8 | steps: 9 | - name: Check out repository 10 | uses: actions/checkout@v4 11 | - name: Generate cache keys 12 | run: | 13 | echo "target=${{ runner.os }}-${{ runner.arch }}-target-${{ hashFiles('Cargo.lock') }}" >> $GITHUB_OUTPUT 14 | id: cache-keys 15 | - name: Restore target directory 16 | uses: actions/cache/restore@v4 17 | with: 18 | path: target 19 | key: ${{ steps.cache-keys.outputs.target }} 20 | - name: Update Rust 21 | run: rustup update stable 22 | - name: Install Rust nightly 23 | run: rustup toolchain install nightly 24 | - name: Install additional Rust components 25 | run: rustup component add rust-src --toolchain nightly 26 | - name: Install additional Rust targets 27 | run: rustup target add aarch64-unknown-none-softfloat 28 | - name: Lint Rust sources 29 | run: | 30 | cargo clippy --package aarch64 --no-deps --target aarch64-unknown-none-softfloat -- -D warnings 31 | cargo clippy --package bitflag --no-deps --target aarch64-unknown-none-softfloat -- -D warnings 32 | cargo clippy --package config --no-deps --target aarch64-unknown-none-softfloat -- -D warnings 33 | cargo clippy --package krt --no-deps --target aarch64-unknown-none-softfloat -- -D warnings 34 | cargo clippy --package macros --no-deps -- -D warnings 35 | - name: Build 36 | run: ./build.py -r 37 | - name: Run tests 38 | run: cargo test 39 | - name: Create Apple Disk Image 40 | run: hdiutil create -volname Obliteration -srcfolder dist Obliteration.dmg 41 | - name: Upload artifacts 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: obliteration-mac-m1 45 | path: Obliteration.dmg 46 | - name: Cache target directory 47 | uses: actions/cache/save@v4 48 | with: 49 | path: target 50 | key: ${{ steps.cache-keys.outputs.target }}-${{ github.run_id }} 51 | if: startsWith(github.ref, 'refs/heads/') 52 | -------------------------------------------------------------------------------- /.github/workflows/ci-windows.yml: -------------------------------------------------------------------------------- 1 | name: CI (Windows) 2 | on: 3 | workflow_call: 4 | jobs: 5 | build: 6 | name: Windows 7 | runs-on: windows-2022 8 | steps: 9 | - name: Check out repository 10 | uses: actions/checkout@v4 11 | - name: Generate cache keys 12 | run: | 13 | echo "target=${{ runner.os }}-target-${{ hashFiles('Cargo.lock') }}" >> $env:GITHUB_OUTPUT 14 | echo "vulkan=${{ runner.os }}-vulkan-1.3.290.0" >> $env:GITHUB_OUTPUT 15 | id: cache-keys 16 | - name: Restore Vulkan SDK 17 | uses: actions/cache/restore@v4 18 | with: 19 | path: C:\VulkanSDK 20 | key: ${{ steps.cache-keys.outputs.vulkan }} 21 | id: restore-vulkan 22 | - name: Install Vulkan SDK 23 | run: | 24 | Invoke-WebRequest -Uri "https://sdk.lunarg.com/sdk/download/1.3.290.0/windows/VulkanSDK-1.3.290.0-Installer.exe" -OutFile VulkanSDK.exe 25 | .\VulkanSDK.exe --root C:\VulkanSDK --accept-licenses --default-answer --confirm-command install 26 | echo "new-install=true" >> $env:GITHUB_OUTPUT 27 | id: install-vulkan 28 | if: ${{ steps.restore-vulkan.outputs.cache-hit != 'true' }} 29 | - name: Set Vulkan SDK path 30 | run: echo "VULKAN_SDK=C:\VulkanSDK" >> $env:GITHUB_ENV 31 | - name: Restore target directory 32 | uses: actions/cache/restore@v4 33 | with: 34 | path: target 35 | key: ${{ steps.cache-keys.outputs.target }} 36 | - name: Update Rust 37 | run: rustup update stable 38 | - name: Add additional Rust targets 39 | run: rustup target add x86_64-unknown-none 40 | - name: Build 41 | run: python3 build.py -r 42 | - name: Run tests 43 | run: cargo test 44 | - name: Upload artifacts 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: obliteration-win-x64 48 | path: dist 49 | - name: Cache target directory 50 | uses: actions/cache/save@v4 51 | with: 52 | path: target 53 | key: ${{ steps.cache-keys.outputs.target }}-${{ github.run_id }} 54 | if: startsWith(github.ref, 'refs/heads/') 55 | - name: Cache Vulkan SDK 56 | uses: actions/cache/save@v4 57 | with: 58 | path: C:\VulkanSDK 59 | key: ${{ steps.cache-keys.outputs.vulkan }} 60 | if: startsWith(github.ref, 'refs/heads/') && steps.install-vulkan.outputs.new-install == 'true' 61 | -------------------------------------------------------------------------------- /.github/workflows/housekeep.yml: -------------------------------------------------------------------------------- 1 | name: Housekeep 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | housekeep: 7 | name: Housekeep 8 | runs-on: ubuntu-24.04 9 | steps: 10 | - name: Install System Packages 11 | run: | 12 | sudo apt-get update 13 | sudo apt-get install -y python3-github 14 | - name: Update PRs 15 | run: | 16 | from datetime import datetime, timezone 17 | from github import Auth, Github 18 | 19 | now = datetime.now(timezone.utc) 20 | gh = Github(auth=Auth.Token("${{ secrets.GITHUB_TOKEN }}")) 21 | repo = gh.get_repo("${{ github.repository }}") 22 | 23 | for pull in repo.get_pulls("open", "updated", "asc"): 24 | if (now - pull.updated_at).days <= 30: 25 | break 26 | print(f"Closing {pull.title}") 27 | pull.create_issue_comment("We are closing this PR due to no any activities in the last 30 days. Feel free to re-open it if you would like to continue working on this.") 28 | pull.edit(state="closed") 29 | shell: python 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Development Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | concurrency: dev-${{ github.ref }} 7 | jobs: 8 | build-windows: 9 | name: Build 10 | uses: ./.github/workflows/ci-windows.yml 11 | build-linux: 12 | name: Build 13 | uses: ./.github/workflows/ci-linux.yml 14 | build-mac: 15 | name: Build 16 | uses: ./.github/workflows/ci-mac.yml 17 | build-docs: 18 | name: Build 19 | uses: ./.github/workflows/ci-docs.yml 20 | deploy-docs: 21 | name: Deploy documentation 22 | runs-on: ubuntu-24.04 23 | needs: build-docs 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | permissions: 28 | pages: write 29 | id-token: write 30 | steps: 31 | - name: Deploy to GitHub Pages 32 | uses: actions/deploy-pages@v4 33 | id: deployment 34 | update-prs: 35 | name: Update PRs 36 | runs-on: ubuntu-24.04 37 | steps: 38 | - name: Install System Packages 39 | run: | 40 | sudo apt-get update 41 | sudo apt-get install -y python3-github 42 | - name: Update PRs 43 | run: | 44 | from datetime import datetime, timezone 45 | from github import Auth, Github 46 | 47 | now = datetime.now(timezone.utc) 48 | gh = Github(auth=Auth.Token("${{ secrets.GITHUB_TOKEN }}")) 49 | repo = gh.get_repo("${{ github.repository }}") 50 | 51 | for pull in repo.get_pulls("open", "updated", "desc", "${{ github.ref_name }}"): 52 | if (now - pull.updated_at).days > 30: 53 | break 54 | ready = False 55 | for label in pull.labels: 56 | if label.name == "S-ready": 57 | ready = True 58 | if ready: 59 | print(f"Removing S-ready from {pull.title}") 60 | pull.remove_from_labels("S-ready") 61 | shell: python 62 | -------------------------------------------------------------------------------- /.github/workflows/pr-build.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | on: 3 | pull_request: 4 | branches: 5 | - '*' 6 | concurrency: pr-${{ github.ref }} 7 | jobs: 8 | prebuild: 9 | name: Pre build 10 | runs-on: ubuntu-24.04 11 | steps: 12 | - name: Check out repository 13 | uses: actions/checkout@v4 14 | - name: Update Rust 15 | run: rustup update stable 16 | - name: Check Rust styles 17 | run: cargo fmt --check 18 | build-windows: 19 | name: Build 20 | uses: ./.github/workflows/ci-windows.yml 21 | needs: prebuild 22 | build-linux: 23 | name: Build 24 | uses: ./.github/workflows/ci-linux.yml 25 | needs: prebuild 26 | build-mac: 27 | name: Build 28 | uses: ./.github/workflows/ci-mac.yml 29 | needs: prebuild 30 | build-docs: 31 | name: Build 32 | uses: ./.github/workflows/ci-docs.yml 33 | needs: prebuild 34 | postbuild: 35 | name: Post build 36 | runs-on: ubuntu-24.04 37 | needs: [build-windows, build-linux, build-mac, build-docs] 38 | steps: 39 | - name: Generate build information 40 | run: | 41 | import json 42 | 43 | info = { 44 | "pr": ${{ github.event.number }}, 45 | "base": "${{ github.base_ref }}", 46 | "head": "${{ github.event.pull_request.head.sha }}", 47 | "author": "${{ github.event.pull_request.head.user.login }}" 48 | } 49 | 50 | with open("build-info.json", "w") as fp: 51 | json.dump(info, fp) 52 | shell: python 53 | - name: Upload build information 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: build-info 57 | path: build-info.json 58 | -------------------------------------------------------------------------------- /.github/workflows/pr-update.yml: -------------------------------------------------------------------------------- 1 | name: Update PR 2 | on: 3 | workflow_run: 4 | workflows: [PR Build] 5 | types: 6 | - completed 7 | jobs: 8 | postbuild: 9 | name: Update PR 10 | runs-on: ubuntu-24.04 11 | if: github.event.workflow_run.conclusion == 'success' 12 | steps: 13 | - name: Install System Packages 14 | run: | 15 | sudo apt-get update 16 | sudo apt-get install -y python3-github 17 | - name: Update PR 18 | run: | 19 | from github import Auth, Github 20 | from io import BytesIO 21 | import json 22 | from urllib.request import Request, urlopen 23 | from zipfile import ZipFile 24 | 25 | tok = "${{ secrets.GITHUB_TOKEN }}" 26 | gh = Github(auth=Auth.Token(tok)) 27 | repo = gh.get_repo("${{ github.repository }}") 28 | run = repo.get_workflow_run(${{ github.event.workflow_run.id }}) 29 | info = None 30 | 31 | for artifact in run.get_artifacts(): 32 | if artifact.name == "build-info": 33 | info = artifact 34 | 35 | req = Request(info.archive_download_url) 36 | req.add_unredirected_header("Authorization", f"token {tok}") 37 | req.add_unredirected_header("Accept", "application/vnd.github+json") 38 | 39 | with urlopen(req) as info: 40 | zip = ZipFile(BytesIO(info.read())) 41 | with zip.open("build-info.json") as info: 42 | info = json.load(info) 43 | 44 | pull = repo.get_pull(info["pr"]) 45 | 46 | if not pull.draft: 47 | author = info["author"]; 48 | head = info["head"] 49 | cmp = repo.compare(info["base"], f"{author}:{head}") 50 | 51 | if cmp.status != "behind": 52 | ready = False 53 | for label in pull.labels: 54 | if label.name == "S-ready": 55 | ready = True 56 | if not ready: 57 | pull.add_to_labels("S-ready") 58 | shell: python 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /dist/ 3 | /site/ 4 | /target/ 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode.hexeditor", 4 | "redhat.vscode-yaml", 5 | "rust-lang.rust-analyzer", 6 | "Slint.slint", 7 | "tamasfe.even-better-toml", 8 | "vadimcn.vscode-lldb" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "GUI", 5 | "type": "lldb", 6 | "request": "launch", 7 | "preLaunchTask": "Build", 8 | "linux": { 9 | "program": "${workspaceFolder}/dist/bin/obliteration" 10 | }, 11 | "osx": { 12 | "program": "${workspaceFolder}/dist/Obliteration.app/Contents/MacOS/Obliteration" 13 | }, 14 | "windows": { 15 | "program": "${workspaceFolder}/dist/obliteration.exe" 16 | } 17 | }, 18 | { 19 | "name": "Kernel", 20 | "type": "lldb", 21 | "request": "launch", 22 | "processCreateCommands": [ 23 | "gdb-remote 1234" 24 | ], 25 | "linux": { 26 | "targetCreateCommands": [ 27 | "target create ${workspaceFolder}/dist/share/obkrnl", 28 | "target modules load --file ${workspaceFolder}/dist/share/obkrnl -s 0xffffffff82200000" 29 | ] 30 | }, 31 | "osx": { 32 | "targetCreateCommands": [ 33 | "target create ${workspaceFolder}/dist/Obliteration.app/Contents/Resources/obkrnl", 34 | "target modules load --file ${workspaceFolder}/dist/Obliteration.app/Contents/Resources/obkrnl -s 0xffffffff82200000" 35 | ] 36 | }, 37 | "windows": { 38 | "targetCreateCommands": [ 39 | "target create ${workspaceFolder}/dist/share/obkrnl", 40 | "target modules load --file ${workspaceFolder}/dist/share/obkrnl -s 0xffffffff82200000" 41 | ] 42 | } 43 | } 44 | ], 45 | "version": "2.0.0" 46 | } 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[json]": { 3 | "editor.formatOnSave": true 4 | }, 5 | "[jsonl]": { 6 | "editor.formatOnSave": true 7 | }, 8 | "[python]": { 9 | "editor.rulers": [ 10 | 100 11 | ] 12 | }, 13 | "[rust]": { 14 | "editor.defaultFormatter": "rust-lang.rust-analyzer", 15 | "editor.formatOnSave": true, 16 | "editor.rulers": [ 17 | 100 18 | ] 19 | }, 20 | "[slint]": { 21 | "editor.defaultFormatter": "Slint.slint", 22 | "editor.formatOnSave": true 23 | }, 24 | "[toml]": { 25 | "editor.formatOnSave": true 26 | }, 27 | "files.insertFinalNewline": true, 28 | "files.trimFinalNewlines": true, 29 | "files.trimTrailingWhitespace": true, 30 | "lldb.launch.initCommands": [ 31 | "settings set target.x86-disassembly-flavor intel" 32 | ], 33 | "rust-analyzer.cargo.features": "all", 34 | "rust-analyzer.imports.granularity.group": "module", 35 | "rust-analyzer.imports.group.enable": false, 36 | "rust-analyzer.imports.prefix": "self", 37 | "slint.libraryPaths": { 38 | "root": "gui/ui" 39 | }, 40 | "slint.preview.style": "fluent-dark" 41 | } 42 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build", 6 | "detail": "Build Obliteration and create distribution files", 7 | "type": "process", 8 | "command": "python3", 9 | "args": [ 10 | "build.py" 11 | ], 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | }, 16 | "isBuildCommand": true 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | dev.obliteration.net 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "config", 5 | "gui", 6 | "kernel", 7 | "lib/aarch64", 8 | "lib/bitflag", 9 | "lib/hv", 10 | "lib/krt", 11 | "lib/x86-64", 12 | "macros", 13 | ] 14 | 15 | [profile.dev] 16 | panic = "abort" 17 | 18 | [profile.release] 19 | panic = "abort" 20 | lto = true 21 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | obliteration 9 | CFBundleGetInfoString 10 | 11 | CFBundleIconFile 12 | obliteration 13 | CFBundleIdentifier 14 | io.github.obhq.obliteration 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | 19 | CFBundleName 20 | Obliteration 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 0.1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 0.1.0 29 | CSResourcesFileMapped 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Obliteration Contributors 2 | Copyright (c) 2022 InoriRus 3 | Copyright (c) 2017 idc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bundle.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/bundle.icns -------------------------------------------------------------------------------- /config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "config" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [features] 7 | serde = ["dep:serde", "dep:serde_bytes"] 8 | virt = ["dep:num_enum"] 9 | 10 | [dependencies] 11 | num_enum = { version = "0.7.3", default-features = false, optional = true } 12 | serde = { version = "1.0.210", features = [ 13 | "derive", 14 | ], default-features = false, optional = true } 15 | serde_bytes = { version = "0.11.17", optional = true } 16 | -------------------------------------------------------------------------------- /config/src/env/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::vm::*; 2 | 3 | mod vm; 4 | 5 | /// Implementation of `bios_smap` structure. 6 | /// 7 | /// This basically a struct that returned from [e820](https://en.wikipedia.org/wiki/E820). All 8 | /// non-BIOS platform (e.g. UEFI) need to populate this struct from the other sources. 9 | #[repr(C)] 10 | pub struct PhysMap { 11 | pub base: u64, 12 | pub len: u64, 13 | pub ty: MapType, 14 | pub attrs: u32, 15 | } 16 | 17 | /// Type of [PhysMap]. 18 | #[repr(u32)] 19 | pub enum MapType { 20 | None = 0, 21 | Ram = 1, 22 | Reserved = 2, 23 | } 24 | -------------------------------------------------------------------------------- /config/src/env/vm.rs: -------------------------------------------------------------------------------- 1 | use super::PhysMap; 2 | use core::num::NonZero; 3 | 4 | /// Provides boot information when booting on a Virtual Machine. 5 | #[repr(C)] 6 | pub struct Vm { 7 | /// Name of the Hypervisor. 8 | /// 9 | /// This used for diagnostic only. 10 | pub hypervisor: [u8; 128], 11 | /// Address of [VmmMemory]. 12 | pub vmm: usize, 13 | /// Address of [ConsoleMemory]. 14 | pub console: usize, 15 | /// Page size on the host. 16 | pub host_page_size: NonZero, 17 | /// Memory map. Set [PhysMap::ty] to [MapType::None](super::MapType::None) to mark the end of 18 | /// the list. 19 | pub memory_map: [PhysMap; 64], 20 | } 21 | 22 | impl Vm { 23 | pub fn hypervisor(&self) -> &[u8] { 24 | let len = self 25 | .hypervisor 26 | .iter() 27 | .position(|&b| b == 0) 28 | .unwrap_or(self.hypervisor.len()); 29 | 30 | &self.hypervisor[..len] 31 | } 32 | } 33 | 34 | /// Layout of a memory for Memory-mapped I/O to communicate with VMM. 35 | #[cfg(feature = "virt")] 36 | #[repr(C)] 37 | pub struct VmmMemory { 38 | pub shutdown: KernelExit, 39 | } 40 | 41 | /// Exit status of the kernel. 42 | #[cfg(feature = "virt")] 43 | #[repr(u8)] 44 | #[derive(Clone, Copy, PartialEq, Eq, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] 45 | pub enum KernelExit { 46 | Success, 47 | Panic, 48 | } 49 | 50 | /// Layout of console memory for Memory-mapped I/O. 51 | /// 52 | /// The sequence of operations on a console memory is per-cpu. The kernel will start each log by: 53 | /// 54 | /// 1. Write [`Self::msg_len`] then [`Self::msg_addr`]. 55 | /// 2. Repeat step 1 until the whole message has been written. 56 | /// 3. Write [`Self::commit`]. 57 | /// 58 | /// Beware that each write to [`Self::msg_len`] except the last one may not cover the full message. 59 | /// The consequence of this is [`Self::msg_addr`] may point to an incomplete UTF-8 byte sequence. 60 | /// That means you should buffer the message until [`Self::commit`] has been written before validating 61 | /// if it is valid UTF-8. 62 | #[cfg(feature = "virt")] 63 | #[repr(C)] 64 | pub struct ConsoleMemory { 65 | pub msg_len: NonZero, 66 | pub msg_addr: usize, 67 | pub commit: ConsoleType, 68 | } 69 | 70 | /// Type of console message. 71 | #[cfg(feature = "virt")] 72 | #[repr(u8)] 73 | #[derive(Debug, Clone, Copy, num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] 74 | pub enum ConsoleType { 75 | Info, 76 | Warn, 77 | Error, 78 | } 79 | -------------------------------------------------------------------------------- /config/src/idps/mod.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Display, Formatter}; 2 | 3 | /// Implementation of [IDPS]. 4 | /// 5 | /// All fields here are big-endian the same as PS3. 6 | /// 7 | /// [IDPS]: https://www.psdevwiki.com/ps3/IDPS 8 | #[repr(C)] 9 | #[derive(Clone)] 10 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 11 | pub struct ConsoleId { 12 | magic: u16, 13 | company: CompanyId, 14 | product: ProductId, 15 | prodsub: u16, 16 | #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] 17 | serial: [u8; 8], 18 | } 19 | 20 | impl ConsoleId { 21 | pub fn new(company: CompanyId, product: ProductId, prodsub: u16, serial: [u8; 8]) -> Self { 22 | Self { 23 | magic: 0, 24 | company, 25 | product, 26 | prodsub, 27 | serial, 28 | } 29 | } 30 | 31 | pub fn product(&self) -> ProductId { 32 | self.product 33 | } 34 | } 35 | 36 | impl Default for ConsoleId { 37 | fn default() -> Self { 38 | Self::new( 39 | CompanyId::SONY, 40 | ProductId::USA, 41 | 0x1200, 42 | [0x10, 0, 0, 0, 0, 0, 0, 0], 43 | ) 44 | } 45 | } 46 | 47 | /// Company identifier for [`ConsoleId`]. 48 | #[repr(transparent)] 49 | #[derive(Clone, Copy)] 50 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 51 | pub struct CompanyId(u16); 52 | 53 | impl CompanyId { 54 | pub const SONY: Self = Self(0x100); 55 | } 56 | 57 | /// Product identifier for [`ConsoleId`]. 58 | /// 59 | /// See https://www.psdevwiki.com/ps4/Console_ID for a list of known IDs. 60 | #[repr(transparent)] 61 | #[derive(Clone, Copy, PartialEq, Eq)] 62 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 63 | pub struct ProductId(u16); 64 | 65 | impl ProductId { 66 | pub const DEVKIT: Self = Self(0x8101); 67 | pub const TESTKIT: Self = Self(0x8201); 68 | pub const USA: Self = Self(0x8401); 69 | pub const SOUTH_ASIA: Self = Self(0x8A01); 70 | } 71 | 72 | impl Display for ProductId { 73 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 74 | let v = match *self { 75 | Self::DEVKIT => "TOOL/DEVKIT", 76 | Self::TESTKIT => "DEX/TESTKIT", 77 | Self::USA => "UC2/USA/CANADA", 78 | Self::SOUTH_ASIA => "E12/MALAYSIA", 79 | _ => return write!(f, "{:#X}", self.0), 80 | }; 81 | 82 | f.write_str(v) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use self::env::*; 4 | pub use self::idps::*; 5 | pub use self::qa::*; 6 | 7 | use core::num::NonZero; 8 | 9 | mod env; 10 | mod idps; 11 | mod qa; 12 | 13 | /// Contains information about the boot environment. 14 | #[repr(C)] 15 | pub enum BootEnv { 16 | Vm(Vm), 17 | } 18 | 19 | /// Runtime configurations for the kernel. 20 | #[repr(C)] 21 | #[derive(Clone)] 22 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 23 | #[cfg_attr(feature = "serde", serde(default))] 24 | pub struct Config { 25 | pub max_cpu: NonZero, 26 | pub idps: ConsoleId, 27 | pub qa: bool, 28 | pub qa_flags: QaFlags, 29 | #[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] 30 | pub env_vars: [u8; 132096], // See init_dynamic_kenv() on the Orbis for this number. 31 | } 32 | 33 | impl Default for Config { 34 | fn default() -> Self { 35 | Self { 36 | max_cpu: NonZero::new(1).unwrap(), 37 | idps: ConsoleId::default(), 38 | qa: false, 39 | qa_flags: QaFlags::default(), 40 | env_vars: [0; 132096], 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /config/src/qa/mod.rs: -------------------------------------------------------------------------------- 1 | /// Flags that control kernel behavior related to QA. 2 | /// 3 | /// See https://www.psdevwiki.com/ps4/QA_Flagging for a list of known flags. 4 | #[repr(C)] 5 | #[derive(Default, Clone)] 6 | #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] 7 | pub struct QaFlags(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] [u8; 16]); 8 | 9 | impl QaFlags { 10 | pub fn internal_dev(&self) -> bool { 11 | (self.0[0] & 4) != 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/building.md: -------------------------------------------------------------------------------- 1 | # Building 2 | 3 | The first step is getting Obliteration source code from our [repository](https://github.com/obhq/obliteration). If you plan to make some contributions you need to fork our repository by clicking on Fork on the top right before proceed and all instructions after this need to be done on your forked repository instead of our repository. 4 | 5 | Once ready click on Code button on the top right then copy the HTTPS URL from the popup (or SSH if you know how to use it). Then open a terminal (or Command Prompt) on the directory where you want to download the source code into. You can use `cd` command to change to the directory you want. Then run the following command: 6 | 7 | ```sh 8 | git clone URL 9 | ``` 10 | 11 | Replace `URL` with the URL you have copied. 12 | 13 | ## Build 14 | 15 | Change into the directory you just downloaded with `cd` and run the following command: 16 | 17 | ```sh 18 | python3 build.py -r 19 | ``` 20 | 21 | Remove `-r` to disable optimization if you plan to make some contributions so the debugger will work properly. Build outputs will be placed in `dist` directory. 22 | -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | In order to build Obliteration from source make sure you have the following prerequisites. 4 | 5 | ## All platforms 6 | 7 | - [Git](https://git-scm.com) 8 | - On Windows make sure you have `Run Git from the Windows Command Prompt` selected when installing 9 | - On Linux it is likely your distro already provided a package for this 10 | - On macOS you can install from Homebrew 11 | - [Rust on the latest stable channel](https://www.rust-lang.org/tools/install) 12 | - Make sure you install using `rustup` 13 | - On Linux your distro may provide a package for this 14 | - On macOS you can install from Homebrew 15 | - [Python 3](https://www.python.org) 16 | - On Windows make sure you have `Add Python to PATH` selected when installing 17 | - On Linux it is likely your distro already provided a package for this 18 | - On macOS you can install latest version from Homebrew 19 | 20 | ## Windows 21 | 22 | - [Visual Studio 2022](https://visualstudio.microsoft.com/vs) 23 | - Rust installer should already install this for you so you should not need to install this manually 24 | - Community edition are free for open-source project 25 | - `Desktop development with C++` workload is required 26 | - [Windows Terminal](https://aka.ms/terminal) 27 | - You can use a classic `Command Prompt` but make sure you enable [ANSI escape sequences](https://stackoverflow.com/q/16755142/1829232) 28 | -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.hypervisor 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gui/build.rs: -------------------------------------------------------------------------------- 1 | use slint_build::CompilerConfiguration; 2 | use std::collections::HashMap; 3 | use std::path::PathBuf; 4 | 5 | fn main() { 6 | let root = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()); 7 | 8 | // Compile Slint. 9 | let config = CompilerConfiguration::new() 10 | .with_style(String::from("fluent-dark")) 11 | .with_library_paths(HashMap::from([("root".into(), root.join("ui"))])); 12 | 13 | slint_build::compile_with_config(PathBuf::from_iter(["ui", "lib.slint"]), config).unwrap(); 14 | 15 | // Compile resources.rc. 16 | #[cfg(windows)] 17 | winres::WindowsResource::new() 18 | .set_resource_file(root.join("resources.rc").to_str().unwrap()) 19 | .compile() 20 | .unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /gui/obliteration.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/gui/obliteration.ico -------------------------------------------------------------------------------- /gui/resources.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/gui/resources.rc -------------------------------------------------------------------------------- /gui/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::part::*; 2 | pub use self::prof::*; 3 | 4 | use std::io::ErrorKind; 5 | use std::path::{Path, PathBuf}; 6 | use thiserror::Error; 7 | 8 | mod part; 9 | mod prof; 10 | 11 | /// Manages all files and directories that stored in the data root. 12 | /// 13 | /// This does not manage file content. Its job is to organize filesystem hierarchy. 14 | pub struct DataMgr { 15 | settings: PathBuf, 16 | part: Part, 17 | prof: Prof, 18 | logs: PathBuf, 19 | } 20 | 21 | impl DataMgr { 22 | pub fn new(root: impl Into) -> Result { 23 | // Build path for top-level items. 24 | let root: PathBuf = root.into(); 25 | let settings = root.join("settings.bin"); 26 | let part = root.join("part"); 27 | let prof = root.join("prof"); 28 | let logs = root.join("kernel.txt"); 29 | 30 | // Create top-level directories. 31 | Self::create_dir(&part)?; 32 | Self::create_dir(&prof)?; 33 | 34 | Ok(Self { 35 | settings, 36 | part: Part::new(part), 37 | prof: Prof::new(prof), 38 | logs, 39 | }) 40 | } 41 | 42 | pub fn settings(&self) -> &Path { 43 | &self.settings 44 | } 45 | 46 | pub fn partitions(&self) -> &Part { 47 | &self.part 48 | } 49 | 50 | pub fn profiles(&self) -> &Prof { 51 | &self.prof 52 | } 53 | 54 | pub fn logs(&self) -> &Path { 55 | &self.logs 56 | } 57 | 58 | fn create_dir(path: &Path) -> Result<(), DataError> { 59 | if let Err(e) = std::fs::create_dir(path) { 60 | if e.kind() != ErrorKind::AlreadyExists { 61 | return Err(DataError::CreateDirectory(path.to_owned(), e)); 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | } 68 | 69 | /// Represents an error when operation on data root fails. 70 | #[derive(Debug, Error)] 71 | pub enum DataError { 72 | #[error("couldn't create {0}")] 73 | CreateDirectory(PathBuf, #[source] std::io::Error), 74 | 75 | #[error("couldn't list item in {0}")] 76 | ReadDirectory(PathBuf, #[source] std::io::Error), 77 | } 78 | -------------------------------------------------------------------------------- /gui/src/data/part.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | /// Manages disk partition to be mounted by the kernel. 4 | pub struct Part { 5 | root: PathBuf, 6 | } 7 | 8 | impl Part { 9 | pub(super) fn new(root: PathBuf) -> Self { 10 | Self { root } 11 | } 12 | 13 | pub fn meta(&self, name: impl AsRef) -> PathBuf { 14 | self.root.join(format!("{}.obp", name.as_ref())) 15 | } 16 | 17 | pub fn data(&self, name: impl AsRef) -> PathBuf { 18 | self.root.join(name.as_ref()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gui/src/data/prof.rs: -------------------------------------------------------------------------------- 1 | use super::DataError; 2 | use std::path::{Path, PathBuf}; 3 | use uuid::Uuid; 4 | 5 | /// Manages profile data stored on the filesystem. 6 | pub struct Prof { 7 | root: PathBuf, 8 | } 9 | 10 | impl Prof { 11 | pub(super) fn new(root: PathBuf) -> Self { 12 | Self { root } 13 | } 14 | 15 | pub fn list(&self) -> Result> + '_, DataError> { 16 | std::fs::read_dir(&self.root) 17 | .map_err(|e| DataError::ReadDirectory(self.root.clone(), e)) 18 | .map(|iter| List { 19 | iter, 20 | path: &self.root, 21 | }) 22 | } 23 | 24 | pub fn data(&self, id: Uuid) -> PathBuf { 25 | let mut buf = Uuid::encode_buffer(); 26 | let id = id.as_hyphenated().encode_lower(&mut buf); 27 | 28 | self.root.join(id) 29 | } 30 | } 31 | 32 | /// Implementation of [`Iterator`] to enumerate profile directories. 33 | struct List<'a> { 34 | iter: std::fs::ReadDir, 35 | path: &'a Path, 36 | } 37 | 38 | impl<'a> Iterator for List<'a> { 39 | type Item = Result; 40 | 41 | fn next(&mut self) -> Option { 42 | self.iter 43 | .next()? 44 | .map_err(|e| DataError::ReadDirectory(self.path.into(), e)) 45 | .map(|i| i.path()) 46 | .into() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gui/src/gdb/handler.rs: -------------------------------------------------------------------------------- 1 | /// Provides methods to handle debug events. 2 | pub trait GdbHandler {} 3 | -------------------------------------------------------------------------------- /gui/src/gdb/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pub use self::handler::*; 3 | 4 | use self::client::ClientDispatcher; 5 | use self::state::SessionState; 6 | use thiserror::Error; 7 | 8 | mod client; 9 | mod handler; 10 | mod state; 11 | 12 | /// Represents a GDB remote session. 13 | /// 14 | /// This type requires the client to be compatible with GDB >= 5.0. 15 | #[derive(Default)] 16 | pub struct GdbSession { 17 | req: Vec, 18 | res: Vec, 19 | state: SessionState, 20 | } 21 | 22 | impl GdbSession { 23 | #[must_use] 24 | pub fn dispatch_client<'a, H: GdbHandler>( 25 | &'a mut self, 26 | data: &[u8], 27 | h: &'a mut H, 28 | ) -> impl GdbDispatcher + 'a { 29 | self.req.extend_from_slice(data); 30 | 31 | ClientDispatcher::new(self, h) 32 | } 33 | } 34 | 35 | /// Provides method to dispatch debug operations. 36 | pub trait GdbDispatcher { 37 | /// The returned response can be empty if this pump does not produce any response. 38 | fn pump(&mut self) -> Result + '_>, GdbError>; 39 | } 40 | 41 | /// Represents an error when [`GdbDispatcher`] fails. 42 | #[derive(Debug, Error)] 43 | pub enum GdbError { 44 | #[error("unknown packet prefix {0:#x}")] 45 | UnknownPacketPrefix(u8), 46 | 47 | #[error("unexpected acknowledgment packet from GDB")] 48 | UnexpectedAck, 49 | 50 | #[error("missing acknowledgment packet from GDB")] 51 | MissingAck, 52 | 53 | #[error("couldn't decode checksum {0:?}")] 54 | DecodeChecksum([u8; 2], #[source] hex::FromHexError), 55 | 56 | #[error("invalid checksum (expect {1}, got {0})")] 57 | InvalidChecksum(u8, u8), 58 | } 59 | -------------------------------------------------------------------------------- /gui/src/gdb/state.rs: -------------------------------------------------------------------------------- 1 | /// Contains states for a GDB remote session. 2 | #[derive(Default)] 3 | pub struct SessionState { 4 | no_ack: Option, 5 | } 6 | 7 | impl SessionState { 8 | pub fn no_ack(&self) -> Option { 9 | self.no_ack 10 | } 11 | 12 | pub fn parse_start_no_ack_mode(&mut self, res: &mut Vec) { 13 | self.no_ack = Some(false); 14 | 15 | res.extend_from_slice(b"OK"); 16 | } 17 | 18 | pub fn parse_ack_no_ack(&mut self) { 19 | self.no_ack = Some(true); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gui/src/graphics/metal/engine.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use crate::graphics::Graphics; 3 | use metal::{Device, MetalLayer}; 4 | use thiserror::Error; 5 | 6 | /// Implementation of [`Graphics`] using Metal. 7 | /// 8 | /// Fields in this struct need to be dropped in a correct order. 9 | pub struct Metal { 10 | device: Device, 11 | } 12 | 13 | impl Metal { 14 | pub fn new() -> Result { 15 | // Get Metal device. 16 | let device = match Device::system_default() { 17 | Some(v) => v, 18 | None => return Err(MetalError::GetDeviceFailed), 19 | }; 20 | 21 | Ok(Self { device }) 22 | } 23 | 24 | /// # Safety 25 | /// The returned [`MetalLayer`] must be dropped before this [`Metal`]. 26 | pub unsafe fn create_layer(&self) -> MetalLayer { 27 | let layer = MetalLayer::new(); 28 | 29 | layer.set_device(&self.device); 30 | layer 31 | } 32 | } 33 | 34 | impl Graphics for Metal {} 35 | 36 | /// Represents an error when [`Metal::new()`] fails. 37 | #[derive(Debug, Error)] 38 | pub enum MetalError { 39 | #[error("couldn't get default MTLDevice")] 40 | GetDeviceFailed, 41 | } 42 | -------------------------------------------------------------------------------- /gui/src/graphics/metal/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use self::engine::Metal; 3 | use super::GraphicsBuilder; 4 | use crate::profile::Profile; 5 | use crate::settings::Settings; 6 | use metal::Device; 7 | use std::ops::Deref; 8 | use std::sync::Arc; 9 | use std::sync::atomic::AtomicBool; 10 | use thiserror::Error; 11 | use winit::window::WindowAttributes; 12 | 13 | mod engine; 14 | mod window; 15 | 16 | pub fn builder(settings: &Settings) -> Result { 17 | Ok(MetalBuilder { 18 | devices: Device::all(), 19 | }) 20 | } 21 | 22 | /// Implementation of [`GraphicsBuilder`] for Metal. 23 | struct MetalBuilder { 24 | devices: Vec, 25 | } 26 | 27 | impl GraphicsBuilder for MetalBuilder { 28 | type PhysicalDevice = metal::Device; 29 | type Engine = Metal; 30 | 31 | fn physical_devices(&self) -> &[Self::PhysicalDevice] { 32 | &self.devices 33 | } 34 | 35 | fn build( 36 | self, 37 | profile: &Profile, 38 | screen: WindowAttributes, 39 | shutdown: &Arc, 40 | ) -> Result, GraphicsError> { 41 | todo!() 42 | } 43 | } 44 | 45 | impl super::PhysicalDevice for metal::Device { 46 | fn id(&self) -> &[u8] { 47 | todo!() 48 | } 49 | 50 | fn name(&self) -> &str { 51 | self.deref().name() 52 | } 53 | } 54 | 55 | /// Represents an error when operation on Metal fails. 56 | #[derive(Debug, Error)] 57 | pub enum GraphicsError {} 58 | -------------------------------------------------------------------------------- /gui/src/graphics/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | pub use self::engine::{GraphicsError, builder}; 3 | 4 | use crate::profile::Profile; 5 | use std::sync::Arc; 6 | use std::sync::atomic::AtomicBool; 7 | use winit::window::WindowAttributes; 8 | 9 | #[cfg_attr(target_os = "macos", path = "metal/mod.rs")] 10 | #[cfg_attr(not(target_os = "macos"), path = "vulkan/mod.rs")] 11 | mod engine; 12 | 13 | /// Provides method to build [`Graphics`]. 14 | pub trait GraphicsBuilder: 'static { 15 | type PhysicalDevice: PhysicalDevice; 16 | type Engine: Graphics; 17 | 18 | fn physical_devices(&self) -> &[Self::PhysicalDevice]; 19 | 20 | /// Currently this method was designed to run only once per application lifetime. 21 | fn build( 22 | self, 23 | profile: &Profile, 24 | screen: WindowAttributes, 25 | shutdown: &Arc, 26 | ) -> Result, GraphicsError>; 27 | } 28 | 29 | /// Represents a graphics hardware. 30 | pub trait PhysicalDevice: Sized { 31 | fn id(&self) -> &[u8]; 32 | fn name(&self) -> &str; 33 | } 34 | 35 | /// The underlying graphics engine (e.g. Vulkan). 36 | /// 37 | /// This trait act as a thin layer for graphics engine to be used by VMM. At compile-time this 38 | /// layer will be optimized out and aggressively inlined the same as Hypervisor trait. 39 | pub trait Graphics: Send + Sync + 'static {} 40 | -------------------------------------------------------------------------------- /gui/src/hw/console/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use self::context::Context; 3 | use super::{Device, DeviceContext}; 4 | use crate::util::VmmStream; 5 | use config::{ConsoleMemory, ConsoleType}; 6 | use hv::Hypervisor; 7 | use std::num::NonZero; 8 | 9 | mod context; 10 | 11 | /// Virtual console for the VM. 12 | pub struct Console { 13 | addr: usize, 14 | len: NonZero, 15 | } 16 | 17 | impl Console { 18 | pub fn new(addr: usize, block_size: NonZero) -> Self { 19 | let len = size_of::() 20 | .checked_next_multiple_of(block_size.get()) 21 | .and_then(NonZero::new) 22 | .unwrap(); 23 | 24 | Self { addr, len } 25 | } 26 | 27 | pub fn create_context<'a, H: Hypervisor>( 28 | &'a self, 29 | hv: &'a H, 30 | logs: &'a VmmStream<(ConsoleType, String)>, 31 | ) -> Box> + 'a> { 32 | Box::new(Context::new(self, hv, logs)) 33 | } 34 | } 35 | 36 | impl Device for Console { 37 | fn name(&self) -> &str { 38 | "Virtual Console" 39 | } 40 | 41 | fn addr(&self) -> usize { 42 | self.addr 43 | } 44 | 45 | fn len(&self) -> NonZero { 46 | self.len 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gui/src/hw/vmm/context.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use super::Vmm; 3 | use crate::hw::{DeviceContext, MmioError, read_u8}; 4 | use config::{KernelExit, VmmMemory}; 5 | use hv::{Cpu, CpuExit, CpuIo}; 6 | use std::error::Error; 7 | use std::mem::offset_of; 8 | use thiserror::Error; 9 | 10 | /// Implementation of [`DeviceContext`]. 11 | pub struct Context<'a> { 12 | dev: &'a Vmm, 13 | } 14 | 15 | impl<'a> Context<'a> { 16 | pub fn new(dev: &'a Vmm) -> Self { 17 | Self { dev } 18 | } 19 | } 20 | 21 | impl DeviceContext for Context<'_> { 22 | fn mmio( 23 | &mut self, 24 | exit: &mut as CpuExit>::Io, 25 | ) -> Result, Box> { 26 | // Check field. 27 | let off = exit.addr() - self.dev.addr; 28 | 29 | if off == offset_of!(VmmMemory, shutdown) { 30 | let exit = read_u8(exit).map_err(|e| ExecError::ReadFailed(off, e))?; 31 | let exit: KernelExit = exit 32 | .try_into() 33 | .map_err(|_| Box::new(ExecError::InvalidExit(exit)))?; 34 | 35 | Ok(Some(exit == KernelExit::Success)) 36 | } else { 37 | Err(Box::new(ExecError::UnknownField(off))) 38 | } 39 | } 40 | } 41 | 42 | /// Represents an error when [`Context::exec()`] fails. 43 | #[derive(Debug, Error)] 44 | enum ExecError { 45 | #[error("unknown field at offset {0:#}")] 46 | UnknownField(usize), 47 | 48 | #[error("couldn't read data for offset {0:#}")] 49 | ReadFailed(usize, #[source] MmioError), 50 | 51 | #[error("{0:#} is not a valid exit status")] 52 | InvalidExit(u8), 53 | } 54 | -------------------------------------------------------------------------------- /gui/src/hw/vmm/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use self::context::Context; 3 | use super::{Device, DeviceContext}; 4 | use config::VmmMemory; 5 | use hv::Cpu; 6 | use std::num::NonZero; 7 | 8 | mod context; 9 | 10 | /// Virtual device for the kernel to communicate with the VMM. 11 | pub struct Vmm { 12 | addr: usize, 13 | len: NonZero, 14 | } 15 | 16 | impl Vmm { 17 | pub fn new(addr: usize, block_size: NonZero) -> Self { 18 | let len = size_of::() 19 | .checked_next_multiple_of(block_size.get()) 20 | .and_then(NonZero::new) 21 | .unwrap(); 22 | 23 | Self { addr, len } 24 | } 25 | 26 | pub fn create_context<'a, C: Cpu>(&'a self) -> Box + 'a> { 27 | Box::new(Context::new(self)) 28 | } 29 | } 30 | 31 | impl Device for Vmm { 32 | fn name(&self) -> &str { 33 | "VMM" 34 | } 35 | 36 | fn addr(&self) -> usize { 37 | self.addr 38 | } 39 | 40 | fn len(&self) -> NonZero { 41 | self.len 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /gui/src/log/file.rs: -------------------------------------------------------------------------------- 1 | use anstyle_parse::Perform; 2 | use std::fs::File; 3 | use std::io::{BufWriter, Write}; 4 | 5 | /// Implementation of [`Perform`] for [`File`]. 6 | pub struct LogFile(BufWriter); 7 | 8 | impl LogFile { 9 | pub fn new(file: File) -> Self { 10 | Self(BufWriter::new(file)) 11 | } 12 | } 13 | 14 | impl Perform for LogFile { 15 | fn print(&mut self, c: char) { 16 | self.0 17 | .write_all(c.encode_utf8(&mut [0; 4]).as_bytes()) 18 | .unwrap(); 19 | } 20 | 21 | fn execute(&mut self, byte: u8) { 22 | match byte { 23 | b'\n' => { 24 | #[cfg(unix)] 25 | self.0.write_all(b"\n").unwrap(); 26 | #[cfg(windows)] 27 | self.0.write_all(b"\r\n").unwrap(); 28 | self.0.flush().unwrap(); 29 | } 30 | _ => (), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gui/src/log/mod.rs: -------------------------------------------------------------------------------- 1 | use self::file::LogFile; 2 | use anstyle_parse::Parser; 3 | use config::ConsoleType; 4 | use std::fs::File; 5 | use std::io::{Write, stderr, stdout}; 6 | use std::path::{Path, PathBuf}; 7 | 8 | mod file; 9 | 10 | /// Provides method to write kernel logs. 11 | pub struct LogWriter { 12 | file: LogFile, 13 | parser: Parser, 14 | path: PathBuf, 15 | } 16 | 17 | impl LogWriter { 18 | pub fn new(file: impl Into) -> Result { 19 | let path = file.into(); 20 | let file = File::create(&path)?; 21 | 22 | Ok(Self { 23 | file: LogFile::new(file), 24 | parser: Parser::default(), 25 | path, 26 | }) 27 | } 28 | 29 | pub fn path(&self) -> &Path { 30 | &self.path 31 | } 32 | 33 | pub fn write(&mut self, ty: ConsoleType, msg: String) { 34 | // Write console. 35 | let msg = msg.as_bytes(); 36 | 37 | match ty { 38 | ConsoleType::Info => stdout().write_all(msg).unwrap(), 39 | ConsoleType::Warn | ConsoleType::Error => stderr().write_all(msg).unwrap(), 40 | } 41 | 42 | // Write file. 43 | for &b in msg { 44 | self.parser.advance(&mut self.file, b); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gui/src/profile/cpu.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | /// Model of the CPU to report to the kernel. 5 | /// 6 | /// This has no effect on non-x86 and the kernel always assume [`CpuModel::Pro`]. 7 | #[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 8 | pub enum CpuModel { 9 | Host, 10 | Pro, 11 | ProWithHost, 12 | } 13 | 14 | impl Display for CpuModel { 15 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 16 | let v = match self { 17 | Self::Host => "Host", 18 | Self::Pro => "PlayStation 4 Pro", 19 | Self::ProWithHost => "PlayStation 4 Pro (Host Features)", 20 | }; 21 | 22 | f.write_str(v) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gui/src/profile/display.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt::{Display, Formatter}; 3 | 4 | /// Display resolution to report to the kernel. 5 | #[derive(Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] 6 | pub enum DisplayResolution { 7 | /// 1280 × 720. 8 | Hd, 9 | /// 1920 × 1080. 10 | FullHd, 11 | /// 3840 × 2160. 12 | UltraHd, 13 | } 14 | 15 | impl Display for DisplayResolution { 16 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 17 | let v = match self { 18 | Self::Hd => "1280 × 720", 19 | Self::FullHd => "1920 × 1080", 20 | Self::UltraHd => "3840 × 2160", 21 | }; 22 | 23 | f.write_str(v) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gui/src/settings/mod.rs: -------------------------------------------------------------------------------- 1 | use minicbor_serde::error::DecodeError; 2 | use serde::{Deserialize, Serialize}; 3 | use std::cell::Cell; 4 | use std::fs::File; 5 | use std::io::{ErrorKind, Write}; 6 | use std::path::Path; 7 | use thiserror::Error; 8 | 9 | /// Contains application settings. 10 | #[derive(Default, Serialize, Deserialize)] 11 | pub struct Settings { 12 | graphics_debug_layer: Cell, 13 | } 14 | 15 | impl Settings { 16 | pub fn load(path: impl AsRef) -> Result { 17 | // Open file. 18 | let path = path.as_ref(); 19 | let data = match std::fs::read(path) { 20 | Ok(v) => v, 21 | Err(e) if e.kind() == ErrorKind::NotFound => return Ok(Self::default()), 22 | Err(e) => return Err(SettingsError::ReadFile(e)), 23 | }; 24 | 25 | // Read file. 26 | let data = match minicbor_serde::from_slice(&data) { 27 | Ok(v) => v, 28 | Err(e) => return Err(SettingsError::LoadFile(e)), 29 | }; 30 | 31 | Ok(data) 32 | } 33 | 34 | pub fn graphics_debug_layer(&self) -> bool { 35 | self.graphics_debug_layer.get() 36 | } 37 | 38 | pub fn set_graphics_debug_layer(&self, v: bool) { 39 | self.graphics_debug_layer.set(v); 40 | } 41 | 42 | pub fn save(&self, path: impl AsRef) -> Result<(), SettingsError> { 43 | let path = path.as_ref(); 44 | let mut file = match File::create(&path) { 45 | Ok(v) => v, 46 | Err(e) => return Err(SettingsError::CreateFile(e)), 47 | }; 48 | 49 | file.write_all(&minicbor_serde::to_vec(self).unwrap()) 50 | .map_err(SettingsError::WriteFile) 51 | } 52 | } 53 | 54 | /// Represents an error when [`Settings`] fails to load or save. 55 | #[derive(Debug, Error)] 56 | pub enum SettingsError { 57 | #[error("couldn't read the file")] 58 | ReadFile(#[source] std::io::Error), 59 | 60 | #[error("couldn't load the file")] 61 | LoadFile(#[source] DecodeError), 62 | 63 | #[error("couldn't create the file")] 64 | CreateFile(#[source] std::io::Error), 65 | 66 | #[error("couldn't write the file")] 67 | WriteFile(#[source] std::io::Error), 68 | } 69 | -------------------------------------------------------------------------------- /gui/src/setup/linux.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind; 2 | use std::path::PathBuf; 3 | use thiserror::Error; 4 | use xdg::BaseDirectories; 5 | 6 | pub fn read_data_root() -> Result, DataRootError> { 7 | // Read config file. 8 | let file = get_config_path()?; 9 | let mut path = match std::fs::read_to_string(&file) { 10 | Ok(v) => v, 11 | Err(e) if e.kind() == ErrorKind::NotFound => return Ok(None), 12 | Err(e) => return Err(DataRootError::ReadFile(file, e)), 13 | }; 14 | 15 | // Trim trailing whitespaces before leading whitespaces so the latter don't need to move 16 | // trailing whitespaces that going to remove anyway. 17 | let count = path.chars().rev().take_while(|c| c.is_whitespace()).count(); 18 | 19 | for _ in 0..count { 20 | path.pop(); 21 | } 22 | 23 | // Trim leading whitespaces. 24 | let count = path.chars().take_while(|c| c.is_whitespace()).count(); 25 | 26 | for _ in 0..count { 27 | path.remove(0); 28 | } 29 | 30 | Ok(Some(path)) 31 | } 32 | 33 | pub fn write_data_root(path: impl AsRef) -> Result<(), DataRootError> { 34 | let file = get_config_path()?; 35 | 36 | if let Err(e) = std::fs::write(&file, path.as_ref()) { 37 | return Err(DataRootError::WriteFile(file, e)); 38 | } 39 | 40 | Ok(()) 41 | } 42 | 43 | fn get_config_path() -> Result { 44 | BaseDirectories::new() 45 | .map(|xdg| { 46 | let mut p = xdg.get_config_home(); 47 | p.push("obliteration.conf"); 48 | p 49 | }) 50 | .map_err(DataRootError::XdgBaseDirectory) 51 | } 52 | 53 | /// Represents an error when read or write data root fails. 54 | #[derive(Debug, Error)] 55 | pub enum DataRootError { 56 | #[error("couldn't load XDG Base Directory")] 57 | XdgBaseDirectory(#[source] xdg::BaseDirectoriesError), 58 | 59 | #[error("couldn't read {0}")] 60 | ReadFile(PathBuf, #[source] std::io::Error), 61 | 62 | #[error("couldn't write {0}")] 63 | WriteFile(PathBuf, #[source] std::io::Error), 64 | } 65 | -------------------------------------------------------------------------------- /gui/src/setup/macos.rs: -------------------------------------------------------------------------------- 1 | use core_foundation::base::TCFType; 2 | use core_foundation::propertylist::{CFPropertyList, CFPropertyListSubClass}; 3 | use core_foundation::string::CFString; 4 | use core_foundation_sys::preferences::{ 5 | CFPreferencesAppSynchronize, CFPreferencesCopyAppValue, CFPreferencesSetAppValue, 6 | kCFPreferencesCurrentApplication, 7 | }; 8 | use thiserror::Error; 9 | 10 | pub fn read_data_root() -> Result, DataRootError> { 11 | // Read value. 12 | let val = KEY.with(|k| unsafe { 13 | CFPreferencesCopyAppValue(k.as_concrete_TypeRef(), kCFPreferencesCurrentApplication) 14 | }); 15 | 16 | if val.is_null() { 17 | return Ok(None); 18 | } 19 | 20 | // Convert value. 21 | let val = unsafe { CFPropertyList::wrap_under_create_rule(val) }; 22 | 23 | val.downcast_into::() 24 | .ok_or_else(|| KEY.with(|k| DataRootError::InvalidPreferenceValue(k.to_string()))) 25 | .map(|v| Some(v.to_string())) 26 | } 27 | 28 | pub fn write_data_root(path: impl AsRef) -> Result<(), DataRootError> { 29 | // Write value. 30 | let v = CFString::new(path.as_ref()).into_CFPropertyList(); 31 | 32 | KEY.with(|k| unsafe { 33 | CFPreferencesSetAppValue( 34 | k.as_concrete_TypeRef(), 35 | v.as_concrete_TypeRef(), 36 | kCFPreferencesCurrentApplication, 37 | ) 38 | }); 39 | 40 | // Writes to permanent storage. 41 | if unsafe { CFPreferencesAppSynchronize(kCFPreferencesCurrentApplication) == 0 } { 42 | return Err(DataRootError::SynchronizePreferences); 43 | } 44 | 45 | Ok(()) 46 | } 47 | 48 | /// Represents an error when read or write data root fails. 49 | #[derive(Debug, Error)] 50 | pub enum DataRootError { 51 | #[error("invalid value for preference {0}")] 52 | InvalidPreferenceValue(String), 53 | 54 | #[error("couldn't synchronize preferences")] 55 | SynchronizePreferences, 56 | } 57 | 58 | thread_local! { 59 | static KEY: CFString = CFString::from_static_string("DataRoot"); 60 | } 61 | -------------------------------------------------------------------------------- /gui/src/ui/linux/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::dialogs::*; 2 | 3 | use self::modal::Modal; 4 | use super::backend::ProtocolSpecific; 5 | use super::{DesktopExt, DesktopWindow, SlintBackend}; 6 | use thiserror::Error; 7 | 8 | mod dialogs; 9 | mod modal; 10 | mod wayland; 11 | mod x11; 12 | 13 | impl DesktopExt for T { 14 | type Modal<'a, P> 15 | = Modal<'a, Self, P> 16 | where 17 | P: DesktopWindow + 'a; 18 | 19 | fn set_center(&self) -> Result<(), PlatformError> { 20 | let back = wae::global::().unwrap(); 21 | 22 | match back.protocol_specific() { 23 | Some(ProtocolSpecific::Wayland(_)) => {} // Wayland doesn't allow windows to position themselves. 24 | Some(ProtocolSpecific::X11(x11)) => unsafe { 25 | self::x11::set_center(&x11, self)?; 26 | }, 27 | None => unimplemented!(), 28 | }; 29 | 30 | Ok(()) 31 | } 32 | 33 | fn set_modal

(self, parent: &P) -> Result, PlatformError> 34 | where 35 | P: DesktopWindow, 36 | Self: Sized, 37 | { 38 | let back = wae::global::().unwrap(); 39 | 40 | let wayland = match back.protocol_specific() { 41 | Some(ProtocolSpecific::Wayland(wayland)) => unsafe { 42 | self::wayland::set_modal(wayland, &self, parent).map(Some)? 43 | }, 44 | Some(ProtocolSpecific::X11(x11)) => unsafe { 45 | self::x11::set_modal(&x11, &self, parent)?; 46 | 47 | None 48 | }, 49 | None => None, 50 | }; 51 | 52 | Ok(Modal::new(self, parent, wayland)) 53 | } 54 | } 55 | 56 | /// Linux-specific error for [`DesktopExt`]. 57 | #[derive(Debug, Error)] 58 | pub enum PlatformError { 59 | #[error("couldn't create xdg_dialog_v1")] 60 | CreateXdgDialogV1(#[source] wayland_client::DispatchError), 61 | 62 | #[error("couldn't set window modal")] 63 | SetModal(#[source] wayland_client::DispatchError), 64 | 65 | #[error("couldn't set window type")] 66 | XcbSetWindowType(#[source] xcb::ProtocolError), 67 | 68 | #[error("couldn't set window wm state")] 69 | XcbSetWmState(#[source] xcb::ProtocolError), 70 | 71 | #[error("couldn't set window parent")] 72 | XcbSetParent(#[source] xcb::ProtocolError), 73 | 74 | #[error("couldn't get window geometry")] 75 | XcbGetGeometry(#[source] xcb::Error), 76 | 77 | #[error("couldn't center window")] 78 | XcbCenterWindow(#[source] xcb::ProtocolError), 79 | 80 | #[error("couldn't get window attributes")] 81 | XlibGetWindowAttributes, 82 | } 83 | -------------------------------------------------------------------------------- /gui/src/ui/linux/modal.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::DesktopWindow; 2 | use std::ops::Deref; 3 | use wae::Blocker; 4 | use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1; 5 | 6 | /// Encapsulates a modal window and its parent. 7 | /// 8 | /// This struct forces the modal window to be dropped before its parent. 9 | pub struct Modal<'a, W, P: DesktopWindow> { 10 | window: W, 11 | wayland: Option, 12 | #[allow(dead_code)] 13 | blocker: Blocker<'a, P>, 14 | } 15 | 16 | impl<'a, W, P: DesktopWindow> Modal<'a, W, P> { 17 | pub fn new(window: W, parent: &'a P, wayland: Option) -> Self { 18 | Self { 19 | window, 20 | wayland, 21 | blocker: wae::block(parent), 22 | } 23 | } 24 | } 25 | 26 | impl<'a, W, P: DesktopWindow> Drop for Modal<'a, W, P> { 27 | fn drop(&mut self) { 28 | if let Some(v) = self.wayland.take() { 29 | v.destroy(); 30 | } 31 | } 32 | } 33 | 34 | impl<'a, W, P: DesktopWindow> Deref for Modal<'a, W, P> { 35 | type Target = W; 36 | 37 | fn deref(&self) -> &Self::Target { 38 | &self.window 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /gui/src/ui/linux/wayland.rs: -------------------------------------------------------------------------------- 1 | use super::PlatformError; 2 | use crate::ui::DesktopWindow; 3 | use crate::ui::backend::Wayland; 4 | use std::ptr::null_mut; 5 | use wayland_backend::sys::client::ObjectId; 6 | use wayland_client::Proxy; 7 | use wayland_protocols::xdg::dialog::v1::client::xdg_dialog_v1::XdgDialogV1; 8 | use wayland_protocols::xdg::shell::client::xdg_toplevel::XdgToplevel; 9 | 10 | /// # Safety 11 | /// `parent` must outlive `target`. 12 | pub unsafe fn set_modal( 13 | wayland: &Wayland, 14 | target: &impl DesktopWindow, 15 | parent: &impl DesktopWindow, 16 | ) -> Result { 17 | // Get xdg_toplevel for parent. 18 | let mut queue = wayland.queue().borrow_mut(); 19 | let mut state = wayland.state().borrow_mut(); 20 | let qh = queue.handle(); 21 | let parent = unsafe { get_xdg_toplevel(wayland, parent) }; 22 | 23 | // Get xdg_dialog_v1. 24 | let target = unsafe { get_xdg_toplevel(wayland, target) }; 25 | let dialog = state.xdg_dialog().get_xdg_dialog(&target, &qh, ()); 26 | 27 | queue 28 | .roundtrip(&mut state) 29 | .map_err(PlatformError::CreateXdgDialogV1)?; 30 | 31 | // Set modal. 32 | target.set_parent(Some(&parent)); 33 | dialog.set_modal(); 34 | 35 | queue 36 | .roundtrip(&mut state) 37 | .map_err(PlatformError::SetModal)?; 38 | 39 | Ok(dialog) 40 | } 41 | 42 | /// # Safety 43 | /// `win` must outlive the returned [`XdgToplevel`]. 44 | unsafe fn get_xdg_toplevel(wayland: &Wayland, win: &impl DesktopWindow) -> XdgToplevel { 45 | let obj = win.xdg_toplevel().map(|v| v.as_ptr()).unwrap_or(null_mut()); 46 | let obj = unsafe { ObjectId::from_ptr(XdgToplevel::interface(), obj.cast()).unwrap() }; 47 | 48 | XdgToplevel::from_id(wayland.connection(), obj).unwrap() 49 | } 50 | -------------------------------------------------------------------------------- /gui/src/ui/macos/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::dialogs::*; 2 | 3 | use self::modal::Modal; 4 | use self::view::get_window; 5 | use super::{DesktopExt, DesktopWindow}; 6 | use block2::RcBlock; 7 | use objc2::msg_send; 8 | use std::ffi::c_long; 9 | use std::ops::Deref; 10 | use thiserror::Error; 11 | 12 | mod dialogs; 13 | mod modal; 14 | mod view; 15 | 16 | impl DesktopExt for T { 17 | type Modal<'a, P> 18 | = Modal<'a, Self, P> 19 | where 20 | P: DesktopWindow + 'a; 21 | 22 | fn set_center(&self) -> Result<(), PlatformError> { 23 | let win = get_window(self.handle()); 24 | let _: () = unsafe { msg_send![win, center] }; 25 | Ok(()) 26 | } 27 | 28 | fn set_modal

(self, parent: &P) -> Result, PlatformError> 29 | where 30 | P: DesktopWindow, 31 | Self: Sized, 32 | { 33 | // Setup completionHandler. 34 | let cb = RcBlock::new(move |_: c_long| {}); 35 | 36 | // Show the sheet. 37 | let w = get_window(self.handle()); 38 | let p = get_window(parent.handle()); 39 | let _: () = unsafe { msg_send![p, beginSheet:w, completionHandler:cb.deref()] }; 40 | 41 | Ok(Modal::new(self, parent)) 42 | } 43 | } 44 | 45 | /// macOS-specific error for [`DesktopExt`]. 46 | #[derive(Debug, Error)] 47 | pub enum PlatformError {} 48 | -------------------------------------------------------------------------------- /gui/src/ui/macos/modal.rs: -------------------------------------------------------------------------------- 1 | use super::view::get_window; 2 | use crate::ui::DesktopWindow; 3 | use objc2::msg_send; 4 | use std::ops::Deref; 5 | use wae::Blocker; 6 | 7 | /// Encapsulates a modal window and its parent. 8 | /// 9 | /// This struct forces the modal window to be dropped before its parent. 10 | pub struct Modal<'a, W, P> 11 | where 12 | W: DesktopWindow, 13 | P: DesktopWindow, 14 | { 15 | window: W, 16 | parent: &'a P, 17 | #[allow(dead_code)] 18 | blocker: Blocker<'a, P>, 19 | } 20 | 21 | impl<'a, W, P> Modal<'a, W, P> 22 | where 23 | W: DesktopWindow, 24 | P: DesktopWindow, 25 | { 26 | pub(super) fn new(window: W, parent: &'a P) -> Self { 27 | Self { 28 | window, 29 | parent, 30 | blocker: wae::block(parent), 31 | } 32 | } 33 | } 34 | 35 | impl<'a, W, P> Drop for Modal<'a, W, P> 36 | where 37 | W: DesktopWindow, 38 | P: DesktopWindow, 39 | { 40 | fn drop(&mut self) { 41 | let w = get_window(self.window.handle()); 42 | let p = get_window(self.parent.handle()); 43 | let _: () = unsafe { msg_send![p, endSheet:w] }; 44 | } 45 | } 46 | 47 | impl<'a, W, P> Deref for Modal<'a, W, P> 48 | where 49 | W: DesktopWindow, 50 | P: DesktopWindow, 51 | { 52 | type Target = W; 53 | 54 | fn deref(&self) -> &Self::Target { 55 | &self.window 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /gui/src/ui/macos/view.rs: -------------------------------------------------------------------------------- 1 | use objc2::msg_send; 2 | use objc2::runtime::NSObject; 3 | use raw_window_handle::{HasWindowHandle, RawWindowHandle}; 4 | 5 | /// The returned `NSWindow` will be valid while `win` still alive. 6 | pub fn get_window(win: impl HasWindowHandle) -> *mut NSObject { 7 | let win = get_view(win); 8 | 9 | unsafe { msg_send![win, window] } 10 | } 11 | 12 | /// The returned `NSView` will be valid while `win` still alive. 13 | pub fn get_view(win: impl HasWindowHandle) -> *mut NSObject { 14 | let win = win.window_handle().unwrap(); 15 | 16 | match win.as_ref() { 17 | RawWindowHandle::AppKit(v) => v.ns_view.as_ptr().cast(), 18 | _ => unreachable!(), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gui/src/ui/windows/modal.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::DesktopWindow; 2 | use std::ops::Deref; 3 | use wae::Blocker; 4 | 5 | /// Encapsulates a modal window and its parent. 6 | /// 7 | /// This struct forces the modal window to be dropped before its parent. 8 | pub struct Modal<'a, W, P: DesktopWindow> { 9 | window: W, 10 | #[allow(dead_code)] 11 | blocker: Blocker<'a, P>, 12 | } 13 | 14 | impl<'a, W, P: DesktopWindow> Modal<'a, W, P> { 15 | pub(super) fn new(window: W, parent: &'a P) -> Self { 16 | Self { 17 | window, 18 | blocker: wae::block(parent), 19 | } 20 | } 21 | } 22 | 23 | impl<'a, W, P: DesktopWindow> Deref for Modal<'a, W, P> { 24 | type Target = W; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | &self.window 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /gui/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::channel::*; 2 | 3 | mod channel; 4 | -------------------------------------------------------------------------------- /gui/src/vfs/mod.rs: -------------------------------------------------------------------------------- 1 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 2 | use redb::{TableDefinition, TypeName}; 3 | 4 | pub const FS_TYPE: TableDefinition<(), FsType> = TableDefinition::new("fs_type"); 5 | 6 | /// Filesystem type. 7 | #[repr(u16)] 8 | #[derive(Debug, Clone, Copy, IntoPrimitive, TryFromPrimitive)] 9 | pub enum FsType { 10 | ExFat = 1, 11 | } 12 | 13 | impl redb::Value for FsType { 14 | type SelfType<'a> = Self; 15 | type AsBytes<'a> = [u8; 2]; 16 | 17 | fn fixed_width() -> Option { 18 | Some(size_of::()) 19 | } 20 | 21 | fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a> 22 | where 23 | Self: 'a, 24 | { 25 | u16::from_le_bytes(data.try_into().unwrap()) 26 | .try_into() 27 | .unwrap() 28 | } 29 | 30 | fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a> 31 | where 32 | Self: 'a, 33 | Self: 'b, 34 | { 35 | u16::from(*value).to_le_bytes() 36 | } 37 | 38 | fn type_name() -> TypeName { 39 | TypeName::new("obliteration::FsType") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /gui/src/vmm/cpu/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use crate::hw::DeviceContext; 3 | use hv::Cpu; 4 | use std::collections::BTreeMap; 5 | use std::num::NonZero; 6 | use thiserror::Error; 7 | 8 | pub mod debug; 9 | 10 | /// Contains instantiated device context for a CPU. 11 | pub struct Device<'a, C: Cpu> { 12 | pub context: Box + 'a>, 13 | pub end: NonZero, 14 | pub name: &'a str, 15 | } 16 | 17 | impl<'a, C: Cpu> Device<'a, C> { 18 | pub fn insert( 19 | tree: &mut BTreeMap, 20 | dev: &'a T, 21 | f: impl FnOnce(&'a T) -> Box + 'a>, 22 | ) { 23 | let addr = dev.addr(); 24 | let dev = Self { 25 | context: f(dev), 26 | end: dev.len().checked_add(addr).unwrap(), 27 | name: dev.name(), 28 | }; 29 | 30 | assert!(tree.insert(addr, dev).is_none()); 31 | } 32 | } 33 | 34 | /// Implementation of [`gdbstub::target::Target::Error`]. 35 | #[derive(Debug, Error)] 36 | pub enum GdbError { 37 | #[error("the main CPU exited")] 38 | MainCpuExited, 39 | 40 | #[error("CPU not found")] 41 | CpuNotFound, 42 | } 43 | -------------------------------------------------------------------------------- /gui/src/vmm/kernel/note.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes}; 2 | use std::cmp::min; 3 | use thiserror::Error; 4 | 5 | /// Iterator to enumerate ELF notes. 6 | pub struct Notes(Bytes); 7 | 8 | impl Notes { 9 | pub(super) fn new(data: impl Into) -> Self { 10 | Self(data.into()) 11 | } 12 | } 13 | 14 | impl Iterator for Notes { 15 | type Item = Result; 16 | 17 | fn next(&mut self) -> Option { 18 | // Check remaining data. 19 | let hdr = 4 * 3; 20 | 21 | if self.0.is_empty() { 22 | return None; 23 | } else if self.0.len() < hdr { 24 | return Some(Err(NoteError::InvalidHeader)); 25 | } 26 | 27 | // Parse header. 28 | let mut hdr = self.0.split_to(hdr); 29 | let nlen: usize = hdr.get_u32_ne().try_into().unwrap(); 30 | let dlen: usize = hdr.get_u32_ne().try_into().unwrap(); 31 | let ty = hdr.get_u32_ne(); 32 | 33 | if nlen == 0 { 34 | // Name is null-terminated so it should have at least 1 byte. 35 | return Some(Err(NoteError::InvalidName)); 36 | } else if nlen > self.0.len() { 37 | return Some(Err(NoteError::InvalidHeader)); 38 | } 39 | 40 | // Get name. 41 | let mut name = self.0.split_to(nlen); 42 | let len = nlen - 1; 43 | 44 | if name.iter().position(|&b| b == 0) != Some(len) { 45 | return Some(Err(NoteError::InvalidName)); 46 | } 47 | 48 | name.truncate(len); 49 | 50 | // Skip alignment. 51 | let skip = nlen.next_multiple_of(4) - nlen; 52 | 53 | self.0.advance(min(skip, self.0.len())); 54 | 55 | if dlen > self.0.len() { 56 | return Some(Err(NoteError::InvalidHeader)); 57 | } 58 | 59 | // Get description. 60 | let desc = self.0.split_to(dlen); 61 | let skip = dlen.next_multiple_of(4) - dlen; 62 | 63 | self.0.advance(min(skip, self.0.len())); 64 | 65 | Some(Ok(Note { name, desc, ty })) 66 | } 67 | } 68 | 69 | /// Parsed ELF program header. 70 | pub struct Note { 71 | pub name: Bytes, 72 | pub desc: Bytes, 73 | pub ty: u32, 74 | } 75 | 76 | /// Represents an error when [`Notes`] fails to enumerate next ELF note. 77 | #[derive(Debug, Error)] 78 | pub enum NoteError { 79 | #[error("invalid header")] 80 | InvalidHeader, 81 | 82 | #[error("invalid name")] 83 | InvalidName, 84 | } 85 | -------------------------------------------------------------------------------- /gui/src/vmm/kernel/segment.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use std::fs::File; 3 | use std::io::Read; 4 | use std::iter::FusedIterator; 5 | use thiserror::Error; 6 | 7 | pub const PT_LOAD: u32 = 1; 8 | pub const PT_DYNAMIC: u32 = 2; 9 | pub const PT_NOTE: u32 = 4; 10 | pub const PT_PHDR: u32 = 6; 11 | pub const PT_GNU_EH_FRAME: u32 = 0x6474e550; 12 | pub const PT_GNU_STACK: u32 = 0x6474e551; 13 | pub const PT_GNU_RELRO: u32 = 0x6474e552; 14 | 15 | /// Iterator to enumerate ELF program headers. 16 | pub struct ProgramHeaders<'a> { 17 | file: &'a mut File, 18 | start: u64, 19 | count: u64, 20 | parsed: u64, 21 | } 22 | 23 | impl<'a> ProgramHeaders<'a> { 24 | pub(super) fn new(file: &'a mut File, start: u64, count: u64) -> Self { 25 | Self { 26 | file, 27 | start, 28 | count, 29 | parsed: 0, 30 | } 31 | } 32 | } 33 | 34 | impl Iterator for ProgramHeaders<'_> { 35 | type Item = Result; 36 | 37 | fn next(&mut self) -> Option { 38 | // Check remaining. 39 | if self.parsed == self.count { 40 | return None; 41 | } 42 | 43 | // Read data. 44 | let mut data = [0u8; 56]; 45 | 46 | if let Err(e) = self.file.read_exact(&mut data) { 47 | return Some(Err(ProgramHeaderError::ReadFailed( 48 | self.start + self.parsed * 56, 49 | e, 50 | ))); 51 | } 52 | 53 | // Parse data. 54 | let p_type = u32::from_ne_bytes(data[..4].try_into().unwrap()); 55 | let p_offset = u64::from_ne_bytes(data[8..16].try_into().unwrap()); 56 | let p_vaddr = usize::from_ne_bytes(data[16..24].try_into().unwrap()); 57 | let p_filesz = usize::from_ne_bytes(data[32..40].try_into().unwrap()); 58 | let p_memsz = usize::from_ne_bytes(data[40..48].try_into().unwrap()); 59 | 60 | self.parsed += 1; 61 | 62 | Some(Ok(ProgramHeader { 63 | p_type, 64 | p_offset, 65 | p_vaddr, 66 | p_filesz, 67 | p_memsz, 68 | })) 69 | } 70 | } 71 | 72 | impl FusedIterator for ProgramHeaders<'_> {} 73 | 74 | /// Parsed ELF program header. 75 | pub struct ProgramHeader { 76 | pub p_type: u32, 77 | pub p_offset: u64, 78 | pub p_vaddr: usize, 79 | pub p_filesz: usize, 80 | pub p_memsz: usize, 81 | } 82 | 83 | /// Represents an error when [`ProgramHeaders`] fails to enumerate an ELF header. 84 | #[derive(Debug, Error)] 85 | pub enum ProgramHeaderError { 86 | #[error("couldn't read 56 bytes at offset {0}")] 87 | ReadFailed(u64, #[source] std::io::Error), 88 | } 89 | -------------------------------------------------------------------------------- /gui/ui/about.slint: -------------------------------------------------------------------------------- 1 | import { AboutSlint, VerticalBox, StandardButton } from "std-widgets.slint"; 2 | import { TabBar, TabContainer } from "@root/widgets/tab.slint"; 3 | 4 | component Obliteration { 5 | VerticalBox { 6 | Image { 7 | source: @image-url("@root/assets/logo.png"); 8 | } 9 | 10 | Text { 11 | text: "Obliteration is a free and open-source software to run PlayStation 4 system software on PC."; 12 | wrap: word-wrap; 13 | } 14 | } 15 | } 16 | 17 | component Slint { 18 | AboutSlint { } 19 | } 20 | 21 | export component AboutWindow inherits Dialog { 22 | title: "About"; 23 | width: 500px; 24 | height: 245px; 25 | 26 | TabContainer { 27 | tab := TabBar { 28 | tabs: [ 29 | { text: "Obliteration", icon: @image-url("@root/assets/icon.png") }, 30 | { 31 | text: "Slint", 32 | icon: @image-url("@root/assets/slint-logo-small-dark.svg"), 33 | colorize-icon: true 34 | } 35 | ]; 36 | } 37 | 38 | if tab.current-page == 0: Obliteration { } 39 | 40 | if tab.current-page == 1: Slint { } 41 | } 42 | 43 | StandardButton { 44 | kind: close; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /gui/ui/assets/cpu-64-bit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/gui/ui/assets/icon.png -------------------------------------------------------------------------------- /gui/ui/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/obhq/obliteration/5842d0ff44ef5ddf6848cf01f239ef754b74102e/gui/ui/assets/logo.png -------------------------------------------------------------------------------- /gui/ui/assets/monitor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/assets/slint-logo-small-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gui/ui/assets/sony-playstation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/close-octagon-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/debug.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox } from "std-widgets.slint"; 2 | 3 | export component WaitForDebugger inherits Window { 4 | in property address; 5 | 6 | title: "Obliteration"; 7 | icon: @image-url("@root/assets/icon.png"); 8 | width: 400px; 9 | height: 50px; 10 | 11 | VerticalBox { 12 | alignment: center; 13 | 14 | Text { 15 | text: "Waiting for debugger at \{address}."; 16 | horizontal-alignment: center; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gui/ui/error.slint: -------------------------------------------------------------------------------- 1 | import { Palette, HorizontalBox, StandardButton } from "std-widgets.slint"; 2 | 3 | component Content inherits Rectangle { 4 | in property message; 5 | 6 | background: Palette.background; 7 | 8 | HorizontalBox { 9 | VerticalLayout { 10 | alignment: start; 11 | 12 | Image { 13 | source: @image-url("close-octagon-outline.svg"); 14 | colorize: Palette.foreground; 15 | width: 50px; 16 | height: 50px; 17 | } 18 | } 19 | 20 | Text { 21 | text: message; 22 | wrap: word-wrap; 23 | } 24 | } 25 | } 26 | 27 | component ActionBar inherits Rectangle { 28 | callback close(); 29 | 30 | background: Palette.alternate-background; 31 | 32 | HorizontalBox { 33 | alignment: end; 34 | 35 | StandardButton { 36 | kind: StandardButtonKind.close; 37 | clicked => { 38 | close(); 39 | } 40 | } 41 | } 42 | } 43 | 44 | export component ErrorWindow inherits Window { 45 | in property message; 46 | 47 | pure callback close(); 48 | 49 | title: "Obliteration"; 50 | icon: @image-url("@root/assets/icon.png"); 51 | min-width: 400px; 52 | preferred-width: 400px; // Force word-wrap instead of expand the window. 53 | 54 | VerticalLayout { 55 | Content { 56 | message: message; 57 | vertical-stretch: 1; 58 | } 59 | 60 | ActionBar { 61 | close => { 62 | close(); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gui/ui/lib.slint: -------------------------------------------------------------------------------- 1 | export { AboutWindow } from "about.slint"; 2 | export { WaitForDebugger } from "debug.slint"; 3 | export { ErrorWindow } from "error.slint"; 4 | export { MainWindow } from "main.slint"; 5 | export { NewProfile } from "new-profile.slint"; 6 | export { SettingsWindow } from "settings.slint"; 7 | export { InstallFirmware, SetupWizard } from "setup.slint"; 8 | -------------------------------------------------------------------------------- /gui/ui/main/display.slint: -------------------------------------------------------------------------------- 1 | import { ComboBox, HorizontalBox, VerticalBox, GroupBox } from "std-widgets.slint"; 2 | 3 | export component DisplayTab { 4 | in property <[string]> devices; 5 | in-out property selected-device; 6 | in property <[string]> resolutions; 7 | in-out property selected-resolution; 8 | 9 | VerticalBox { 10 | padding-top: 0; 11 | alignment: start; 12 | 13 | HorizontalBox { 14 | padding: 0; 15 | 16 | GroupBox { 17 | title: "Device"; 18 | width: 50%; 19 | ComboBox { 20 | model: root.devices; 21 | current-index <=> root.selected-device; 22 | } 23 | } 24 | 25 | GroupBox { 26 | title: "Resolution"; 27 | ComboBox { 28 | model: root.resolutions; 29 | current-index <=> root.selected-resolution; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /gui/ui/main/idps.slint: -------------------------------------------------------------------------------- 1 | export component IdpsTab { } 2 | -------------------------------------------------------------------------------- /gui/ui/main/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/main/start.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gui/ui/new-profile.slint: -------------------------------------------------------------------------------- 1 | import { StandardButton, GroupBox, LineEdit } from "std-widgets.slint"; 2 | 3 | export component NewProfile inherits Dialog { 4 | out property name; 5 | 6 | title: "New Profile"; 7 | min-width: 400px; 8 | 9 | VerticalLayout { 10 | alignment: start; 11 | 12 | GroupBox { 13 | title: "Name"; 14 | 15 | LineEdit { 16 | text <=> root.name; 17 | } 18 | } 19 | } 20 | 21 | StandardButton { 22 | kind: ok; 23 | } 24 | 25 | StandardButton { 26 | kind: cancel; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gui/ui/settings.slint: -------------------------------------------------------------------------------- 1 | import { StandardButton } from "std-widgets.slint"; 2 | import { TabBar, TabContainer } from "@root/widgets/tab.slint"; 3 | import { GraphicsTab } from "settings/graphics.slint"; 4 | 5 | export component SettingsWindow inherits Dialog { 6 | in property graphics-debug-layer-name: "VK_LAYER_KHRONOS_validation"; 7 | in-out property graphics-debug-layer-checked; 8 | 9 | title: "Settings"; 10 | 11 | TabContainer { 12 | tab := TabBar { 13 | tabs: [ 14 | { text: "Graphics", icon: @image-url("@root/assets/monitor.svg"), colorize-icon: true } 15 | ]; 16 | } 17 | 18 | if tab.current-page == 0: GraphicsTab { 19 | debug-layer-name: root.graphics-debug-layer-name; 20 | debug-layer-checked <=> root.graphics-debug-layer-checked; 21 | } 22 | } 23 | 24 | StandardButton { 25 | kind: ok; 26 | } 27 | 28 | StandardButton { 29 | kind: cancel; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gui/ui/settings/graphics.slint: -------------------------------------------------------------------------------- 1 | import { CheckBox, VerticalBox, GroupBox } from "std-widgets.slint"; 2 | 3 | export component GraphicsTab { 4 | in property debug-layer-name; 5 | in-out property debug-layer-checked; 6 | 7 | VerticalBox { 8 | padding-top: 0; 9 | padding-bottom: 0; 10 | 11 | GroupBox { 12 | title: "Debug"; 13 | 14 | VerticalLayout { 15 | alignment: start; 16 | 17 | CheckBox { 18 | text: "Enable \{debug-layer-name} (restart required)"; 19 | checked <=> root.debug-layer-checked; 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gui/ui/setup/conclusion.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox } from "std-widgets.slint"; 2 | import { Header } from "header.slint"; 3 | 4 | export component Conclusion { 5 | VerticalBox { 6 | Header { 7 | title: "Setup complete"; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /gui/ui/setup/firmware.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox, HorizontalBox, LineEdit, Button, Palette, ProgressIndicator } from "std-widgets.slint"; 2 | import { Header } from "header.slint"; 3 | 4 | export component Firmware { 5 | in-out property firmware-dump <=> input.text; 6 | 7 | pure callback browse(); 8 | 9 | VerticalBox { 10 | Header { 11 | title: "Install Firmware"; 12 | } 13 | 14 | Text { 15 | text: "Select a firmware dump that you got from Firmware Dumper."; 16 | } 17 | 18 | HorizontalBox { 19 | padding: 0; 20 | 21 | input := LineEdit { 22 | placeholder-text: "Path to a firmware dump"; 23 | } 24 | 25 | Button { 26 | text: "..."; 27 | clicked => { 28 | browse(); 29 | } 30 | } 31 | } 32 | 33 | Rectangle { } 34 | } 35 | } 36 | 37 | export component InstallFirmware inherits Window { 38 | in property status; 39 | in property progress; 40 | 41 | title: "Installing Firmware"; 42 | icon: @image-url("@root/assets/icon.png"); 43 | min-width: 400px; 44 | preferred-width: 400px; 45 | min-height: 100px; 46 | preferred-height: 100px; 47 | 48 | VerticalBox { 49 | alignment: center; 50 | 51 | Text { 52 | text: "Installing firmware, please wait."; 53 | horizontal-alignment: center; 54 | } 55 | 56 | ProgressIndicator { 57 | progress: progress; 58 | } 59 | 60 | Text { 61 | text: status; 62 | horizontal-alignment: center; 63 | wrap: no-wrap; 64 | overflow: elide; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gui/ui/setup/header.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox } from "std-widgets.slint"; 2 | 3 | export component Header { 4 | in property title; 5 | 6 | VerticalBox { 7 | padding: 0; 8 | 9 | Text { 10 | text: root.title; 11 | font-size: 30px; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gui/ui/setup/intro.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox, Button } from "std-widgets.slint"; 2 | import { Header } from "header.slint"; 3 | 4 | export component Intro { 5 | pure callback get-dumper(); 6 | 7 | VerticalBox { 8 | Header { 9 | title: "Introduction"; 10 | } 11 | 12 | Text { 13 | text: "This wizard will help you setup Obliteration. To ensure you're ready, make sure you have a firmware dumped from your PlayStation 4 using the Firmware Dumper."; 14 | wrap: word-wrap; 15 | } 16 | 17 | VerticalLayout { 18 | vertical-stretch: 1; 19 | alignment: center; 20 | 21 | HorizontalLayout { 22 | alignment: center; 23 | 24 | Button { 25 | text: "Get Firmware Dumper"; 26 | clicked => { 27 | get-dumper(); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gui/ui/setup/nav.slint: -------------------------------------------------------------------------------- 1 | import { HorizontalBox, Button, StandardButton, Palette } from "std-widgets.slint"; 2 | 3 | export component NavBar inherits Rectangle { 4 | in property back-text; 5 | in property back-enabled; 6 | in property next-text; 7 | 8 | callback back-clicked <=> back.clicked; 9 | callback next-clicked <=> next.clicked; 10 | pure callback cancel(); 11 | 12 | background: Palette.alternate-background; 13 | 14 | HorizontalBox { 15 | alignment: end; 16 | 17 | back := Button { 18 | text: root.back-text; 19 | enabled: root.back-enabled; 20 | } 21 | 22 | next := Button { 23 | text: root.next-text; 24 | } 25 | 26 | StandardButton { 27 | kind: StandardButtonKind.cancel; 28 | clicked => { 29 | cancel(); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gui/ui/setup/root.slint: -------------------------------------------------------------------------------- 1 | import { VerticalBox, HorizontalBox, LineEdit, Button } from "std-widgets.slint"; 2 | import { Header } from "header.slint"; 3 | 4 | export component DataRoot { 5 | in-out property path <=> input.text; 6 | 7 | callback browse(); 8 | 9 | VerticalBox { 10 | Header { 11 | title: "Data Location"; 12 | } 13 | 14 | Text { 15 | text: "Select a directory to store Obliteration data. This directory will be used to store everything, including firmware and games. If you have data from the previous usage you can reuse those data."; 16 | wrap: word-wrap; 17 | } 18 | 19 | HorizontalBox { 20 | padding: 0; 21 | 22 | input := LineEdit { 23 | placeholder-text: "Path to a directory"; 24 | } 25 | 26 | Button { 27 | text: "..."; 28 | clicked => { 29 | browse(); 30 | } 31 | } 32 | } 33 | 34 | Rectangle { } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gui/ui/widgets/tab.slint: -------------------------------------------------------------------------------- 1 | import { HorizontalBox, Palette } from "std-widgets.slint"; 2 | 3 | component Tab { 4 | in property text; 5 | in property icon; 6 | in property colorize-icon; 7 | in property selected; 8 | 9 | callback clicked <=> touch.clicked; 10 | 11 | max-height: l.preferred-height; 12 | 13 | states [ 14 | pressed when touch.pressed && !root.selected: { 15 | state.opacity: 0.8; 16 | } 17 | hover when touch.has-hover && !root.selected: { 18 | state.opacity: 0.6; 19 | } 20 | selected when root.selected: { 21 | state.opacity: 1; 22 | } 23 | ] 24 | 25 | state := Rectangle { 26 | opacity: 0; 27 | background: Palette.background.darker(0.1); 28 | border-top-left-radius: 4px; 29 | border-top-right-radius: 4px; 30 | 31 | animate opacity { duration: 150ms; } 32 | } 33 | 34 | l := HorizontalBox { 35 | alignment: center; 36 | 37 | Image { 38 | source: root.icon; 39 | width: 15px; 40 | colorize: root.colorize-icon ? Palette.control-foreground : transparent; 41 | } 42 | 43 | Text { 44 | text: root.text; 45 | } 46 | } 47 | 48 | touch := TouchArea { 49 | width: 100%; 50 | height: 100%; 51 | } 52 | } 53 | 54 | export component TabContainer { 55 | Rectangle { 56 | background: Palette.background.darker(0.1); 57 | border-width: 1px; 58 | border-color: Palette.border; 59 | border-radius: 4px; 60 | 61 | VerticalLayout { 62 | padding: 1px; 63 | 64 | @children 65 | } 66 | } 67 | } 68 | 69 | export component TabBar { 70 | in property <[{text: string, icon: image, colorize-icon: bool}]> tabs; 71 | out property current-page; 72 | 73 | Rectangle { 74 | background: Palette.border; 75 | border-top-left-radius: 4px; 76 | border-top-right-radius: 4px; 77 | } 78 | 79 | HorizontalLayout { 80 | for tab[index] in root.tabs: Tab { 81 | text: tab.text; 82 | icon: tab.icon; 83 | colorize-icon: tab.colorize-icon; 84 | selected: index == root.current-page; 85 | clicked => { 86 | root.current-page = index; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /kernel/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-none-softfloat] 2 | rustflags = ["-C", "relocation-model=pic"] 3 | -------------------------------------------------------------------------------- /kernel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obkrnl" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bitfield-struct = "0.10.1" 8 | bitflag = { path = "../lib/bitflag" } 9 | config = { path = "../config" } 10 | hashbrown = "0.14.5" 11 | humansize = { version = "2.1.3", features = ["no_alloc"] } 12 | krt = { path = "../lib/krt" } 13 | macros = { path = "../macros" } 14 | talc = { version = "4.4.1", default-features = false } 15 | thiserror = { version = "2.0.12", default-features = false } 16 | 17 | [target.'cfg(target_arch = "x86_64")'.dependencies] 18 | x86-64 = { path = "../lib/x86-64" } 19 | -------------------------------------------------------------------------------- /kernel/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /kernel/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let target = std::env::var("TARGET").unwrap(); 3 | 4 | match target.as_str() { 5 | "aarch64-unknown-none-softfloat" => { 6 | println!("cargo::rustc-link-arg-bins=--pie"); 7 | println!("cargo::rustc-link-arg-bins=-zmax-page-size=0x4000"); 8 | } 9 | "x86_64-unknown-none" => { 10 | println!("cargo::rustc-link-arg-bins=-zmax-page-size=0x1000"); 11 | } 12 | _ => {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /kernel/src/aarch64.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::sync::Arc; 3 | 4 | pub fn identify_cpu() -> CpuInfo { 5 | todo!() 6 | } 7 | 8 | pub unsafe fn setup_main_cpu(cpu: CpuInfo) -> Arc { 9 | todo!() 10 | } 11 | 12 | /// Contains information for CPU on current machine. 13 | pub struct CpuInfo { 14 | pub cpu_vendor: String, 15 | } 16 | 17 | /// Contains architecture-specific configurations obtained from [`setup_main_cpu()`]. 18 | pub struct ArchConfig { 19 | pub secondary_start: &'static [u8], 20 | } 21 | -------------------------------------------------------------------------------- /kernel/src/config/aarch64.rs: -------------------------------------------------------------------------------- 1 | pub const PAGE_SHIFT: usize = 14; // 16K 2 | -------------------------------------------------------------------------------- /kernel/src/config/dipsw.rs: -------------------------------------------------------------------------------- 1 | /// Identifier of a Dipsw. 2 | #[derive(Clone, Copy, PartialEq, Eq)] 3 | pub enum Dipsw { 4 | Unk0 = 0, 5 | DisabledKaslr = 12, 6 | Unk16 = 16, 7 | Unk17 = 17, 8 | Unk24 = 24, 9 | Unk97 = 97, 10 | Unk140 = 140, 11 | Unk146 = 146, 12 | } 13 | -------------------------------------------------------------------------------- /kernel/src/config/x86_64.rs: -------------------------------------------------------------------------------- 1 | // We use 4K 4-Level Paging here. You may wonder about this because it seems like page size on the 2 | // PS4 is 16K. The truth is the PS4 emulate the 16K page size with 4K pages. You can check this by 3 | // yourself by looking at acpi_install_wakeup_handler() function on the PS4 kernel and compare it 4 | // with FreeBSD version. No idea why the PS4 choose to emulate 16K page. 5 | pub const PAGE_SHIFT: usize = 12; 6 | -------------------------------------------------------------------------------- /kernel/src/context/aarch64.rs: -------------------------------------------------------------------------------- 1 | use super::Base; 2 | use crate::arch::ArchConfig; 3 | use crate::proc::Thread; 4 | use core::marker::PhantomPinned; 5 | use core::pin::Pin; 6 | 7 | /// Extended [Base] for AArch64. 8 | #[repr(C)] 9 | pub(super) struct Context { 10 | pub base: Base, // Must be first field. 11 | phantom: PhantomPinned, 12 | } 13 | 14 | impl Context { 15 | pub fn new(base: Base, arch: &ArchConfig) -> Self { 16 | Self { 17 | base, 18 | phantom: PhantomPinned, 19 | } 20 | } 21 | 22 | pub unsafe fn activate(self: Pin<&mut Self>) { 23 | todo!(); 24 | } 25 | 26 | pub unsafe fn load_static_ptr() -> *const T { 27 | todo!() 28 | } 29 | 30 | pub unsafe fn load_ptr() -> *const T { 31 | todo!() 32 | } 33 | 34 | pub unsafe fn load_volatile_usize() -> usize { 35 | todo!() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /kernel/src/context/arc.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | use core::ops::Deref; 3 | 4 | /// Provides an access to the value of [Arc](alloc::sync::Arc) on [Context](super::Context) without 5 | /// cloning a reference. 6 | /// 7 | /// We need this type because return a reference from [Context](super::Context) require static 8 | /// lifetime, which allow the caller to store it at a global level. Once the value is destroyed that 9 | /// reference will be invalid. We can solve this by return a cloned [Arc](alloc::sync::Arc) but most 10 | /// of the time the caller just want a temporary access to the value, which mean the increment and 11 | /// decrement of a reference is not needed so we invent this type to solve this problem. 12 | /// 13 | /// This type work by making itself not [Send] and [Sync], which prevent the caller from storing it 14 | /// at a global level. 15 | #[repr(transparent)] 16 | pub struct BorrowedArc(*const T); // A pointer make this type automatically !Send and !Sync. 17 | 18 | impl BorrowedArc { 19 | /// # Safety 20 | /// `v` must be owned by [Arc](alloc::sync::Arc) if not null. 21 | pub(super) const unsafe fn new(v: *const T) -> Option { 22 | if v.is_null() { None } else { Some(Self(v)) } 23 | } 24 | 25 | /// # Safety 26 | /// `v` must be owned by [Arc](alloc::sync::Arc). 27 | pub(super) const unsafe fn from_non_null(v: *const T) -> Self { 28 | Self(v) 29 | } 30 | 31 | /// Note that this is an associated function, which means that you have to call it as 32 | /// `BorrowedArc::as_ptr(v)` instead of `v.as_ptr()`. This is so that there is no conflict with 33 | /// a method on the inner type. 34 | pub fn as_ptr(this: &Self) -> *const T { 35 | this.0 36 | } 37 | 38 | pub fn into_owned(self) -> Arc { 39 | // SAFETY: This is safe because the requirement of new() and from_non_null(). 40 | unsafe { Arc::increment_strong_count(self.0) }; 41 | unsafe { Arc::from_raw(self.0) } 42 | } 43 | } 44 | 45 | impl Clone for BorrowedArc { 46 | fn clone(&self) -> Self { 47 | *self 48 | } 49 | } 50 | 51 | impl Copy for BorrowedArc {} 52 | 53 | impl Deref for BorrowedArc { 54 | type Target = T; 55 | 56 | fn deref(&self) -> &Self::Target { 57 | unsafe { &*self.0 } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /kernel/src/context/local.rs: -------------------------------------------------------------------------------- 1 | use super::{PinnedContext, config, pin_cpu}; 2 | use alloc::vec::Vec; 3 | use core::ops::Deref; 4 | 5 | /// Encapsulates per-CPU value. 6 | /// 7 | /// `T` need to implement [Send] for this type to implement [Send] and [Sync] because its value 8 | /// created by one thread then access from another thread. 9 | /// 10 | /// Use [RefCell](core::cell::RefCell) if you need interior mutability but it will make that value 11 | /// not safe to access from any interrupt handler. You can't use mutex here because once the thread 12 | /// is pinned it cannot go to sleep. 13 | pub struct CpuLocal(Vec); 14 | 15 | impl CpuLocal { 16 | pub fn new(mut f: impl FnMut(usize) -> T) -> Self { 17 | let len = config().max_cpu().get(); 18 | let mut vec = Vec::with_capacity(len); 19 | 20 | for i in 0..len { 21 | vec.push(f(i)); 22 | } 23 | 24 | Self(vec) 25 | } 26 | 27 | /// The calling thread cannot go to sleep until the returned [`CpuLock`] is dropped. Attempt to 28 | /// call any function that can put the thread to sleep will be panic. 29 | pub fn lock(&self) -> CpuLock { 30 | let pin = pin_cpu(); 31 | let val = &self.0[unsafe { pin.cpu() }]; 32 | 33 | CpuLock { val, pin } 34 | } 35 | } 36 | 37 | unsafe impl Send for CpuLocal {} 38 | unsafe impl Sync for CpuLocal {} 39 | 40 | /// RAII struct to access per-CPU value in [`CpuLocal`]. 41 | pub struct CpuLock<'a, T> { 42 | val: &'a T, 43 | #[allow(dead_code)] 44 | pin: PinnedContext, // Must be dropped last. 45 | } 46 | 47 | impl Deref for CpuLock<'_, T> { 48 | type Target = T; 49 | 50 | fn deref(&self) -> &Self::Target { 51 | self.val 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kernel/src/event/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::ty::*; 2 | 3 | use crate::lock::{Mutex, MutexGuard}; 4 | use alloc::collections::btree_map::BTreeMap; 5 | 6 | mod ty; 7 | 8 | /// Encapsulate a set of [`Event`]. 9 | /// 10 | /// Usually there are only one [`EventSet`] per subsystem. The purpose of this struct is to prevent 11 | /// race condition during subscribing and triggering multiple events. In other words, this struct 12 | /// provide atomicity for subscription to multiple events in the set. 13 | pub struct EventSet(Mutex); // TODO: Change to RwLock. 14 | 15 | impl EventSet { 16 | pub fn trigger(&self) -> EventTrigger { 17 | EventTrigger(self.0.lock()) 18 | } 19 | } 20 | 21 | impl Default for EventSet { 22 | fn default() -> Self { 23 | Self(Mutex::default()) 24 | } 25 | } 26 | 27 | /// Implementation of `eventhandler_list` structure. 28 | /// 29 | /// Our implementation is different from PS4 version to make it idomatic to Rust. 30 | pub struct Event { 31 | subscribers: BTreeMap<(u32, u64), T::Wrapper>, // el_entries 32 | } 33 | 34 | impl Default for Event { 35 | fn default() -> Self { 36 | Self { 37 | subscribers: BTreeMap::new(), 38 | } 39 | } 40 | } 41 | 42 | /// Struct to trigger one or more events. 43 | /// 44 | /// It is guarantee that no other handler in the current set will get registered until this struct 45 | /// has been dropped. 46 | pub struct EventTrigger<'a, S>(MutexGuard<'a, S>); 47 | 48 | impl EventTrigger<'_, S> { 49 | pub fn select(&mut self, event: E) -> impl Iterator 50 | where 51 | E: FnOnce(&S) -> &Event, 52 | T: EventType, 53 | { 54 | event(&self.0).subscribers.values() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /kernel/src/event/ty.rs: -------------------------------------------------------------------------------- 1 | use crate::subsystem::Subsystem; 2 | use alloc::boxed::Box; 3 | use alloc::sync::Arc; 4 | 5 | /// Type of an event. 6 | pub trait EventType: 'static { 7 | type Handler; 8 | type Wrapper: Send + Sync + 'static; 9 | } 10 | 11 | impl EventType for fn(&A) { 12 | type Handler = fn(&Arc, &A); 13 | type Wrapper = Box; 14 | } 15 | 16 | impl EventType for fn(&mut A) { 17 | type Handler = fn(&Arc, &mut A); 18 | type Wrapper = Box; 19 | } 20 | -------------------------------------------------------------------------------- /kernel/src/imgact/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::ps4::*; 2 | 3 | mod ps4; 4 | -------------------------------------------------------------------------------- /kernel/src/imgact/ps4/abi.rs: -------------------------------------------------------------------------------- 1 | use crate::proc::ProcAbi; 2 | 3 | /// Implementation of [`ProcAbi`] for PS4 processes. 4 | pub struct Ps4Abi; 5 | 6 | impl ProcAbi for Ps4Abi { 7 | fn syscall_handler(&self) { 8 | todo!() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /kernel/src/imgact/ps4/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::abi::*; 2 | 3 | mod abi; 4 | -------------------------------------------------------------------------------- /kernel/src/imgfmt/elf/mod.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Deref; 2 | 3 | /// Single ELF note. 4 | #[repr(C)] 5 | pub struct Note { 6 | hdr: NoteHdr, 7 | name: [u8; N], 8 | desc: NoteDesc, 9 | } 10 | 11 | impl Note { 12 | /// # Safety 13 | /// `name` must contains NUL as a last element. 14 | pub const unsafe fn new(name: [u8; N], ty: u32, desc: [u8; D]) -> Self { 15 | Self { 16 | hdr: NoteHdr { 17 | name_len: N as _, 18 | desc_len: D as _, 19 | ty, 20 | }, 21 | name, 22 | desc: NoteDesc(desc), 23 | } 24 | } 25 | } 26 | 27 | /// Implementation of `Elf64_Nhdr` and `Elf32_Nhdr` structure. 28 | #[repr(C)] 29 | pub struct NoteHdr { 30 | /// n_namesz. 31 | pub name_len: u32, 32 | /// n_descsz. 33 | pub desc_len: u32, 34 | /// n_type. 35 | pub ty: u32, 36 | } 37 | 38 | /// Note description. 39 | #[repr(C, align(4))] 40 | pub struct NoteDesc([u8; L]); 41 | 42 | impl Deref for NoteDesc { 43 | type Target = [u8; L]; 44 | 45 | fn deref(&self) -> &Self::Target { 46 | &self.0 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use core::mem::offset_of; 54 | 55 | #[test] 56 | fn note() { 57 | assert_eq!(offset_of!(Note::<3, 1>, name), 12); 58 | assert_eq!(offset_of!(Note::<3, 1>, desc), 16); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /kernel/src/imgfmt/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod elf; 2 | -------------------------------------------------------------------------------- /kernel/src/lock/gutex/guard.rs: -------------------------------------------------------------------------------- 1 | use super::GroupGuard; 2 | use core::fmt::{Display, Formatter}; 3 | use core::ops::{Deref, DerefMut}; 4 | 5 | /// RAII structure used to release the shared read access of a lock when dropped. 6 | pub struct GutexRead<'a, T> { 7 | #[allow(dead_code)] // active and value fields is protected by this lock. 8 | lock: GroupGuard<'a>, 9 | active: *mut usize, 10 | value: *const T, 11 | } 12 | 13 | impl<'a, T> GutexRead<'a, T> { 14 | /// # Safety 15 | /// `active` and `value` must be protected by `lock`. 16 | pub(super) fn new(lock: GroupGuard<'a>, active: *mut usize, value: *const T) -> Self { 17 | Self { 18 | lock, 19 | active, 20 | value, 21 | } 22 | } 23 | } 24 | 25 | impl Drop for GutexRead<'_, T> { 26 | fn drop(&mut self) { 27 | unsafe { *self.active -= 1 }; 28 | } 29 | } 30 | 31 | impl Deref for GutexRead<'_, T> { 32 | type Target = T; 33 | 34 | fn deref(&self) -> &Self::Target { 35 | unsafe { &*self.value } 36 | } 37 | } 38 | 39 | impl Display for GutexRead<'_, T> { 40 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 41 | self.deref().fmt(f) 42 | } 43 | } 44 | 45 | unsafe impl Sync for GutexRead<'_, T> {} 46 | 47 | /// RAII structure used to release the exclusive write access of a lock when dropped. 48 | pub struct GutexWrite<'a, T> { 49 | #[allow(dead_code)] // active and value fields is protected by this lock. 50 | lock: GroupGuard<'a>, 51 | active: *mut usize, 52 | value: *mut T, 53 | } 54 | 55 | impl<'a, T> GutexWrite<'a, T> { 56 | /// # Safety 57 | /// `active` and `value` must be protected by `lock`. 58 | pub(super) unsafe fn new(lock: GroupGuard<'a>, active: *mut usize, value: *mut T) -> Self { 59 | Self { 60 | active, 61 | value, 62 | lock, 63 | } 64 | } 65 | } 66 | 67 | impl Drop for GutexWrite<'_, T> { 68 | fn drop(&mut self) { 69 | unsafe { *self.active = 0 }; 70 | } 71 | } 72 | 73 | impl Deref for GutexWrite<'_, T> { 74 | type Target = T; 75 | 76 | fn deref(&self) -> &Self::Target { 77 | unsafe { &*self.value } 78 | } 79 | } 80 | 81 | impl DerefMut for GutexWrite<'_, T> { 82 | fn deref_mut(&mut self) -> &mut Self::Target { 83 | unsafe { &mut *self.value } 84 | } 85 | } 86 | 87 | impl Display for GutexWrite<'_, T> { 88 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 89 | self.deref().fmt(f) 90 | } 91 | } 92 | 93 | unsafe impl Sync for GutexWrite<'_, T> {} 94 | -------------------------------------------------------------------------------- /kernel/src/lock/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::gutex::*; 2 | pub use self::mutex::*; 3 | 4 | mod gutex; 5 | mod mutex; 6 | 7 | const MTX_UNOWNED: usize = 4; 8 | -------------------------------------------------------------------------------- /kernel/src/proc/abi.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of `sysentvec` structure. 2 | pub trait ProcAbi: Send + Sync { 3 | fn syscall_handler(&self); 4 | } 5 | -------------------------------------------------------------------------------- /kernel/src/proc/cell.rs: -------------------------------------------------------------------------------- 1 | use super::Thread; 2 | use crate::context::{BorrowedArc, current_thread}; 3 | use core::cell::Cell; 4 | 5 | /// Encapsulates a field of [Thread] that can only be accessed by the CPU that currently executing 6 | /// the thread. 7 | /// 8 | /// # Context safety 9 | /// [`Default`] implementation of this type does not require a CPU context as long as implementation 10 | /// on `T` does not. 11 | #[derive(Default)] 12 | pub struct PrivateCell(T); 13 | 14 | impl PrivateCell { 15 | fn validate(&self, owner: &Thread) { 16 | // This check will optimized out for most of the time due to the implementation of 17 | // current_thread() use "pure" + "nomem" on inline assembly. 18 | let current = current_thread(); 19 | 20 | if !core::ptr::eq(BorrowedArc::as_ptr(¤t), owner) { 21 | panic!("accessing a private cell from the other thread is not supported"); 22 | } 23 | } 24 | } 25 | 26 | impl PrivateCell> { 27 | /// See [set] for a safe wrapper. 28 | /// 29 | /// # Safety 30 | /// `owner` must be an owner of this field. 31 | /// 32 | /// # Panics 33 | /// If `owner` is not the current thread. 34 | pub unsafe fn set(&self, owner: &Thread, v: T) { 35 | self.validate(owner); 36 | self.0.set(v); 37 | } 38 | } 39 | 40 | impl PrivateCell> { 41 | /// See [get] for a safe wrapper. 42 | /// 43 | /// # Safety 44 | /// `owner` must be an owner of this field. 45 | /// 46 | /// # Panics 47 | /// If `owner` is not the current thread. 48 | pub unsafe fn get(&self, owner: &Thread) -> T { 49 | self.validate(owner); 50 | self.0.get() 51 | } 52 | } 53 | 54 | unsafe impl Sync for PrivateCell {} 55 | 56 | /// Safe wrapper of [PrivateCell::set()]. 57 | macro_rules! set { 58 | ($t:ident, $f:ident, $v:expr) => { 59 | // SAFETY: $t is an owner of $f. 60 | unsafe { $t.$f.set($t, $v) } 61 | }; 62 | } 63 | 64 | /// Safe wrapper of [PrivateCell::get()]. 65 | macro_rules! get { 66 | ($t:ident, $f:ident) => { 67 | // SAFETY: $t is an owner of $f. 68 | unsafe { $t.$f.get($t) } 69 | }; 70 | } 71 | 72 | pub(super) use get; 73 | pub(super) use set; 74 | -------------------------------------------------------------------------------- /kernel/src/proc/pid.rs: -------------------------------------------------------------------------------- 1 | use core::borrow::Borrow; 2 | use core::ffi::c_int; 3 | 4 | /// Unique identifier of a process. 5 | #[repr(transparent)] 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | pub struct Pid(c_int); 8 | 9 | impl Pid { 10 | pub const KERNEL: Self = Self(0); 11 | pub const IDLE: Self = Self(10); 12 | 13 | /// Returns [`None`] if `v` is negative. 14 | pub const fn new(v: c_int) -> Option { 15 | if v >= 0 { Some(Self(v)) } else { None } 16 | } 17 | } 18 | 19 | impl Borrow for Pid { 20 | fn borrow(&self) -> &c_int { 21 | &self.0 22 | } 23 | } 24 | 25 | impl PartialEq for Pid { 26 | fn eq(&self, other: &c_int) -> bool { 27 | self.0 == *other 28 | } 29 | } 30 | 31 | impl PartialEq for c_int { 32 | fn eq(&self, other: &Pid) -> bool { 33 | *self == other.0 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kernel/src/proc/process.rs: -------------------------------------------------------------------------------- 1 | use super::{ProcAbi, ProcEvents}; 2 | use crate::event::EventSet; 3 | use alloc::sync::Arc; 4 | use core::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | /// Implementation of `proc` structure. 7 | pub struct Proc { 8 | abi: Arc, // p_sysent 9 | pager: AtomicUsize, 10 | } 11 | 12 | impl Proc { 13 | /// See `proc_init` and `proc_ctor` on the Orbis for a reference. 14 | /// 15 | /// # Reference offsets 16 | /// | Version | Offset | 17 | /// |---------|---------------------| 18 | /// |PS4 11.00|0x375970 and 0x3755D0| 19 | pub fn new(abi: Arc, events: &Arc>) -> Arc { 20 | let mut proc = Self { 21 | abi, 22 | pager: AtomicUsize::new(0), 23 | }; 24 | 25 | // Trigger process_init event. 26 | let mut et = events.trigger(); 27 | 28 | for h in et.select(|s| &s.process_init) { 29 | h(&mut proc); 30 | } 31 | 32 | // Trigger process_ctor event. 33 | let proc = Arc::new(proc); 34 | let weak = Arc::downgrade(&proc); 35 | 36 | for h in et.select(|s| &s.process_ctor) { 37 | h(&weak); 38 | } 39 | 40 | drop(et); 41 | 42 | todo!() 43 | } 44 | 45 | /// This function does not do anything except initialize the struct memory. It is the caller 46 | /// responsibility to configure the process after this so it have a proper states and trigger 47 | /// necessary events. 48 | /// 49 | /// # Context safety 50 | /// This function does not require a CPU context. 51 | pub fn new_bare(abi: Arc) -> Self { 52 | Self { 53 | abi, 54 | pager: AtomicUsize::new(0), 55 | } 56 | } 57 | 58 | pub fn abi(&self) -> &Arc { 59 | &self.abi 60 | } 61 | 62 | pub fn pager(&self) -> usize { 63 | self.pager.load(Ordering::Relaxed) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /kernel/src/sched/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::sleep::*; 2 | 3 | mod sleep; 4 | -------------------------------------------------------------------------------- /kernel/src/sched/sleep.rs: -------------------------------------------------------------------------------- 1 | use crate::context::current_thread; 2 | 3 | /// See `_sleep` on the PS4 for a reference. 4 | pub fn sleep() { 5 | // Remove current thread from sleep queue. 6 | let td = current_thread(); 7 | let addr = td.sleeping_mut(); 8 | 9 | if *addr != 0 { 10 | todo!() 11 | } 12 | 13 | todo!() 14 | } 15 | -------------------------------------------------------------------------------- /kernel/src/signal/mod.rs: -------------------------------------------------------------------------------- 1 | /// Value of *nix signal. 2 | #[derive(Debug, Clone, Copy)] 3 | pub struct Signal(u8); 4 | 5 | impl Signal { 6 | const MAX: u8 = 128; // _SIG_MAXSIG 7 | 8 | /// # Panics 9 | /// If `v` is not a valid signal number. 10 | pub const fn from_bits(v: u8) -> Self { 11 | assert!(v <= Self::MAX); 12 | Self(v) 13 | } 14 | 15 | pub const fn into_bits(self) -> u8 { 16 | self.0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /kernel/src/subsystem/mod.rs: -------------------------------------------------------------------------------- 1 | /// Subsystem of the kernel. 2 | /// 3 | /// There should be only one instance for each subsystem. Normally it will live forever until the 4 | /// machine is shutdown. That means it is okay to to leak it. 5 | pub trait Subsystem: Send + Sync + 'static {} 6 | -------------------------------------------------------------------------------- /kernel/src/trap/aarch64.rs: -------------------------------------------------------------------------------- 1 | /// Main entry point for interrupt. 2 | /// 3 | /// This will be called by an inline assembly. 4 | pub extern "C" fn interrupt_handler(_: &mut TrapFrame) { 5 | todo!() 6 | } 7 | 8 | /// Contains states of the interupted program. 9 | #[repr(C)] 10 | pub struct TrapFrame {} 11 | -------------------------------------------------------------------------------- /kernel/src/trap/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::arch::*; 2 | 3 | #[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")] 4 | #[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")] 5 | mod arch; 6 | mod vm; 7 | -------------------------------------------------------------------------------- /kernel/src/trap/vm.rs: -------------------------------------------------------------------------------- 1 | use super::TrapFrame; 2 | use config::Vm; 3 | 4 | /// # Interupt safety 5 | /// This function can be called from interupt handler. 6 | pub fn interrupt_handler(_: &Vm, _: &mut TrapFrame) { 7 | // TODO: Implement a virtual device with GDB stub. 8 | todo!() 9 | } 10 | -------------------------------------------------------------------------------- /kernel/src/trap/x86_64.rs: -------------------------------------------------------------------------------- 1 | use crate::context::current_thread; 2 | use config::BootEnv; 3 | use core::sync::atomic::Ordering; 4 | use krt::boot_env; 5 | 6 | /// Main entry point for interrupt. 7 | /// 8 | /// This will be called by an inline assembly. 9 | /// 10 | /// See `trap` function on the PS4 for a reference. 11 | pub extern "C" fn interrupt_handler(frame: &mut TrapFrame) { 12 | let td = current_thread(); 13 | 14 | unsafe { td.active_interrupts().fetch_add(1, Ordering::Relaxed) }; 15 | 16 | match frame.num { 17 | TrapNo::Breakpoint => match boot_env() { 18 | BootEnv::Vm(vm) => super::vm::interrupt_handler(vm, frame), 19 | }, 20 | } 21 | 22 | unsafe { td.active_interrupts().fetch_sub(1, Ordering::Relaxed) }; 23 | } 24 | 25 | /// Main entry point for `syscall` instruction. 26 | /// 27 | /// This will be called by an inline assembly. 28 | /// 29 | /// See `amd64_syscall` function on the PS4 for a reference. 30 | pub extern "C" fn syscall_handler() { 31 | // TODO: Implement pc_cnt.v_syscall increment. 32 | let td = current_thread(); 33 | let p = td.proc(); 34 | 35 | td.set_profiling_ticks(0); 36 | 37 | // We merge sv_fetch_syscall_args and the code to invoke each syscall handler together. 38 | p.abi().syscall_handler(); 39 | 40 | todo!() 41 | } 42 | 43 | /// Predefined interrupt vector number. 44 | #[allow(dead_code)] // Used by inline assembly. 45 | #[repr(u32)] 46 | #[derive(Clone, Copy, PartialEq, Eq)] 47 | pub enum TrapNo { 48 | Breakpoint = 3, // T_BPTFLT 49 | } 50 | 51 | /// Contains states of the interupted program. 52 | #[repr(C)] 53 | pub struct TrapFrame { 54 | pub rdi: usize, // tf_rdi 55 | pub rsi: usize, // tf_rsi 56 | pub rdx: usize, // tf_rdx 57 | pub rcx: usize, // tf_rcx 58 | pub r8: usize, // tf_r8 59 | pub r9: usize, // tf_r9 60 | pub rax: usize, // tf_rax 61 | pub rbx: usize, // tf_rbx 62 | pub rbp: usize, // tf_rbp 63 | pub r10: usize, // tf_r10 64 | pub r11: usize, // tf_r11 65 | pub r12: usize, // tf_r12 66 | pub r13: usize, // tf_r13 67 | pub r14: usize, // tf_r14 68 | pub r15: usize, // tf_r15 69 | pub num: TrapNo, // tf_trapno 70 | pub fs: u16, // tf_fs 71 | pub gs: u16, // tf_gs 72 | } 73 | -------------------------------------------------------------------------------- /kernel/src/uma/aarch64.rs: -------------------------------------------------------------------------------- 1 | use super::Alloc; 2 | use crate::vm::Vm; 3 | 4 | pub fn small_alloc(vm: &Vm, flags: Alloc) { 5 | todo!() 6 | } 7 | -------------------------------------------------------------------------------- /kernel/src/uma/boxed.rs: -------------------------------------------------------------------------------- 1 | use core::ops::Deref; 2 | 3 | /// Encapsulates an object allocated from a UMA zone. 4 | pub struct UmaBox(*mut T); 5 | 6 | impl Deref for UmaBox { 7 | type Target = T; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | unsafe { &*self.0 } 11 | } 12 | } 13 | 14 | unsafe impl Send for UmaBox {} 15 | unsafe impl Sync for UmaBox {} 16 | -------------------------------------------------------------------------------- /kernel/src/uma/bucket.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of `uma_bucket` structure. 2 | #[repr(C)] 3 | pub struct UmaBucket { 4 | len: usize, // ub_cnt 5 | items: I, // ub_bucket 6 | } 7 | 8 | impl UmaBucket { 9 | pub fn len(&self) -> usize { 10 | self.len 11 | } 12 | } 13 | 14 | /// Each item in the [`UmaBucket::items`]. 15 | pub struct BucketItem {} 16 | -------------------------------------------------------------------------------- /kernel/src/uma/slab.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of `uma_slab_head`, `uma_slab` and `uma_slab_refcnt`. 2 | /// 3 | /// We use slightly different mechanism here but has the same memory layout. 4 | #[repr(C)] 5 | pub struct Slab { 6 | free: I, // us_freelist 7 | } 8 | 9 | /// Item in the slab to represents `uma_slab` structure. 10 | #[repr(C)] 11 | pub struct Free {} 12 | 13 | /// Item in the slab to represents `uma_slab_refcnt` structure. 14 | #[repr(C)] 15 | pub struct RcFree {} 16 | -------------------------------------------------------------------------------- /kernel/src/uma/x86_64.rs: -------------------------------------------------------------------------------- 1 | use super::Alloc; 2 | use crate::vm::Vm; 3 | 4 | /// See `uma_small_alloc` on the Orbis for a reference. 5 | /// 6 | /// # Reference offsets 7 | /// | Version | Offset | 8 | /// |---------|--------| 9 | /// |PS4 11.00|0x22FD70| 10 | pub fn small_alloc(vm: &Vm, flags: Alloc) { 11 | // TODO: There are an increment on an unknown variable on the Orbis. 12 | vm.alloc_page( 13 | None, 14 | // TODO: Refactor this for readability. 15 | ((((u32::from(flags) & 0x100) >> 2) - (u32::from((u32::from(flags) & 0x401) == 1)) + 0x22) 16 | | 0x100) 17 | .into(), 18 | ); 19 | 20 | todo!() 21 | } 22 | -------------------------------------------------------------------------------- /kernel/src/vm/object.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of `vm_object` structure. 2 | pub struct VmObject { 3 | vm: usize, 4 | } 5 | 6 | impl VmObject { 7 | pub fn vm(&self) -> usize { 8 | self.vm 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /kernel/src/vm/page.rs: -------------------------------------------------------------------------------- 1 | /// Implementation of `vm_page` structure. 2 | pub struct VmPage {} 3 | -------------------------------------------------------------------------------- /kernel/src/vm/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::lock::Gutex; 2 | 3 | /// Contains statistics for a VM. 4 | /// 5 | /// This is a subset of `vmmeter` structure. 6 | pub struct VmStats { 7 | pub free_reserved: usize, // v_free_reserved 8 | pub cache_count: Gutex, // v_cache_count 9 | pub free_count: Gutex, // v_free_count 10 | pub interrupt_free_min: Gutex, // v_interrupt_free_min 11 | } 12 | -------------------------------------------------------------------------------- /legacy/src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::EINVAL; 2 | use crate::info; 3 | use crate::process::{PcbFlags, VThread}; 4 | use crate::syscalls::{SysErr, SysIn, SysOut, Syscalls}; 5 | use std::sync::atomic::AtomicU64; 6 | use std::sync::Arc; 7 | 8 | /// An implementation of machine-dependent services. 9 | pub struct MachDep { 10 | tsc_freq: AtomicU64, 11 | } 12 | 13 | impl MachDep { 14 | const I386_GET_IOPERM: u32 = 3; 15 | const I386_SET_IOPERM: u32 = 4; 16 | const AMD64_SET_FSBASE: u32 = 129; 17 | 18 | // PS4 / PS4 Slim 19 | const TSC_FREQ: u64 = 1_600_000_000; 20 | 21 | // PS4 PRO (Neo) TODO 22 | // const TSC_FREQ: u64 = 2_130_000_000; 23 | 24 | pub fn new(sys: &mut Syscalls) -> Arc { 25 | let mach = Arc::new(Self { 26 | tsc_freq: Self::init_tsc(), 27 | }); 28 | 29 | sys.register(165, &mach, Self::sysarch); 30 | 31 | mach 32 | } 33 | 34 | fn sysarch(self: &Arc, td: &Arc, i: &SysIn) -> Result { 35 | let op: u32 = i.args[0].try_into().unwrap(); 36 | let parms: *mut u8 = i.args[1].into(); 37 | let mut pcb = td.pcb_mut(); 38 | 39 | if op < 2 { 40 | return Err(SysErr::Raw(EINVAL)); 41 | } 42 | 43 | match op { 44 | Self::I386_GET_IOPERM | Self::I386_SET_IOPERM => todo!("sysarch with op = 3 | 4"), 45 | _ => {} 46 | } 47 | 48 | match op { 49 | Self::AMD64_SET_FSBASE => { 50 | // We can't check if the value within the user space because we are not a real 51 | // kernel. 52 | let v = unsafe { std::ptr::read_unaligned(parms as _) }; 53 | 54 | pcb.fsbase = v; 55 | pcb.flags |= PcbFlags::PCB_FULL_IRET; 56 | 57 | info!("FS segment has been changed to {v:#x}."); 58 | } 59 | v => todo!("sysarch with op = {v}"), 60 | } 61 | 62 | Ok(SysOut::ZERO) 63 | } 64 | 65 | pub fn tsc_freq(&self) -> &AtomicU64 { 66 | &self.tsc_freq 67 | } 68 | 69 | fn init_tsc() -> AtomicU64 { 70 | AtomicU64::new(Self::TSC_FREQ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /legacy/src/arnd.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | /// An implementation of `arc4rand` on the PS4. 4 | /// TODO: Implement reseed. 5 | pub fn rand_bytes(buf: &mut [u8]) { 6 | ARND.lock().unwrap().rand_bytes(buf) 7 | } 8 | 9 | static ARND: Mutex = Mutex::new(State::new()); 10 | 11 | /// Random number generator based on 12 | /// https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/libkern/arc4random.c. 13 | #[derive(Debug)] 14 | struct State { 15 | i: u8, 16 | j: u8, 17 | sbox: [u8; 256], 18 | } 19 | 20 | impl State { 21 | const fn new() -> Self { 22 | Self { 23 | i: 0, 24 | j: 0, 25 | sbox: sbox_init(), 26 | } 27 | } 28 | 29 | fn rand_bytes(&mut self, buf: &mut [u8]) { 30 | buf.iter_mut().for_each(|b| *b = self.rand_byte()); 31 | } 32 | 33 | fn rand_byte(&mut self) -> u8 { 34 | let s = self; 35 | 36 | s.i = s.i.wrapping_add(1); 37 | s.j = s.j.wrapping_add(s.sbox[s.i as usize]); 38 | s.sbox.swap(s.i as usize, s.j as usize); 39 | s.sbox[s.sbox[s.i as usize].wrapping_add(s.sbox[s.j as usize]) as usize] 40 | } 41 | } 42 | 43 | const fn sbox_init() -> [u8; 256] { 44 | let mut sbox: [u8; 256] = [0; 256]; 45 | 46 | let mut i = 0; 47 | 48 | while i < 256 { 49 | sbox[i] = i as u8; 50 | i += 1; 51 | } 52 | 53 | sbox 54 | } 55 | -------------------------------------------------------------------------------- /legacy/src/dev/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::fs::{ 3 | make_dev, CharacterDevice, DeviceDriver, DriverFlags, IoCmd, IoLen, IoVec, IoVecMut, 4 | MakeDevError, MakeDevFlags, Mode, OpenFlags, 5 | }; 6 | use crate::process::VThread; 7 | use crate::ucred::{Gid, Uid}; 8 | use std::sync::Arc; 9 | use thiserror::Error; 10 | 11 | #[derive(Debug)] 12 | struct Camera {} 13 | 14 | impl Camera { 15 | fn new() -> Self { 16 | Self {} 17 | } 18 | } 19 | 20 | impl DeviceDriver for Camera { 21 | #[allow(unused_variables)] // TODO: remove when implementing 22 | fn open( 23 | &self, 24 | dev: &Arc, 25 | mode: OpenFlags, 26 | devtype: i32, 27 | td: Option<&VThread>, 28 | ) -> Result<(), Box> { 29 | todo!() 30 | } 31 | 32 | #[allow(unused_variables)] // TODO: remove when implementing 33 | fn read( 34 | &self, 35 | dev: &Arc, 36 | off: Option, 37 | buf: &mut [IoVecMut], 38 | td: Option<&VThread>, 39 | ) -> Result> { 40 | todo!() 41 | } 42 | 43 | #[allow(unused_variables)] // TODO: remove when implementing 44 | fn write( 45 | &self, 46 | dev: &Arc, 47 | off: Option, 48 | buf: &[IoVec], 49 | td: Option<&VThread>, 50 | ) -> Result> { 51 | todo!() 52 | } 53 | 54 | #[allow(unused_variables)] // TODO: remove when implementing 55 | fn ioctl( 56 | &self, 57 | dev: &Arc, 58 | cmd: IoCmd, 59 | td: Option<&VThread>, 60 | ) -> Result<(), Box> { 61 | todo!() 62 | } 63 | } 64 | 65 | pub struct CameraManager { 66 | camera: Arc, 67 | } 68 | 69 | impl CameraManager { 70 | pub fn new() -> Result, CameraInitError> { 71 | let camera = make_dev( 72 | Camera::new(), 73 | DriverFlags::from_bits_retain(0x80000004), 74 | 0, 75 | "camera", 76 | Uid::ROOT, 77 | Gid::ROOT, 78 | Mode::new(0o666).unwrap(), 79 | None, 80 | MakeDevFlags::ETERNAL, 81 | )?; 82 | 83 | Ok(Arc::new(Self { camera })) 84 | } 85 | } 86 | 87 | /// Represents an error when [`CameraManager`] fails to initialize. 88 | #[derive(Debug, Error)] 89 | pub enum CameraInitError { 90 | #[error("cannot create camera device")] 91 | CreateGcFailed(#[from] MakeDevError), 92 | } 93 | -------------------------------------------------------------------------------- /legacy/src/dev/dipsw.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::{ 4 | errno::Errno, 5 | fs::{ 6 | make_dev, CharacterDevice, DeviceDriver, DriverFlags, IoCmd, MakeDevError, MakeDevFlags, 7 | Mode, 8 | }, 9 | process::VThread, 10 | ucred::{Gid, Uid}, 11 | }; 12 | use std::sync::Arc; 13 | 14 | #[derive(Debug)] 15 | struct Dipsw {} 16 | 17 | impl Dipsw { 18 | fn new() -> Self { 19 | Self {} 20 | } 21 | } 22 | 23 | impl DeviceDriver for Dipsw { 24 | #[allow(unused_variables)] 25 | fn ioctl( 26 | &self, 27 | dev: &Arc, 28 | cmd: IoCmd, 29 | td: Option<&VThread>, 30 | ) -> Result<(), Box> { 31 | let td = td.unwrap(); 32 | 33 | if !td.cred().is_system() { 34 | match cmd { 35 | // TODO: properly implement this 36 | IoCmd::DIPSWCHECK2(val) | IoCmd::DIPSWUNK(val) => *val = false as i32, 37 | _ => todo!(), 38 | } 39 | } else { 40 | todo!() 41 | } 42 | 43 | Ok(()) 44 | } 45 | } 46 | 47 | pub struct DipswManager { 48 | dipsw: Arc, 49 | } 50 | 51 | impl DipswManager { 52 | pub fn new() -> Result, DipswInitError> { 53 | let dipsw = make_dev( 54 | Dipsw::new(), 55 | DriverFlags::from_bits_retain(0x80000004), 56 | 0, 57 | "dipsw", 58 | Uid::ROOT, 59 | Gid::ROOT, 60 | Mode::new(0o644).unwrap(), 61 | None, 62 | MakeDevFlags::ETERNAL, 63 | )?; 64 | 65 | Ok(Arc::new(Self { dipsw })) 66 | } 67 | } 68 | 69 | /// Represents an error when [`DipswManager`] fails to initialize. 70 | #[derive(Debug, Error)] 71 | pub enum DipswInitError { 72 | #[error("cannot create dipsw device")] 73 | CreateDipswFailed(#[from] MakeDevError), 74 | } 75 | -------------------------------------------------------------------------------- /legacy/src/dev/hid.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::fs::{CharacterDevice, DeviceDriver, IoCmd, IoLen, IoVecMut}; 3 | use crate::process::VThread; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | struct Hid {} 8 | 9 | impl DeviceDriver for Hid { 10 | #[allow(unused_variables)] // TODO: remove when implementing 11 | fn read( 12 | &self, 13 | dev: &Arc, 14 | off: Option, 15 | buf: &mut [IoVecMut], 16 | td: Option<&VThread>, 17 | ) -> Result> { 18 | todo!() 19 | } 20 | 21 | #[allow(unused_variables)] // TODO: remove when implementing 22 | fn ioctl( 23 | &self, 24 | dev: &Arc, 25 | cmd: IoCmd, 26 | td: Option<&VThread>, 27 | ) -> Result<(), Box> { 28 | todo!() 29 | } 30 | } 31 | 32 | #[repr(C)] 33 | #[derive(Debug)] 34 | pub struct OpenPortArgs { 35 | user_id: u32, 36 | ty: u32, 37 | index: u32, 38 | } 39 | -------------------------------------------------------------------------------- /legacy/src/dev/mod.rs: -------------------------------------------------------------------------------- 1 | pub use camera::*; 2 | pub use dce::*; 3 | pub use deci::*; 4 | pub use dipsw::*; 5 | pub use dmem::*; 6 | pub use gc::*; 7 | pub use hid::*; 8 | pub use hmd::*; 9 | pub use random::*; 10 | pub use rng::*; 11 | pub use sbl_srv::*; 12 | pub use ttyconsole::*; 13 | 14 | mod camera; 15 | mod dce; 16 | mod deci; 17 | mod dipsw; 18 | mod dmem; 19 | mod gc; 20 | mod hid; 21 | mod hmd; 22 | mod random; 23 | mod rng; 24 | mod sbl_srv; 25 | mod ttyconsole; 26 | -------------------------------------------------------------------------------- /legacy/src/dev/random.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::fs::{CharacterDevice, DefaultDeviceError, DeviceDriver, IoCmd, IoLen, IoVec, IoVecMut}; 3 | use crate::process::VThread; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | struct Random {} 8 | 9 | impl DeviceDriver for Random { 10 | #[allow(unused_variables)] // TODO: remove when implementing 11 | fn read( 12 | &self, 13 | dev: &Arc, 14 | off: Option, 15 | buf: &mut [IoVecMut], 16 | td: Option<&VThread>, 17 | ) -> Result> { 18 | todo!() 19 | } 20 | 21 | #[allow(unused_variables)] // TODO: remove when implementing 22 | fn write( 23 | &self, 24 | dev: &Arc, 25 | off: Option, 26 | buf: &[IoVec], 27 | td: Option<&VThread>, 28 | ) -> Result> { 29 | todo!() 30 | } 31 | 32 | fn ioctl( 33 | &self, 34 | _: &Arc, 35 | cmd: IoCmd, 36 | _: Option<&VThread>, 37 | ) -> Result<(), Box> { 38 | match cmd { 39 | IoCmd::FIOASYNC(_) | IoCmd::FIONBIO(_) => Ok(()), 40 | _ => Err(Box::new(DefaultDeviceError::CommandNotSupported)), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /legacy/src/dev/rng.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | arnd, 3 | errno::Errno, 4 | fs::{ 5 | make_dev, CharacterDevice, DeviceDriver, DriverFlags, IoCmd, MakeDevError, MakeDevFlags, 6 | Mode, 7 | }, 8 | process::VThread, 9 | ucred::{Gid, Uid}, 10 | }; 11 | use std::sync::Arc; 12 | use thiserror::Error; 13 | 14 | #[derive(Debug)] 15 | struct Rng {} 16 | 17 | impl Rng { 18 | fn new() -> Self { 19 | Self {} 20 | } 21 | } 22 | 23 | impl DeviceDriver for Rng { 24 | fn ioctl( 25 | &self, 26 | _: &Arc, 27 | cmd: IoCmd, 28 | _: Option<&VThread>, 29 | ) -> Result<(), Box> { 30 | match cmd { 31 | // TODO: these are separate algorithms, and should be implemented as such, 32 | // however arc4rand seems sufficient for now 33 | IoCmd::RNGGETGENUINE(input) | IoCmd::RNGFIPS(input) => { 34 | input.error = 0; 35 | 36 | arnd::rand_bytes(&mut input.data); 37 | 38 | Ok(()) 39 | } 40 | _ => todo!(), // ENOIOCTL, 41 | } 42 | } 43 | } 44 | 45 | #[repr(C)] 46 | #[derive(Debug)] 47 | pub struct RngInput { 48 | /// This field seems to be treated as an error 49 | error: i32, 50 | data: [u8; 64], 51 | } 52 | 53 | pub struct RngManager { 54 | rng: Arc, 55 | } 56 | 57 | impl RngManager { 58 | pub fn new() -> Result, RngInitError> { 59 | let rng = make_dev( 60 | Rng::new(), 61 | DriverFlags::from_bits_retain(0x80000004), 62 | 0, 63 | "rng", 64 | Uid::ROOT, 65 | Gid::ROOT, 66 | Mode::new(0o444).unwrap(), 67 | None, 68 | MakeDevFlags::ETERNAL, 69 | )?; 70 | 71 | Ok(Arc::new(Self { rng })) 72 | } 73 | } 74 | 75 | /// Represents an error when [`RngManager`] fails to initialize. 76 | #[derive(Debug, Error)] 77 | pub enum RngInitError { 78 | #[error("cannot create rng device")] 79 | CreateRngFailed(#[from] MakeDevError), 80 | } 81 | -------------------------------------------------------------------------------- /legacy/src/dev/sbl_srv.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | errno::Errno, 3 | fs::{ 4 | make_dev, CharacterDevice, DeviceDriver, DriverFlags, IoCmd, MakeDevError, MakeDevFlags, 5 | Mode, 6 | }, 7 | process::VThread, 8 | ucred::{Gid, Uid}, 9 | }; 10 | use std::sync::Arc; 11 | use thiserror::Error; 12 | 13 | #[derive(Debug)] 14 | struct SblSrv {} 15 | 16 | impl SblSrv { 17 | fn new() -> Self { 18 | Self {} 19 | } 20 | } 21 | 22 | impl DeviceDriver for SblSrv { 23 | #[allow(unused_variables)] // TODO: remove when implementing 24 | fn ioctl( 25 | &self, 26 | dev: &Arc, 27 | cmd: IoCmd, 28 | td: Option<&VThread>, 29 | ) -> Result<(), Box> { 30 | todo!() 31 | } 32 | } 33 | 34 | pub struct SblSrvManager { 35 | sbl: Arc, 36 | } 37 | 38 | impl SblSrvManager { 39 | pub fn new() -> Result, SblSrvInitError> { 40 | let sbl = make_dev( 41 | SblSrv {}, 42 | DriverFlags::INIT, 43 | 0, 44 | "sbl_srv", 45 | Uid::ROOT, 46 | Gid::ROOT, 47 | Mode::new(0o600).unwrap(), 48 | None, 49 | MakeDevFlags::empty(), 50 | ) 51 | .map_err(SblSrvInitError::CreateSblSrvFailed)?; 52 | 53 | Ok(Arc::new(Self { sbl })) 54 | } 55 | } 56 | 57 | /// Represents an error when [`SblSrvManager`] fails to initialize. 58 | #[derive(Debug, Error)] 59 | pub enum SblSrvInitError { 60 | #[error("cannot create sbl_srv device")] 61 | CreateSblSrvFailed(#[source] MakeDevError), 62 | } 63 | -------------------------------------------------------------------------------- /legacy/src/dmem/blockpool.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::Errno; 2 | use crate::fs::{DefaultFileBackendError, FileBackend, IoCmd, PollEvents, Stat, VFile, Vnode}; 3 | use crate::process::VThread; 4 | use std::sync::Arc; 5 | 6 | #[derive(Debug)] 7 | pub struct BlockPool {} 8 | 9 | impl BlockPool { 10 | pub fn new() -> Self { 11 | Self {} 12 | } 13 | } 14 | 15 | impl FileBackend for BlockPool { 16 | fn is_seekable(&self) -> bool { 17 | todo!() 18 | } 19 | 20 | #[allow(unused_variables)] // TODO: remove when implementing 21 | fn ioctl(&self, file: &VFile, cmd: IoCmd, td: Option<&VThread>) -> Result<(), Box> { 22 | match cmd { 23 | IoCmd::BPOOLEXPAND(args) => todo!(), 24 | IoCmd::BPOOLSTATS(out) => todo!(), 25 | _ => Err(Box::new(DefaultFileBackendError::IoctlNotSupported)), 26 | } 27 | } 28 | 29 | #[allow(unused_variables)] // TODO: remove when implementing 30 | fn poll(&self, file: &VFile, events: PollEvents, td: &VThread) -> PollEvents { 31 | todo!() 32 | } 33 | 34 | fn stat(&self, _: &VFile, _: Option<&VThread>) -> Result> { 35 | let mut stat = Stat::zeroed(); 36 | 37 | stat.block_size = 0x10000; 38 | stat.mode = 0o130000; 39 | 40 | todo!() 41 | } 42 | 43 | fn vnode(&self) -> Option<&Arc> { 44 | None 45 | } 46 | } 47 | 48 | #[repr(C)] 49 | #[derive(Debug)] 50 | pub struct BlockpoolExpandArgs { 51 | len: usize, 52 | search_start: usize, 53 | search_end: usize, 54 | alignment: usize, 55 | } 56 | 57 | #[repr(C)] 58 | #[derive(Debug)] 59 | pub struct BlockpoolStats { 60 | avail_flushed: i32, 61 | avail_cached: i32, 62 | allocated_flushed: i32, 63 | allocated_cached: i32, 64 | } 65 | -------------------------------------------------------------------------------- /legacy/src/event/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::subsystem::Subsystem; 2 | use std::collections::BTreeMap; 3 | use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}; 4 | 5 | pub use self::ty::*; 6 | 7 | mod ty; 8 | 9 | pub struct Event { 10 | next_id: u64, 11 | } 12 | 13 | impl Event { 14 | /// See `eventhandler_register` on the PS4 for a reference. 15 | /// 16 | /// `handler` must not call into any function that is going to trigger any event in the same 17 | /// [`EventSet`] because it can cause a deadlock. That means what `handler` can do is limited. 18 | pub fn subscribe( 19 | &mut self, 20 | subsys: &Arc, 21 | handler: T::Handler, 22 | priority: u32, 23 | ) { 24 | let subsys = subsys.clone(); 25 | let id = self.next_id; 26 | 27 | assert!(self 28 | .subscribers 29 | .insert((priority, id), T::wrap_handler(subsys, handler)) 30 | .is_none()); 31 | 32 | self.next_id += 1; 33 | } 34 | } 35 | 36 | impl Default for Event { 37 | fn default() -> Self { 38 | Self { next_id: 0 } 39 | } 40 | } 41 | 42 | impl EventSet { 43 | pub fn lock(&self) -> RwLockWriteGuard { 44 | self.0.write().unwrap() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /legacy/src/event/ty.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | /// Type of an event. 4 | pub trait EventType: 'static { 5 | fn wrap_handler(s: Arc, h: Self::Handler) -> Self::Wrapper 6 | where 7 | S: Send + Sync + 'static; 8 | } 9 | 10 | impl EventType for fn(A) { 11 | type Handler = fn(&Arc, A); 12 | type Wrapper = Box; 13 | 14 | fn wrap_handler(s: Arc, h: Self::Handler) -> Self::Wrapper 15 | where 16 | S: Send + Sync + 'static, 17 | { 18 | Box::new(move |arg| h(&s, arg)) 19 | } 20 | } 21 | 22 | #[allow(coherence_leak_check)] // https://github.com/rust-lang/rust/issues/56105#issuecomment-606379619 23 | impl EventType for for<'a> fn(&'a A) { 24 | type Handler = fn(&Arc, &A); 25 | type Wrapper = Box; 26 | 27 | fn wrap_handler(s: Arc, h: Self::Handler) -> Self::Wrapper 28 | where 29 | S: Send + Sync + 'static, 30 | { 31 | Box::new(move |arg| h(&s, arg)) 32 | } 33 | } 34 | 35 | #[allow(coherence_leak_check)] // https://github.com/rust-lang/rust/issues/56105#issuecomment-606379619 36 | impl EventType for for<'a> fn(&'a mut A) { 37 | fn wrap_handler(s: Arc, h: Self::Handler) -> Self::Wrapper 38 | where 39 | S: Send + Sync + 'static, 40 | { 41 | Box::new(move |arg| h(&s, arg)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /legacy/src/fs/dirent.rs: -------------------------------------------------------------------------------- 1 | /// An implementation of `dirent` structure. 2 | #[derive(Debug)] 3 | pub struct Dirent { 4 | ty: DirentType, // d_type 5 | name: String, // d_name 6 | } 7 | 8 | impl Dirent { 9 | pub fn new>(ty: DirentType, name: N) -> Self { 10 | Self { 11 | ty, 12 | name: name.into(), 13 | } 14 | } 15 | 16 | pub fn ty(&self) -> DirentType { 17 | self.ty 18 | } 19 | 20 | pub fn is_directory(&self) -> bool { 21 | matches!(self.ty, DirentType::Directory) 22 | } 23 | 24 | pub fn name(&self) -> &str { 25 | self.name.as_ref() 26 | } 27 | } 28 | 29 | /// Type of [`Dirent`]. 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 31 | pub enum DirentType { 32 | Character = 2, // DT_CHR 33 | Directory = 4, // DT_DIR 34 | Link = 10, // DT_LNK 35 | } 36 | -------------------------------------------------------------------------------- /legacy/src/fs/null/hash.rs: -------------------------------------------------------------------------------- 1 | use crate::fs::{Mount, Vnode}; 2 | use std::{ 3 | collections::HashMap, 4 | sync::{Arc, Mutex}, 5 | }; 6 | 7 | pub(super) static NULL_HASHTABLE: Mutex = Mutex::new(NullHashTable(None)); 8 | 9 | // Maps a hash to a pair of (lower vnode, null vnode). 10 | pub(super) struct NullHashTable(Option, Arc)>>); 11 | 12 | impl NullHashTable { 13 | /// See `null_hashget` on the PS4 for a reference. 14 | pub(super) fn get(&mut self, mnt: &Arc, lower: &Arc) -> Option> { 15 | let table = self.0.get_or_insert(HashMap::new()); 16 | 17 | let hash = lower.hash_index(); 18 | 19 | let (stored_lower, nullnode) = table.get(&hash)?; 20 | 21 | if Arc::ptr_eq(lower, stored_lower) && Arc::ptr_eq(nullnode.mount(), mnt) { 22 | return Some(nullnode.clone()); 23 | } 24 | 25 | None 26 | } 27 | 28 | /// See `null_hashins` on the PS4 for a reference. 29 | pub(super) fn insert(&mut self, mnt: &Arc, lower: &Arc, nullnode: &Arc) { 30 | let table = self.0.get_or_insert(HashMap::new()); 31 | 32 | let hash_index = lower.hash_index(); 33 | 34 | table.insert(hash_index, (lower.clone(), nullnode.clone())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /legacy/src/fs/null/mod.rs: -------------------------------------------------------------------------------- 1 | use self::vnode::null_nodeget; 2 | use super::{ 3 | Filesystem, Fs, FsConfig, LookupError, Mount, MountFlags, MountOpts, MountSource, VPathBuf, 4 | Vnode, 5 | }; 6 | use crate::errno::{Errno, EOPNOTSUPP}; 7 | use crate::ucred::Ucred; 8 | use macros::Errno; 9 | use std::sync::Arc; 10 | use thiserror::Error; 11 | 12 | mod hash; 13 | mod vnode; 14 | 15 | /// An implementation of `null_mount` structure. 16 | #[derive(Debug)] 17 | struct NullFs { 18 | lower: Arc, 19 | } 20 | 21 | impl NullFs { 22 | pub fn lower(&self) -> &Arc { 23 | &self.lower 24 | } 25 | } 26 | 27 | impl Filesystem for NullFs { 28 | fn root(self: Arc, mnt: &Arc) -> Result, Box> { 29 | let vnode = null_nodeget(mnt, &self.lower)?; 30 | 31 | Ok(vnode) 32 | } 33 | } 34 | 35 | pub fn mount( 36 | fs: Option<&Arc>, 37 | conf: &'static FsConfig, 38 | cred: &Arc, 39 | path: VPathBuf, 40 | _: Option>, 41 | mut opts: MountOpts, 42 | flags: MountFlags, 43 | ) -> Result> { 44 | if flags.intersects(MountFlags::MNT_ROOTFS) { 45 | return Err(Box::new(MountError::RootFs)); 46 | } 47 | 48 | if flags.intersects(MountFlags::MNT_UPDATE) { 49 | if let Some(true) = opts.remove("export") { 50 | todo!("nullfs_mount with MNT_UPDATE and export = true") 51 | } else { 52 | return Err(Box::new(MountError::NoExport)); 53 | } 54 | } 55 | 56 | // Get target path. 57 | let target: VPathBuf = 58 | opts.remove_or_else("target", || todo!("nullfs_mount without target option")); 59 | 60 | let lower = fs 61 | .unwrap() 62 | .lookup(&target, true, None) 63 | .map_err(MountError::LookupTargetVnode)?; 64 | 65 | Ok(Mount::new( 66 | conf, 67 | cred, 68 | MountSource::Path(target), 69 | path, 70 | Some(lower.clone()), 71 | flags, 72 | NullFs { lower }, 73 | )) 74 | } 75 | 76 | #[derive(Debug, Error, Errno)] 77 | enum MountError { 78 | #[error("mounting as root FS is not supported")] 79 | #[errno(EOPNOTSUPP)] 80 | RootFs, 81 | 82 | #[error("update mount is not supported without export option")] 83 | #[errno(EOPNOTSUPP)] 84 | NoExport, 85 | 86 | #[error("couldn't lookup target vnode")] 87 | LookupTargetVnode(#[source] LookupError), 88 | } 89 | -------------------------------------------------------------------------------- /legacy/src/fs/stat.rs: -------------------------------------------------------------------------------- 1 | use crate::time::TimeSpec; 2 | 3 | /// An implementation of the `stat` structure. 4 | #[repr(C)] 5 | pub struct Stat { 6 | dev: i32, 7 | ino: u32, 8 | pub mode: u16, 9 | nlink: u16, 10 | uid: u32, 11 | gid: u32, 12 | rdev: i32, 13 | atime: TimeSpec, 14 | mtime: TimeSpec, 15 | ctime: TimeSpec, 16 | pub size: i64, 17 | block_count: i64, 18 | pub block_size: u32, 19 | flags: u32, 20 | gen: u32, 21 | _spare: i32, 22 | birthtime: TimeSpec, 23 | } 24 | 25 | impl Stat { 26 | /// This is what would happen when calling `bzero` on a `stat` structure. 27 | pub fn zeroed() -> Self { 28 | unsafe { std::mem::zeroed() } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /legacy/src/idt/entry.rs: -------------------------------------------------------------------------------- 1 | /// An entry in the ID table. 2 | #[derive(Debug)] 3 | pub struct Entry { 4 | name: Option, 5 | data: T, 6 | ty: u16, 7 | } 8 | 9 | impl Entry { 10 | pub fn new(name: Option, data: T, ty: u16) -> Self { 11 | Self { 12 | name: name, 13 | data, 14 | ty, 15 | } 16 | } 17 | 18 | pub fn data(&self) -> &T { 19 | &self.data 20 | } 21 | 22 | pub fn ty(&self) -> u16 { 23 | self.ty 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /legacy/src/idt/mod.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | pub use self::entry::*; 4 | 5 | mod entry; 6 | 7 | const ENTRY_COUNT: usize = 0x80; 8 | 9 | /// An implementation of `sys/kern/orbis_idt.c`. 10 | #[derive(Debug)] 11 | pub struct Idt { 12 | sets: Vec<[Option>; ENTRY_COUNT]>, 13 | next: usize, 14 | limit: usize, 15 | } 16 | 17 | impl Idt { 18 | const NONE: Option> = None; 19 | 20 | /// See `_id_table_create` on the PS4 for a reference. 21 | pub fn new(limit: usize) -> Self { 22 | assert_ne!(limit, 0); 23 | 24 | // Allocate the first set. 25 | let sets = vec![[Self::NONE; ENTRY_COUNT]]; 26 | 27 | Self { 28 | sets, 29 | next: 0, 30 | limit, 31 | } 32 | } 33 | 34 | pub fn alloc(&mut self, entry: Entry) -> usize { 35 | let Ok((_, id)) = self.try_alloc_with::<_, Infallible>(|id| Ok(entry)) else { 36 | unreachable!(); 37 | }; 38 | 39 | id 40 | } 41 | 42 | /// See `id_alloc` on the PS4 for a reference. 43 | pub fn try_alloc_with(&mut self, factory: F) -> Result<(&mut Entry, usize), E> 44 | where 45 | F: FnOnce(usize) -> Result, E>, 46 | { 47 | // Allocate a new set if necessary. 48 | let id = self.next; 49 | let set = id / ENTRY_COUNT; 50 | 51 | while set >= self.sets.len() { 52 | todo!("id_alloc with entries span across the first set"); 53 | } 54 | 55 | // Get the entry. 56 | let set = &mut self.sets[set]; 57 | let entry = &mut set[id % ENTRY_COUNT]; 58 | 59 | assert!(entry.is_none()); 60 | 61 | // Set the value. 62 | let value = entry.insert(factory(id)?); 63 | 64 | // Update table states. 65 | self.next += 1; 66 | 67 | Ok((value, id)) 68 | } 69 | 70 | /// See `id_rlock` on the PS4 for a reference. 71 | pub fn get_mut(&mut self, id: usize, ty: Option) -> Option<&mut Entry> { 72 | if id >= 0x10000 { 73 | return None; 74 | } 75 | 76 | let i = id & 0x1fff; 77 | let set = self.sets.get_mut(i / ENTRY_COUNT)?; 78 | let entry = set[i % ENTRY_COUNT].as_mut()?; 79 | 80 | if let Some(ty) = ty { 81 | if entry.ty() != ty { 82 | return None; 83 | } 84 | } 85 | 86 | Some(entry) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /legacy/src/imgact/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod orbis; 2 | -------------------------------------------------------------------------------- /legacy/src/imgact/orbis/library.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | /// Contains information about the library. 4 | #[derive(Debug)] 5 | pub struct LibraryInfo { 6 | id: u16, 7 | name: String, 8 | flags: LibraryFlags, 9 | } 10 | 11 | impl LibraryInfo { 12 | pub(crate) fn new(id: u16, name: String, flags: LibraryFlags) -> Self { 13 | Self { id, name, flags } 14 | } 15 | 16 | pub fn id(&self) -> u16 { 17 | self.id 18 | } 19 | 20 | pub fn name(&self) -> &str { 21 | self.name.as_ref() 22 | } 23 | 24 | pub fn flags(&self) -> LibraryFlags { 25 | self.flags 26 | } 27 | 28 | pub fn flags_mut(&mut self) -> &mut LibraryFlags { 29 | &mut self.flags 30 | } 31 | } 32 | 33 | bitflags! { 34 | /// Flags of [`LibraryInfo`]. 35 | #[derive(Debug, Clone, Copy)] 36 | pub struct LibraryFlags: u64 { 37 | const EXPORT = 0x010000; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /legacy/src/imgact/orbis/module.rs: -------------------------------------------------------------------------------- 1 | /// Contains information about the module. 2 | #[derive(Debug)] 3 | pub struct ModuleInfo { 4 | id: u16, 5 | name: String, 6 | } 7 | 8 | impl ModuleInfo { 9 | pub(crate) fn new(id: u16, name: String) -> Self { 10 | Self { id, name } 11 | } 12 | 13 | pub fn id(&self) -> u16 { 14 | self.id 15 | } 16 | 17 | pub fn name(&self) -> &str { 18 | self.name.as_ref() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /legacy/src/imgact/orbis/ty.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | /// Type of (S)ELF file. 4 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 5 | pub struct FileType(u16); 6 | 7 | impl FileType { 8 | pub const ET_EXEC: Self = Self(0x0002); 9 | pub const ET_SCE_EXEC: Self = Self(0xfe00); 10 | pub const ET_SCE_REPLAY_EXEC: Self = Self(0xfe01); 11 | pub const ET_SCE_DYNEXEC: Self = Self(0xfe10); 12 | pub const ET_SCE_DYNAMIC: Self = Self(0xfe18); 13 | 14 | pub fn new(v: u16) -> Self { 15 | Self(v) 16 | } 17 | } 18 | 19 | impl Display for FileType { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | match *self { 22 | Self::ET_EXEC => f.write_str("ET_EXEC"), 23 | Self::ET_SCE_EXEC => f.write_str("ET_SCE_EXEC"), 24 | Self::ET_SCE_REPLAY_EXEC => f.write_str("ET_SCE_REPLAY_EXEC"), 25 | Self::ET_SCE_DYNEXEC => f.write_str("ET_SCE_DYNEXEC"), 26 | Self::ET_SCE_DYNAMIC => f.write_str("ET_SCE_DYNAMIC"), 27 | _ => write!(f, "{:#06x}", self.0), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /legacy/src/namedobj/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::EINVAL; 2 | use crate::idt::Entry; 3 | use crate::info; 4 | use crate::process::VThread; 5 | use crate::syscalls::{SysErr, SysIn, SysOut, Syscalls}; 6 | use std::sync::Arc; 7 | 8 | pub struct NamedObjManager {} 9 | 10 | impl NamedObjManager { 11 | pub fn new(sys: &mut Syscalls) -> Arc { 12 | let namedobj = Arc::new(Self {}); 13 | 14 | sys.register(557, &namedobj, Self::sys_namedobj_create); 15 | 16 | namedobj 17 | } 18 | 19 | fn sys_namedobj_create( 20 | self: &Arc, 21 | td: &Arc, 22 | i: &SysIn, 23 | ) -> Result { 24 | // Get arguments. 25 | let name = unsafe { i.args[0].to_str(32) }?.ok_or(SysErr::Raw(EINVAL))?; 26 | let data: usize = i.args[1].into(); 27 | let flags: u32 = i.args[2].try_into().unwrap(); 28 | 29 | // Allocate the entry. 30 | let mut table = td.proc().objects_mut(); 31 | 32 | let obj = NamedObj::new(name, data); 33 | 34 | let id = table.alloc({ 35 | Entry::new( 36 | Some(name.to_owned()), 37 | Arc::new(obj), 38 | (flags as u16) | 0x1000, 39 | ) 40 | }); 41 | 42 | info!( 43 | "Named object '{}' (ID = {}) was created with data = {:#x} and flags = {:#x}.", 44 | name, id, data, flags 45 | ); 46 | 47 | Ok(id.into()) 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | pub struct NamedObj { 53 | name: String, 54 | data: usize, 55 | } 56 | 57 | impl NamedObj { 58 | pub fn new(name: impl Into, data: usize) -> Self { 59 | Self { 60 | name: name.into(), 61 | data, 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /legacy/src/net/proto/inet/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{ListenError, SockAddr, Socket, SocketBackend}; 2 | use crate::errno::Errno; 3 | use crate::fs::IoCmd; 4 | use crate::process::VThread; 5 | use std::sync::Arc; 6 | 7 | #[derive(Debug)] 8 | pub(super) enum InetProtocol { 9 | UdpPeerToPeer, 10 | } 11 | 12 | impl SocketBackend for InetProtocol { 13 | fn attach(&self, _: &Arc, _: &VThread) -> Result<(), Box> { 14 | //TODO: properly implement this. 15 | match self { 16 | Self::UdpPeerToPeer => Ok(()), 17 | } 18 | } 19 | 20 | fn bind( 21 | &self, 22 | _socket: &Arc, 23 | _addr: &SockAddr, 24 | _td: &VThread, 25 | ) -> Result<(), Box> { 26 | todo!() 27 | } 28 | 29 | fn connect( 30 | &self, 31 | _socket: &Arc, 32 | _addr: &SockAddr, 33 | _td: &VThread, 34 | ) -> Result<(), Box> { 35 | todo!() 36 | } 37 | 38 | fn control( 39 | &self, 40 | _: &Arc, 41 | cmd: IoCmd, 42 | _: Option<&VThread>, 43 | ) -> Result<(), Box> { 44 | match self { 45 | Self::UdpPeerToPeer => match cmd { 46 | // TODO: properly implement this. It is difficult to judge what it currently does, 47 | // because the socket is simply created, this ioctl is called and then the socket is immediately closed. 48 | IoCmd::BNETUNK(_) => Ok(()), 49 | _ => todo!(), 50 | }, 51 | } 52 | } 53 | 54 | fn listen( 55 | &self, 56 | _socket: &Arc, 57 | _backlog: i32, 58 | _td: Option<&VThread>, 59 | ) -> Result<(), Box> { 60 | match self { 61 | Self::UdpPeerToPeer => Err(Box::new(ListenError::NotSupported)), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /legacy/src/net/proto/unix/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{SockAddr, Socket, SocketBackend}; 2 | use crate::errno::Errno; 3 | use crate::fs::IoCmd; 4 | use crate::process::VThread; 5 | use std::sync::Arc; 6 | 7 | #[derive(Debug)] 8 | pub(super) enum UnixProtocol { 9 | Stream = 1, 10 | Datagram = 2, 11 | SeqPacket = 5, 12 | } 13 | 14 | impl SocketBackend for UnixProtocol { 15 | fn attach(&self, _: &Arc, _: &VThread) -> Result<(), Box> { 16 | todo!() 17 | } 18 | 19 | fn bind( 20 | &self, 21 | _socket: &Arc, 22 | _addr: &SockAddr, 23 | _td: &VThread, 24 | ) -> Result<(), Box> { 25 | todo!() 26 | } 27 | 28 | fn connect( 29 | &self, 30 | _socket: &Arc, 31 | _addr: &SockAddr, 32 | _td: &VThread, 33 | ) -> Result<(), Box> { 34 | todo!() 35 | } 36 | 37 | fn control( 38 | &self, 39 | _: &Arc, 40 | _: IoCmd, 41 | _: Option<&VThread>, 42 | ) -> Result<(), Box> { 43 | todo!() 44 | } 45 | 46 | fn listen( 47 | &self, 48 | _socket: &Arc, 49 | _backlog: i32, 50 | _td: Option<&VThread>, 51 | ) -> Result<(), Box> { 52 | todo!() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /legacy/src/osem/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::EINVAL; 2 | use crate::idt::Entry; 3 | use crate::process::VThread; 4 | use crate::syscalls::{SysErr, SysIn, SysOut, Syscalls}; 5 | use bitflags::bitflags; 6 | use std::sync::Arc; 7 | 8 | pub struct OsemManager {} 9 | 10 | impl OsemManager { 11 | pub fn new(sys: &mut Syscalls) -> Arc { 12 | let osem = Arc::new(Self {}); 13 | 14 | sys.register(549, &osem, Self::sys_osem_create); 15 | 16 | osem 17 | } 18 | 19 | fn sys_osem_create(self: &Arc, td: &Arc, i: &SysIn) -> Result { 20 | let name = unsafe { i.args[0].to_str(32) }?.unwrap(); 21 | let flags = { 22 | let flags = i.args[1].try_into().unwrap(); 23 | let mut flags = OsemFlags::from_bits_retain(flags); 24 | 25 | if flags.bits() & 0xfffffefc != 0 || flags.bits() & 0x3 == 0x3 { 26 | return Err(SysErr::Raw(EINVAL)); 27 | } 28 | 29 | if flags.bits() & 0x3 == 0 { 30 | flags |= OsemFlags::UNK1; 31 | } 32 | 33 | flags 34 | }; 35 | 36 | let mut objects = td.proc().objects_mut(); 37 | 38 | let id = objects.alloc(Entry::new(Some(name.to_owned()), Osem::new(flags), 0x120)); 39 | 40 | Ok(id.into()) 41 | } 42 | } 43 | 44 | struct Osem { 45 | flags: OsemFlags, 46 | } 47 | 48 | impl Osem { 49 | pub fn new(flags: OsemFlags) -> Arc { 50 | Arc::new(Self { flags }) 51 | } 52 | } 53 | 54 | bitflags! { 55 | pub struct OsemFlags: u32 { 56 | const UNK1 = 0x1; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /legacy/src/process/active.rs: -------------------------------------------------------------------------------- 1 | /// States of a process when it is still active. 2 | #[derive(Debug)] 3 | pub struct ActiveProc {} 4 | 5 | impl ActiveProc { 6 | pub(super) fn new() -> Self { 7 | Self {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /legacy/src/process/appinfo.rs: -------------------------------------------------------------------------------- 1 | /// An implementation of `appinfo` structure on the PS4. 2 | #[derive(Debug)] 3 | pub struct AppInfo { 4 | unk1: u32, // 0x02 = ET_SCE_REPLAY_EXEC 5 | title_id: String, 6 | } 7 | 8 | impl AppInfo { 9 | pub fn new() -> Self { 10 | Self { 11 | unk1: 0, 12 | title_id: String::new(), 13 | } 14 | } 15 | 16 | pub fn unk1(&self) -> u32 { 17 | self.unk1 18 | } 19 | 20 | pub fn serialize(&self) -> [u8; 72] { 21 | // TODO: Right now we don't know how appinfo is structured but it seems like it is safe to 22 | // fill it with zeroes. 23 | [0; 72] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /legacy/src/process/binary.rs: -------------------------------------------------------------------------------- 1 | use crate::rtld::Module; 2 | use std::sync::Arc; 3 | 4 | /// Implementation of a structure that hold dynamic loading for a process. 5 | /// 6 | /// Each process on the PS4 have one field for holding dynamic loading data. This struct represents 7 | /// that field. 8 | #[derive(Debug)] 9 | pub struct Binaries { 10 | list: Vec>, // obj_list + obj_tail 11 | mains: Vec>, // list_main 12 | globals: Vec>, // list_global 13 | app: Arc, // obj_main 14 | } 15 | 16 | impl Binaries { 17 | pub fn new(app: Arc) -> Self { 18 | Self { 19 | list: vec![app.clone()], 20 | mains: vec![app.clone()], 21 | globals: Vec::new(), 22 | app, 23 | } 24 | } 25 | 26 | /// The returned iterator will never be empty and the first item is always the application 27 | /// itself. 28 | pub fn list(&self) -> impl ExactSizeIterator> { 29 | self.list.iter() 30 | } 31 | 32 | /// The returned iterator will never be empty and the first item is always the application 33 | /// itself. 34 | pub fn mains(&self) -> impl Iterator> { 35 | self.mains.iter() 36 | } 37 | 38 | pub fn globals(&self) -> impl Iterator> { 39 | self.globals.iter() 40 | } 41 | 42 | pub fn app(&self) -> &Arc { 43 | &self.app 44 | } 45 | 46 | pub fn push(&mut self, md: Arc, main: bool) { 47 | if main { 48 | self.list.push(md.clone()); 49 | self.mains.push(md); 50 | } else { 51 | self.list.push(md); 52 | } 53 | } 54 | 55 | pub fn push_global(&mut self, md: Arc) { 56 | self.globals.push(md); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /legacy/src/process/cpuset.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::EINVAL; 2 | use crate::syscalls::{SysArg, SysErr}; 3 | 4 | /// An implementation of `cpuset`. 5 | #[derive(Debug)] 6 | pub struct CpuSet { 7 | mask: CpuMask, // cs_mask 8 | } 9 | 10 | impl CpuSet { 11 | pub fn new(mask: CpuMask) -> Self { 12 | Self { mask } 13 | } 14 | 15 | pub fn mask(&self) -> &CpuMask { 16 | &self.mask 17 | } 18 | } 19 | 20 | /// An implementation of `cpuset_t`. 21 | #[repr(C)] 22 | #[derive(Debug, Default)] 23 | pub struct CpuMask { 24 | pub bits: [u64; 1], 25 | } 26 | 27 | /// An implementation of `cpulevel_t`. 28 | #[repr(i32)] 29 | #[derive(Debug, Clone, Copy)] 30 | pub(super) enum CpuLevel { 31 | Root = 1, 32 | Cpuset = 2, 33 | Which = 3, 34 | } 35 | 36 | impl CpuLevel { 37 | pub fn new(v: i32) -> Option { 38 | Some(match v { 39 | 1 => Self::Root, 40 | 2 => Self::Cpuset, 41 | 3 => Self::Which, 42 | _ => return None, 43 | }) 44 | } 45 | } 46 | 47 | impl TryFrom for CpuLevel { 48 | type Error = SysErr; 49 | 50 | fn try_from(value: SysArg) -> Result { 51 | value 52 | .try_into() 53 | .ok() 54 | .and_then(|v| Self::new(v)) 55 | .ok_or(SysErr::Raw(EINVAL)) 56 | } 57 | } 58 | 59 | /// An implementation of `cpuwhich_t`. 60 | #[repr(i32)] 61 | #[derive(Debug, Clone, Copy)] 62 | pub(super) enum CpuWhich { 63 | Tid = 1, 64 | Pid = 2, 65 | Cpuset = 3, 66 | Irq = 4, 67 | Jail = 5, 68 | } 69 | 70 | impl CpuWhich { 71 | pub fn new(v: i32) -> Option { 72 | Some(match v { 73 | 1 => Self::Tid, 74 | 2 => Self::Pid, 75 | 3 => Self::Cpuset, 76 | 4 => Self::Irq, 77 | 5 => Self::Jail, 78 | _ => return None, 79 | }) 80 | } 81 | } 82 | 83 | impl TryFrom for CpuWhich { 84 | type Error = SysErr; 85 | 86 | fn try_from(value: SysArg) -> Result { 87 | value 88 | .try_into() 89 | .ok() 90 | .and_then(|v| Self::new(v)) 91 | .ok_or(SysErr::Raw(EINVAL)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /legacy/src/process/group.rs: -------------------------------------------------------------------------------- 1 | use super::{Pid, VSession}; 2 | use gmtx::{Gutex, GutexGroup, GutexReadGuard, GutexWriteGuard}; 3 | use std::sync::Arc; 4 | 5 | /// An implementation of `pgrp` struct. 6 | #[derive(Debug)] 7 | pub struct VProcGroup { 8 | id: Pid, // pg_id 9 | session: Gutex>, // pg_session 10 | } 11 | 12 | impl VProcGroup { 13 | pub fn new(id: Pid, session: Arc) -> Arc { 14 | let gg = GutexGroup::new(); 15 | 16 | Arc::new(Self { 17 | id, 18 | session: gg.spawn(session), 19 | }) 20 | } 21 | 22 | pub fn session(&self) -> GutexReadGuard<'_, Arc> { 23 | self.session.read() 24 | } 25 | 26 | pub fn session_mut(&self) -> GutexWriteGuard<'_, Arc> { 27 | self.session.write() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /legacy/src/process/pcb.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | /// Implementation of `pcb` structure. 4 | #[derive(Default)] 5 | pub struct Pcb { 6 | pub r15: usize, // pcb_r15 7 | pub r14: usize, // pcb_r14 8 | pub r13: usize, // pcb_r13 9 | pub r12: usize, // pcb_r12 10 | pub rbp: usize, // pcb_rbp 11 | pub rsp: usize, // pcb_rsp 12 | pub rbx: usize, // pcb_rbx 13 | pub rip: usize, // pcb_rip 14 | pub fsbase: usize, // pcb_fsbase 15 | pub flags: PcbFlags, // pcb_flags 16 | } 17 | 18 | bitflags! { 19 | /// Flags of [`Pcb`]. 20 | #[derive(Default)] 21 | pub struct PcbFlags: u32 { 22 | const PCB_FULL_IRET = 0x01; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /legacy/src/process/pid.rs: -------------------------------------------------------------------------------- 1 | use crate::syscalls::{SysArg, SysOut}; 2 | use std::borrow::Borrow; 3 | 4 | /// Unique identifier of a process. 5 | #[repr(transparent)] 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 7 | pub struct Pid(i32); 8 | 9 | impl Pid { 10 | pub const KERNEL: Self = Self(0); 11 | pub const IDLE: Self = Self(10); 12 | 13 | /// Returns [`None`] if `v` is negative. 14 | pub const fn new(v: i32) -> Option { 15 | if v >= 0 { 16 | Some(Self(v)) 17 | } else { 18 | None 19 | } 20 | } 21 | } 22 | 23 | impl From for Pid { 24 | fn from(value: SysArg) -> Self { 25 | // We want to catch when the PS4 send an unexpected PID instead of silently return an error. 26 | Pid::new(value.try_into().unwrap()).unwrap() 27 | } 28 | } 29 | 30 | impl Borrow for Pid { 31 | fn borrow(&self) -> &i32 { 32 | &self.0 33 | } 34 | } 35 | 36 | impl PartialEq for Pid { 37 | fn eq(&self, other: &i32) -> bool { 38 | self.0 == *other 39 | } 40 | } 41 | 42 | impl PartialEq for i32 { 43 | fn eq(&self, other: &Pid) -> bool { 44 | *self == other.0 45 | } 46 | } 47 | 48 | impl From for SysOut { 49 | fn from(value: Pid) -> Self { 50 | value.0.into() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /legacy/src/process/session.rs: -------------------------------------------------------------------------------- 1 | use super::Pid; 2 | use gmtx::{Gutex, GutexGroup}; 3 | use std::sync::Arc; 4 | 5 | /// An implementation of `session` structure. 6 | #[derive(Debug)] 7 | pub struct VSession { 8 | id: Pid, // s_sid 9 | login: Gutex, // s_login 10 | } 11 | 12 | impl VSession { 13 | pub fn new(id: Pid, login: String) -> Arc { 14 | let gg = GutexGroup::new(); 15 | 16 | Arc::new(Self { 17 | id, 18 | login: gg.spawn(login), 19 | }) 20 | } 21 | 22 | pub fn set_login>(&self, v: V) { 23 | *self.login.write() = v.into(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /legacy/src/process/zombie.rs: -------------------------------------------------------------------------------- 1 | /// States of a process when it is a zombie. 2 | #[derive(Debug)] 3 | pub struct ZombieProc {} 4 | 5 | impl ZombieProc { 6 | pub(super) fn new() -> Self { 7 | Self {} 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /legacy/src/rcmgr/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | /// Implementation of RcMgr kernel services. 4 | /// 5 | /// Not sure what the meaning of "Rc". 6 | pub struct RcMgr {} 7 | 8 | impl RcMgr { 9 | pub fn new() -> Arc { 10 | Arc::new(Self {}) 11 | } 12 | 13 | /// See `sceSblRcMgrIsAllowULDebugger` on the PS4 for a reference. 14 | pub fn is_allow_ul_debugger(&self) -> bool { 15 | if !self.is_qa_enabled() { 16 | return false; 17 | } 18 | 19 | todo!() 20 | } 21 | 22 | /// See `sceSblRcMgrIsSoftwagnerQafForAcmgr` on the PS4 for a reference, 23 | pub fn is_softwagner_qaf_for_acmgr(&self) -> bool { 24 | if !self.is_qa_enabled() { 25 | return false; 26 | } 27 | 28 | todo!() 29 | } 30 | 31 | fn is_qa_enabled(&self) -> bool { 32 | false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /legacy/src/sched/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::pcpu::Pcpu; 2 | use crate::process::VThread; 3 | use std::sync::Arc; 4 | 5 | pub use self::runq::*; 6 | 7 | mod runq; 8 | 9 | /// Threads scheduler. 10 | pub struct Scheduler { 11 | rq: Runq, 12 | } 13 | 14 | impl Scheduler { 15 | pub fn new() -> Self { 16 | Self { rq: Runq::new() } 17 | } 18 | 19 | /// See `sched_choose` on the PS4 for a reference. 20 | pub fn choose(&self, cx: &Pcpu) -> Arc { 21 | // TODO: Pull a ready thread from runq. 22 | cx.idle_thread().upgrade().unwrap() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /legacy/src/sched/runq.rs: -------------------------------------------------------------------------------- 1 | use crate::process::VThread; 2 | use std::collections::VecDeque; 3 | use std::sync::{Mutex, Weak}; 4 | 5 | /// Implementation of `runq` structure. 6 | pub struct Runq { 7 | queues: [Mutex>>; 1024], // rq_queues 8 | } 9 | 10 | impl Runq { 11 | pub(super) fn new() -> Self { 12 | Self { 13 | queues: [const { Mutex::new(VecDeque::new()) }; 1024], 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /legacy/src/signal/set.rs: -------------------------------------------------------------------------------- 1 | use super::{strsignal, Signal, SignalIter}; 2 | use std::fmt::{Display, Formatter}; 3 | use std::ops::{BitAndAssign, BitOrAssign, Not}; 4 | 5 | /// An implementation of `sigset_t`. 6 | #[repr(C)] 7 | #[derive(Debug, Clone, Copy, Default)] 8 | pub struct SignalSet { 9 | bits: [u32; 4], 10 | } 11 | 12 | impl SignalSet { 13 | /// An implementation of `SIGISMEMBER`. 14 | pub fn contains(&self, sig: Signal) -> bool { 15 | (self.bits[Self::word(sig)] & Self::bit(sig)) != 0 16 | } 17 | 18 | /// An implementation of `SIGADDSET`. 19 | pub fn add(&mut self, sig: Signal) { 20 | self.bits[Self::word(sig)] |= Self::bit(sig); 21 | } 22 | 23 | /// An implementation of `SIGDELSET`. 24 | pub fn remove(&mut self, sig: Signal) { 25 | self.bits[Self::word(sig)] &= !Self::bit(sig); 26 | } 27 | 28 | // An implementation of `_SIG_IDX`. 29 | fn idx(s: Signal) -> i32 { 30 | s.get() - 1 31 | } 32 | 33 | /// An implementation of `_SIG_WORD`. 34 | fn word(s: Signal) -> usize { 35 | // This is safe because `Signal` is guaranteed to be non-negative. 36 | (Self::idx(s) >> 5).try_into().unwrap() 37 | } 38 | 39 | /// An implementation of `_SIG_BIT`. 40 | fn bit(s: Signal) -> u32 { 41 | 1 << (Self::idx(s) & 31) 42 | } 43 | } 44 | 45 | impl BitAndAssign for SignalSet { 46 | fn bitand_assign(&mut self, rhs: Self) { 47 | for i in 0..4 { 48 | self.bits[i] &= rhs.bits[i]; 49 | } 50 | } 51 | } 52 | 53 | impl BitOrAssign for SignalSet { 54 | fn bitor_assign(&mut self, rhs: Self) { 55 | for i in 0..4 { 56 | self.bits[i] |= rhs.bits[i]; 57 | } 58 | } 59 | } 60 | 61 | impl Not for SignalSet { 62 | type Output = Self; 63 | 64 | fn not(mut self) -> Self::Output { 65 | for i in 0..4 { 66 | self.bits[i] = !self.bits[i]; 67 | } 68 | self 69 | } 70 | } 71 | 72 | impl Display for SignalSet { 73 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 74 | let mut first = true; 75 | 76 | for sig in SignalIter::new().filter(|sig| self.contains(*sig)) { 77 | if !first { 78 | f.write_str(" | ")?; 79 | } 80 | 81 | f.write_str(strsignal(sig).as_ref())?; 82 | first = false; 83 | } 84 | 85 | if first { 86 | f.write_str("none")?; 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /legacy/src/syscalls/error.rs: -------------------------------------------------------------------------------- 1 | use crate::errno::{strerror, Errno}; 2 | use std::error::Error; 3 | use std::fmt::{Display, Formatter}; 4 | use std::num::NonZeroI32; 5 | 6 | /// Error of each syscall. 7 | #[derive(Debug)] 8 | pub enum SysErr { 9 | Raw(NonZeroI32), 10 | Object(Box), 11 | } 12 | 13 | impl SysErr { 14 | pub fn errno(&self) -> NonZeroI32 { 15 | match self { 16 | Self::Raw(v) => *v, 17 | Self::Object(v) => v.errno(), 18 | } 19 | } 20 | } 21 | 22 | impl From> for SysErr { 23 | fn from(value: Box) -> Self { 24 | Self::Object(value) 25 | } 26 | } 27 | 28 | impl From for SysErr { 29 | fn from(value: T) -> Self { 30 | Self::Object(Box::new(value)) 31 | } 32 | } 33 | 34 | impl Error for SysErr { 35 | fn source(&self) -> Option<&(dyn Error + 'static)> { 36 | match self { 37 | Self::Raw(_) => None, 38 | Self::Object(e) => e.source(), 39 | } 40 | } 41 | } 42 | 43 | impl Display for SysErr { 44 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 45 | match self { 46 | Self::Raw(v) => f.write_str(strerror(*v)), 47 | Self::Object(e) => Display::fmt(&e, f), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /legacy/src/syscalls/output.rs: -------------------------------------------------------------------------------- 1 | use std::num::NonZeroI32; 2 | 3 | /// Outputs of the syscall entry point. 4 | #[repr(C)] 5 | #[derive(Clone, Copy)] 6 | pub struct SysOut { 7 | rax: usize, 8 | rdx: usize, 9 | } 10 | 11 | impl SysOut { 12 | pub const ZERO: Self = Self { rax: 0, rdx: 0 }; 13 | } 14 | 15 | impl From<*mut T> for SysOut { 16 | fn from(value: *mut T) -> Self { 17 | Self { 18 | rax: value as _, 19 | rdx: 0, 20 | } 21 | } 22 | } 23 | 24 | impl From for SysOut { 25 | fn from(value: bool) -> Self { 26 | Self { 27 | rax: value.into(), 28 | rdx: 0, 29 | } 30 | } 31 | } 32 | 33 | impl From for SysOut { 34 | fn from(value: i32) -> Self { 35 | Self { 36 | rax: value as isize as usize, // Sign extended. 37 | rdx: 0, 38 | } 39 | } 40 | } 41 | 42 | impl From for SysOut { 43 | fn from(value: i64) -> Self { 44 | Self { 45 | rax: value as usize, 46 | rdx: 0, 47 | } 48 | } 49 | } 50 | 51 | impl From for SysOut { 52 | fn from(value: usize) -> Self { 53 | Self { rax: value, rdx: 0 } 54 | } 55 | } 56 | 57 | impl From for SysOut { 58 | fn from(value: NonZeroI32) -> Self { 59 | Self { 60 | rax: value.get() as isize as usize, // Sign extended. 61 | rdx: 0, 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /legacy/src/sysent/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::syscalls::Syscalls; 2 | use std::sync::Arc; 3 | 4 | /// Implementation of `sysentvec` structure. 5 | #[derive(Debug)] 6 | pub struct ProcAbi { 7 | syscalls: Option>, // sv_size + sv_table 8 | } 9 | 10 | impl ProcAbi { 11 | pub fn new(syscalls: Option>) -> Self { 12 | Self { syscalls } 13 | } 14 | 15 | pub fn syscalls(&self) -> Option<&Arc> { 16 | self.syscalls.as_ref() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /legacy/src/ucred/id.rs: -------------------------------------------------------------------------------- 1 | /// An implementation of `uid_t`. 2 | #[repr(transparent)] 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 4 | pub struct Uid(i32); 5 | 6 | impl Uid { 7 | pub const ROOT: Self = Self(0); 8 | 9 | pub const fn new(v: i32) -> Option { 10 | if v >= 0 { 11 | Some(Self(v)) 12 | } else { 13 | None 14 | } 15 | } 16 | } 17 | 18 | /// An implementation of `gid_t`. 19 | #[repr(transparent)] 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 21 | pub struct Gid(i32); 22 | 23 | impl Gid { 24 | pub const ROOT: Self = Self(0); 25 | 26 | pub const fn new(v: i32) -> Option { 27 | if v >= 0 { 28 | Some(Self(v)) 29 | } else { 30 | None 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /legacy/src/ucred/privilege.rs: -------------------------------------------------------------------------------- 1 | macro_rules! privileges { 2 | ( 3 | $( #[$attr:meta] )* 4 | pub enum $name:ident { 5 | $( 6 | $( #[$var_attr:meta] )* 7 | $variant:ident = $value:expr, 8 | )* 9 | } 10 | ) => { 11 | $( #[$attr] )* 12 | pub enum $name { 13 | $( 14 | $( #[$var_attr] )* 15 | #[allow(non_camel_case_types)] 16 | $variant = $value 17 | ),* 18 | } 19 | 20 | impl std::fmt::Display for $name { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | match *self { 23 | $( 24 | Self::$variant => f.write_str(stringify!($variant)), 25 | )* 26 | } 27 | } 28 | } 29 | }; 30 | } 31 | 32 | privileges! { 33 | /// Privilege identifier. 34 | /// 35 | /// See https://github.com/freebsd/freebsd-src/blob/release/9.1.0/sys/sys/priv.h for standard 36 | /// FreeBSD privileges. 37 | #[repr(i32)] 38 | #[derive(PartialEq, Eq)] 39 | pub enum Privilege { 40 | /// Exceed system open files limit. 41 | #[allow(unused)] 42 | MAXFILES = 3, 43 | /// Exempt bsd.seeothergids. 44 | #[allow(unused)] 45 | SEEOTHERGIDS = 59, 46 | /// Exempt bsd.seeotheruids. 47 | #[allow(unused)] 48 | SEEOTHERUIDS = 60, 49 | /// Can call setlogin. 50 | PROC_SETLOGIN = 161, 51 | /// Override vnode DAC read perm. 52 | VFS_READ = 310, 53 | /// Override vnode DAC write perm. 54 | VFS_WRITE = 311, 55 | /// Override vnode DAC admin perm. 56 | VFS_ADMIN = 312, 57 | /// Override vnode DAC exec perm. 58 | VFS_EXEC = 313, 59 | /// Override vnode DAC lookup perm. 60 | VFS_LOOKUP = 314, 61 | /// Currently unknown. 62 | SCE680 = 680, 63 | /// Currently unknown. 64 | SCE683 = 683, 65 | /// Currently unknown. 66 | #[allow(unused)] 67 | SCE685 = 685, 68 | /// Currently unknown. 69 | SCE686 = 686, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /legacy/src/vm/iter.rs: -------------------------------------------------------------------------------- 1 | use super::Alloc; 2 | use std::collections::BTreeMap; 3 | 4 | /// An iterator to enumerate all [`Alloc`] as mutable starting within the specified address. 5 | pub(super) struct StartFromMut<'a> { 6 | iter: Option>, 7 | } 8 | 9 | impl<'a> StartFromMut<'a> { 10 | pub fn new(map: &'a mut BTreeMap, addr: usize) -> Self { 11 | // Find the first allocation info. 12 | let first = match map.range(..=addr).next_back() { 13 | Some(v) => v.1, 14 | None => return Self { iter: None }, 15 | }; 16 | 17 | // Check if the target address is in the range of first allocation. 18 | Self { 19 | iter: if (first.end() as usize) <= addr { 20 | None 21 | } else { 22 | Some(map.range_mut((first.addr as usize)..)) 23 | }, 24 | } 25 | } 26 | } 27 | 28 | impl<'a> Iterator for StartFromMut<'a> { 29 | type Item = (usize, &'a mut Alloc); 30 | 31 | fn next(&mut self) -> Option { 32 | let iter = match &mut self.iter { 33 | Some(v) => v, 34 | None => return None, 35 | }; 36 | 37 | match iter.next() { 38 | Some(v) => Some((*v.0, v.1)), 39 | None => { 40 | self.iter = None; 41 | None 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /legacy/src/vm/page.rs: -------------------------------------------------------------------------------- 1 | use super::VmSpace; 2 | 3 | /// Encapsulated one or more virtual pages. 4 | pub struct VPages<'a> { 5 | mm: &'a VmSpace, 6 | ptr: *mut u8, 7 | len: usize, 8 | } 9 | 10 | impl<'a> VPages<'a> { 11 | pub(super) fn new(mm: &'a VmSpace, ptr: *mut u8, len: usize) -> Self { 12 | Self { mm, ptr, len } 13 | } 14 | 15 | pub fn addr(&self) -> usize { 16 | self.ptr as _ 17 | } 18 | 19 | pub fn end(&self) -> *const u8 { 20 | unsafe { self.ptr.add(self.len) } 21 | } 22 | 23 | pub fn as_mut_ptr(&mut self) -> *mut u8 { 24 | self.ptr 25 | } 26 | 27 | pub fn into_raw(self) -> *mut u8 { 28 | let ptr = self.ptr; 29 | std::mem::forget(self); 30 | ptr 31 | } 32 | } 33 | 34 | impl<'a> Drop for VPages<'a> { 35 | fn drop(&mut self) { 36 | self.mm.munmap(self.ptr, self.len).unwrap(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /legacy/src/vm/stack.rs: -------------------------------------------------------------------------------- 1 | use super::Protections; 2 | use std::ptr::null_mut; 3 | 4 | /// Contains information about the stack of the application main thread. 5 | #[derive(Debug)] 6 | pub struct AppStack { 7 | guard: *mut u8, 8 | stack: *mut u8, 9 | len: usize, 10 | prot: Protections, 11 | } 12 | 13 | impl AppStack { 14 | pub(super) fn new() -> Self { 15 | Self { 16 | guard: null_mut(), 17 | stack: null_mut(), 18 | len: 0x200000, 19 | prot: Protections::CPU_READ | Protections::CPU_WRITE, 20 | } 21 | } 22 | 23 | pub fn guard(&self) -> usize { 24 | self.guard as _ 25 | } 26 | 27 | pub fn start(&self) -> *mut u8 { 28 | self.stack 29 | } 30 | 31 | pub fn end(&self) -> *const u8 { 32 | unsafe { self.stack.add(self.len) } 33 | } 34 | 35 | pub fn len(&self) -> usize { 36 | self.len 37 | } 38 | 39 | pub fn prot(&self) -> Protections { 40 | self.prot 41 | } 42 | 43 | pub(super) fn set_guard(&mut self, v: *mut u8) { 44 | self.guard = v; 45 | } 46 | 47 | pub(super) fn set_stack(&mut self, v: *mut u8) { 48 | self.stack = v; 49 | } 50 | } 51 | 52 | unsafe impl Send for AppStack {} 53 | -------------------------------------------------------------------------------- /lib/aarch64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aarch64" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bitfield-struct = "0.10.1" 8 | -------------------------------------------------------------------------------- /lib/aarch64/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /lib/aarch64/src/exception.rs: -------------------------------------------------------------------------------- 1 | use bitfield_struct::bitfield; 2 | 3 | /// Raw value of `VBAR_EL1`. 4 | #[bitfield(u64)] 5 | pub struct Vbar { 6 | #[bits(11)] 7 | __: u16, 8 | #[bits(53)] 9 | pub addr: u64, 10 | } 11 | 12 | /// Raw value of `ESR_EL1`, `ESR_EL2` and `ESR_EL3`. 13 | #[bitfield(u64)] 14 | pub struct Esr { 15 | #[bits(25)] 16 | pub iss: u32, 17 | pub il: bool, 18 | #[bits(6)] 19 | pub ec: u8, 20 | #[bits(24)] 21 | pub iss2: u32, 22 | __: u8, 23 | } 24 | -------------------------------------------------------------------------------- /lib/aarch64/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use self::exception::*; 4 | 5 | mod exception; 6 | -------------------------------------------------------------------------------- /lib/bitflag/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitflag" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /lib/bitflag/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub use self::mask::*; 4 | 5 | use core::ops::{BitOr, Not}; 6 | 7 | mod mask; 8 | 9 | /// Type of bit flag. 10 | pub trait Type: From { 11 | type Raw: Raw; 12 | } 13 | 14 | /// Underlying type of [`Type`]. 15 | pub trait Raw: Eq + BitOr + Not + Copy {} 16 | 17 | impl Raw for u32 {} 18 | 19 | /// Provides method to construct a value from [`Raw`]. 20 | pub trait FromRaw: Sized { 21 | fn from_raw(raw: T) -> Option; 22 | } 23 | 24 | impl FromRaw for u16 { 25 | fn from_raw(raw: u32) -> Option { 26 | raw.try_into().ok() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/bitflag/src/mask.rs: -------------------------------------------------------------------------------- 1 | use crate::Type; 2 | use core::marker::PhantomData; 3 | use core::ops::{BitOr, Not}; 4 | 5 | /// Mask of a value in the set. 6 | pub struct Mask { 7 | mask: T::Raw, 8 | phantom: PhantomData, 9 | } 10 | 11 | impl Mask { 12 | /// # Safety 13 | /// `mask` must be valid for `V` (e.g. has exactly one bit set if `V` is `bool`) and must not 14 | /// have zero bit in the middle. 15 | pub const unsafe fn new(mask: T::Raw) -> Self { 16 | Self { 17 | mask, 18 | phantom: PhantomData, 19 | } 20 | } 21 | 22 | pub const fn mask(self) -> T::Raw { 23 | self.mask 24 | } 25 | } 26 | 27 | impl Clone for Mask { 28 | fn clone(&self) -> Self { 29 | *self 30 | } 31 | } 32 | 33 | impl Copy for Mask {} 34 | 35 | impl BitOr for Mask { 36 | type Output = T; 37 | 38 | fn bitor(self, rhs: Self) -> Self::Output { 39 | T::from(self.mask | rhs.mask) 40 | } 41 | } 42 | 43 | impl Not for Mask { 44 | type Output = T; 45 | 46 | fn not(self) -> Self::Output { 47 | T::from(!self.mask) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/hv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hv" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | rustc-hash = "2.1.1" 8 | thiserror = "2.0.11" 9 | 10 | [target.'cfg(target_arch = "aarch64")'.dependencies] 11 | aarch64 = { path = "../aarch64" } 12 | bitfield-struct = "0.10.1" 13 | 14 | [target.'cfg(target_arch = "x86_64")'.dependencies] 15 | x86-64 = { path = "../x86-64" } 16 | 17 | [target.'cfg(unix)'.dependencies] 18 | libc = "0.2.170" 19 | 20 | [target.'cfg(target_os = "macos")'.dependencies] 21 | applevisor-sys = "0.1.3" 22 | 23 | [target.'cfg(target_os = "windows")'.dependencies] 24 | windows-sys = { version = "0.59.0", features = ["Win32_System_Hypervisor"] } 25 | -------------------------------------------------------------------------------- /lib/hv/src/macos/mapper.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT OR Apache-2.0 2 | use crate::RamMapper; 3 | use applevisor_sys::{HV_MEMORY_EXEC, HV_MEMORY_READ, HV_MEMORY_WRITE, hv_vm_map}; 4 | use std::error::Error; 5 | use std::num::NonZero; 6 | 7 | /// Implementation of [`RamMapper`] for Hypervisor Framework. 8 | pub struct HvfMapper; 9 | 10 | impl RamMapper for HvfMapper { 11 | unsafe fn map( 12 | &self, 13 | host: *mut u8, 14 | vm: usize, 15 | len: NonZero, 16 | ) -> Result<(), Box> { 17 | let host = host.cast(); 18 | let vm = vm.try_into().unwrap(); 19 | let prot = HV_MEMORY_READ | HV_MEMORY_WRITE | HV_MEMORY_EXEC; 20 | let ret = unsafe { hv_vm_map(host, vm, len.get(), prot) }; 21 | 22 | match NonZero::new(ret) { 23 | Some(ret) => Err(format!("hv_vm_map() = {ret:#x}").into()), 24 | None => Ok(()), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/hv/src/ram/mapper.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::num::NonZero; 3 | 4 | /// Provides a method to map a portion of RAM dynamically. 5 | pub(crate) trait RamMapper: Send + Sync + 'static { 6 | /// # Safety 7 | /// The range specified with `host` and `len` will be shared with the VM, which mean it should 8 | /// not contains any data that should not visible to the VM. 9 | unsafe fn map( 10 | &self, 11 | host: *mut u8, 12 | vm: usize, 13 | len: NonZero, 14 | ) -> Result<(), Box>; 15 | } 16 | 17 | impl RamMapper for () { 18 | unsafe fn map( 19 | &self, 20 | _: *mut u8, 21 | _: usize, 22 | _: NonZero, 23 | ) -> Result<(), Box> { 24 | Ok(()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/hv/src/ram/unix.rs: -------------------------------------------------------------------------------- 1 | use libc::{ 2 | _SC_PAGE_SIZE, MAP_ANON, MAP_FAILED, MAP_FIXED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE, 3 | mmap, mprotect, munmap, sysconf, 4 | }; 5 | use std::io::Error; 6 | use std::num::NonZero; 7 | use std::ptr::null_mut; 8 | 9 | pub fn get_page_size() -> Result, Error> { 10 | let v = unsafe { sysconf(_SC_PAGE_SIZE) }; 11 | 12 | if v < 0 { 13 | Err(Error::last_os_error()) 14 | } else { 15 | Ok(v.try_into().ok().and_then(NonZero::new).unwrap()) 16 | } 17 | } 18 | 19 | pub fn reserve(len: NonZero) -> Result<*mut u8, Error> { 20 | let prot = PROT_NONE; 21 | let flags = MAP_PRIVATE | MAP_ANON; 22 | let mem = unsafe { mmap(null_mut(), len.get(), prot, flags, -1, 0) }; 23 | 24 | if mem == MAP_FAILED { 25 | return Err(Error::last_os_error()); 26 | } 27 | 28 | Ok(mem.cast()) 29 | } 30 | 31 | pub unsafe fn free(addr: *const u8, len: NonZero) -> Result<(), Error> { 32 | if unsafe { munmap(addr.cast_mut().cast(), len.get()) } < 0 { 33 | Err(Error::last_os_error()) 34 | } else { 35 | Ok(()) 36 | } 37 | } 38 | 39 | pub unsafe fn commit(addr: *const u8, len: NonZero) -> Result<(), Error> { 40 | let addr = addr.cast_mut().cast(); 41 | let prot = PROT_READ | PROT_WRITE; 42 | let flags = MAP_PRIVATE | MAP_ANON | MAP_FIXED; 43 | let ptr = unsafe { mmap(addr, len.get(), prot, flags, -1, 0) }; 44 | 45 | if ptr == MAP_FAILED { 46 | Err(Error::last_os_error()) 47 | } else { 48 | Ok(()) 49 | } 50 | } 51 | 52 | pub unsafe fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { 53 | if unsafe { mprotect(addr.cast(), len, PROT_NONE) < 0 } { 54 | Err(Error::last_os_error()) 55 | } else { 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/hv/src/ram/windows.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use std::mem::zeroed; 3 | use std::num::NonZero; 4 | use std::ptr::null; 5 | use windows_sys::Win32::System::Memory::{ 6 | MEM_COMMIT, MEM_DECOMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_NOACCESS, PAGE_READWRITE, 7 | VirtualAlloc, VirtualFree, 8 | }; 9 | use windows_sys::Win32::System::SystemInformation::GetSystemInfo; 10 | 11 | pub fn get_page_size() -> Result, Error> { 12 | let mut i = unsafe { zeroed() }; 13 | 14 | unsafe { GetSystemInfo(&mut i) }; 15 | 16 | Ok(i.dwPageSize.try_into().ok().and_then(NonZero::new).unwrap()) 17 | } 18 | 19 | pub fn reserve(len: NonZero) -> Result<*mut u8, Error> { 20 | let mem = unsafe { VirtualAlloc(null(), len.get(), MEM_RESERVE, PAGE_NOACCESS) }; 21 | 22 | if mem.is_null() { 23 | return Err(Error::last_os_error()); 24 | } 25 | 26 | Ok(mem.cast()) 27 | } 28 | 29 | pub unsafe fn free(addr: *const u8, _: NonZero) -> Result<(), Error> { 30 | if unsafe { VirtualFree(addr.cast_mut().cast(), 0, MEM_RELEASE) } == 0 { 31 | Err(Error::last_os_error()) 32 | } else { 33 | Ok(()) 34 | } 35 | } 36 | 37 | pub unsafe fn commit(addr: *const u8, len: NonZero) -> Result<(), Error> { 38 | let ptr = unsafe { VirtualAlloc(addr.cast(), len.get(), MEM_COMMIT, PAGE_READWRITE) }; 39 | 40 | if ptr.is_null() { 41 | Err(Error::last_os_error()) 42 | } else { 43 | Ok(()) 44 | } 45 | } 46 | 47 | pub unsafe fn decommit(addr: *mut u8, len: usize) -> Result<(), Error> { 48 | if unsafe { VirtualFree(addr.cast(), len, MEM_DECOMMIT) == 0 } { 49 | Err(Error::last_os_error()) 50 | } else { 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/krt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "krt" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anstyle = { version = "1.0.10", default-features = false } 8 | config = { path = "../../config", features = ["virt"] } 9 | -------------------------------------------------------------------------------- /lib/krt/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use config::BootEnv; 2 | use core::ptr::null; 3 | 4 | pub fn boot_env() -> &'static BootEnv { 5 | // SAFETY: This is safe because the setup() requirements. 6 | unsafe { &*BOOT_ENV } 7 | } 8 | 9 | /// # Safety 10 | /// This function must be called immediately in the [_start](super::_start) function. After that it 11 | /// must never be called again. 12 | #[allow(dead_code)] 13 | pub(super) unsafe fn setup(env: &'static BootEnv) { 14 | unsafe { BOOT_ENV = env }; 15 | } 16 | 17 | static mut BOOT_ENV: *const BootEnv = null(); 18 | -------------------------------------------------------------------------------- /lib/krt/src/console/vm.rs: -------------------------------------------------------------------------------- 1 | use config::{ConsoleMemory, ConsoleType, Vm}; 2 | use core::cmp::min; 3 | use core::fmt::{Display, Write}; 4 | use core::num::NonZero; 5 | use core::ptr::write_volatile; 6 | 7 | pub fn print(env: &Vm, ty: ConsoleType, msg: impl Display) { 8 | let c = env.console as *mut ConsoleMemory; 9 | let mut w = Writer { 10 | con: c, 11 | buf: [0; 1024], 12 | len: 0, 13 | }; 14 | 15 | writeln!(w, "{msg}").unwrap(); 16 | drop(w); 17 | 18 | unsafe { write_volatile(&raw mut (*c).commit, ty) }; 19 | } 20 | 21 | /// [Write] implementation to write the message to the VMM console. 22 | struct Writer { 23 | con: *mut ConsoleMemory, 24 | buf: [u8; 1024], 25 | len: usize, 26 | } 27 | 28 | impl Writer { 29 | fn flush(&mut self) { 30 | let len = match NonZero::new(self.len) { 31 | Some(v) => v, 32 | None => return, 33 | }; 34 | 35 | unsafe { write_volatile(&raw mut (*self.con).msg_len, len) }; 36 | unsafe { write_volatile(&raw mut (*self.con).msg_addr, self.buf.as_ptr() as _) }; 37 | 38 | self.len = 0; 39 | } 40 | } 41 | 42 | impl Drop for Writer { 43 | fn drop(&mut self) { 44 | self.flush(); 45 | } 46 | } 47 | 48 | impl Write for Writer { 49 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 50 | let mut s = s.as_bytes(); 51 | 52 | while !s.is_empty() { 53 | // Append to the available buffer. 54 | let available = self.buf.len() - self.len; 55 | let len = min(s.len(), available); 56 | let (src, remain) = s.split_at(len); 57 | 58 | self.buf[self.len..(self.len + len)].copy_from_slice(src); 59 | self.len += len; 60 | 61 | // Flush if the buffer is full. 62 | if self.len == self.buf.len() { 63 | self.flush(); 64 | } 65 | 66 | s = remain; 67 | } 68 | 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/krt/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Minimal Rust runtime for the kernel. 2 | //! 3 | //! This crate provides foundations for the kernel to run. Its contains panic handler, console I/O 4 | //! and other stuff. All of the provided functionalities here can be used immediately when 5 | //! the execution has been reached the kernel entry point and it can also used on interrupt handler. 6 | #![no_std] 7 | 8 | pub use self::config::*; 9 | pub use self::console::*; 10 | 11 | use core::panic::PanicInfo; 12 | 13 | mod config; 14 | mod console; 15 | mod panic; 16 | 17 | /// Entry point of the kernel. 18 | /// 19 | /// This will be called by a bootloader or a hypervisor. The following are requirements to call this 20 | /// function: 21 | /// 22 | /// 1. The kernel does not remap itself so it must be mapped at a desired virtual address and all 23 | /// relocations must be applied. This imply that the kernel can only be run in a virtual address 24 | /// space. 25 | /// 2. Interrupt is disabled. 26 | /// 3. Only main CPU can execute this function. 27 | #[cfg(target_os = "none")] 28 | #[unsafe(no_mangle)] 29 | extern "C" fn _start(env: &'static ::config::BootEnv, config: &'static ::config::Config) -> ! { 30 | // SAFETY: We call it as the first thing here. 31 | unsafe { self::config::setup(env) }; 32 | main(config); 33 | } 34 | 35 | #[allow(dead_code)] 36 | #[cfg_attr(target_os = "none", panic_handler)] 37 | fn panic(i: &PanicInfo) -> ! { 38 | let (file, line) = match i.location() { 39 | Some(v) => (v.file(), v.line()), 40 | None => ("unknown", 0), 41 | }; 42 | 43 | // Print the message. 44 | self::console::error(file, line, format_args!("Kernel panic - {}.", i.message())); 45 | self::panic::panic(); 46 | } 47 | 48 | #[cfg(target_os = "none")] 49 | unsafe extern "Rust" { 50 | safe fn main(config: &'static ::config::Config) -> !; 51 | } 52 | -------------------------------------------------------------------------------- /lib/krt/src/panic/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::boot_env; 2 | use config::BootEnv; 3 | 4 | mod vm; 5 | 6 | /// Perform panic after printing the panic message. 7 | pub fn panic() -> ! { 8 | match boot_env() { 9 | BootEnv::Vm(env) => self::vm::panic(env), 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/krt/src/panic/vm.rs: -------------------------------------------------------------------------------- 1 | use config::{KernelExit, Vm, VmmMemory}; 2 | use core::hint::unreachable_unchecked; 3 | use core::ptr::{addr_of_mut, write_volatile}; 4 | 5 | pub fn panic(env: &Vm) -> ! { 6 | let vmm = env.vmm as *mut VmmMemory; 7 | 8 | unsafe { write_volatile(addr_of_mut!((*vmm).shutdown), KernelExit::Panic) }; 9 | unsafe { unreachable_unchecked() }; 10 | } 11 | -------------------------------------------------------------------------------- /lib/x86-64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x86-64" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | bitfield-struct = "0.10.1" 8 | -------------------------------------------------------------------------------- /lib/x86-64/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /lib/x86-64/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | pub use self::msr::*; 3 | pub use self::segment::*; 4 | 5 | use bitfield_struct::bitfield; 6 | 7 | mod msr; 8 | mod segment; 9 | 10 | /// Represents a value of `RFLAGS`. 11 | /// 12 | /// See RFLAGS Register section on AMD64 Architecture Programmer's Manual Volume 2 for more details. 13 | #[bitfield(u64)] 14 | pub struct Rflags { 15 | pub cf: bool, 16 | pub reserved: bool, 17 | pub pf: bool, 18 | __: bool, 19 | pub af: bool, 20 | __: bool, 21 | pub zf: bool, 22 | pub sf: bool, 23 | pub tf: bool, 24 | pub r#if: bool, 25 | pub df: bool, 26 | pub of: bool, 27 | #[bits(2)] 28 | pub iopl: u8, 29 | pub nt: bool, 30 | __: bool, 31 | pub rf: bool, 32 | pub vm: bool, 33 | pub ac: bool, 34 | pub vif: bool, 35 | pub vip: bool, 36 | pub id: bool, 37 | #[bits(42)] 38 | __: u64, 39 | } 40 | -------------------------------------------------------------------------------- /lib/x86-64/src/msr.rs: -------------------------------------------------------------------------------- 1 | use crate::SegmentSelector; 2 | use bitfield_struct::bitfield; 3 | 4 | /// Raw value of `EFER` register. 5 | /// 6 | /// See Extended Feature Enable Register (EFER) section on AMD64 Architecture Programmer's Manual 7 | /// Volume 2 for more details. 8 | #[bitfield(u64)] 9 | pub struct Efer { 10 | pub sce: bool, 11 | #[bits(7)] 12 | __: u8, 13 | pub lme: bool, 14 | __: bool, 15 | pub lma: bool, 16 | pub nxe: bool, 17 | pub svme: bool, 18 | pub lmsle: bool, 19 | pub ffxsr: bool, 20 | pub tce: bool, 21 | __: bool, 22 | pub mcommit: bool, 23 | pub intwb: bool, 24 | __: bool, 25 | pub uaie: bool, 26 | pub aibrse: bool, 27 | #[bits(42)] 28 | __: u64, 29 | } 30 | 31 | /// Raw value of `STAR` register. 32 | /// 33 | /// See SYSCALL and SYSRET section on AMD64 Architecture Programmer's Manual Volume 2 for more 34 | /// details. 35 | #[bitfield(u64)] 36 | pub struct Star { 37 | pub syscall_eip: u32, 38 | #[bits(16)] 39 | pub syscall_sel: SegmentSelector, 40 | #[bits(16)] 41 | pub sysret_sel: SegmentSelector, 42 | } 43 | -------------------------------------------------------------------------------- /macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macros" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | proc-macro2 = "1.0" 11 | quote = "1.0" 12 | syn = { version = "2.0", features = ["full", "extra-traits"] } 13 | -------------------------------------------------------------------------------- /macros/src/enum_conversions.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::ItemEnum; 4 | 5 | pub fn transform(arg: ItemEnum) -> syn::Result { 6 | let enum_name = &arg.ident; 7 | 8 | let mut stream = TokenStream::new(); 9 | 10 | for variant in arg.variants.iter() { 11 | if variant.fields.len() != 1 { 12 | return Err(syn::Error::new_spanned( 13 | variant, 14 | "expected variant to have one field", 15 | )); 16 | } 17 | 18 | let field = variant.fields.iter().next().unwrap(); 19 | 20 | let variant_name = &variant.ident; 21 | let variant_type = &field.ty; 22 | 23 | let impls = quote! { 24 | impl From<#variant_type> for #enum_name { 25 | fn from(v: #variant_type) -> Self { 26 | Self::#variant_name(v) 27 | } 28 | } 29 | 30 | impl TryFrom<#enum_name> for #variant_type { 31 | type Error = #enum_name; 32 | 33 | fn try_from(v: #enum_name) -> Result { 34 | match v { 35 | #enum_name::#variant_name(v) => Ok(v), 36 | _ => Err(v), 37 | } 38 | } 39 | } 40 | 41 | impl<'a> TryFrom<&'a #enum_name> for &'a #variant_type { 42 | type Error = &'a #enum_name; 43 | 44 | fn try_from(v: &'a #enum_name) -> Result { 45 | match v { 46 | #enum_name::#variant_name(v) => Ok(v), 47 | _ => Err(v), 48 | } 49 | } 50 | } 51 | 52 | impl<'a> TryFrom<&'a mut #enum_name> for &'a mut #variant_type { 53 | type Error = &'a mut #enum_name; 54 | 55 | fn try_from(v: &'a mut #enum_name) -> Result { 56 | match v { 57 | #enum_name::#variant_name(v) => Ok(v), 58 | _ => Err(v), 59 | } 60 | } 61 | } 62 | }; 63 | 64 | stream.extend(impls); 65 | } 66 | Ok(stream) 67 | } 68 | -------------------------------------------------------------------------------- /macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{Error, ItemEnum, ItemStatic, LitStr, parse_macro_input}; 3 | 4 | mod bitflag; 5 | mod elf; 6 | mod enum_conversions; 7 | mod errno; 8 | mod vpath; 9 | 10 | /// The reason we use `bitflag` as a name instead of `bitflags` is to make it matched with 11 | /// `bitfield-struct` crate. 12 | #[proc_macro_attribute] 13 | pub fn bitflag(args: TokenStream, item: TokenStream) -> TokenStream { 14 | let item = parse_macro_input!(item as ItemEnum); 15 | let mut opts = self::bitflag::Options::default(); 16 | let parser = syn::meta::parser(|m| opts.parse(m)); 17 | 18 | parse_macro_input!(args with parser); 19 | 20 | self::bitflag::transform(opts, item) 21 | .unwrap_or_else(Error::into_compile_error) 22 | .into() 23 | } 24 | 25 | /// Note will not produced for test target. 26 | #[proc_macro_attribute] 27 | pub fn elf_note(args: TokenStream, item: TokenStream) -> TokenStream { 28 | let item = parse_macro_input!(item as ItemStatic); 29 | let mut opts = self::elf::Options::default(); 30 | let parser = syn::meta::parser(|m| opts.parse(m)); 31 | 32 | parse_macro_input!(args with parser); 33 | 34 | self::elf::transform_note(opts, item) 35 | .unwrap_or_else(Error::into_compile_error) 36 | .into() 37 | } 38 | 39 | #[proc_macro_derive(EnumConversions)] 40 | pub fn implement_conversions(item: TokenStream) -> TokenStream { 41 | let item = parse_macro_input!(item as ItemEnum); 42 | 43 | enum_conversions::transform(item) 44 | .unwrap_or_else(Error::into_compile_error) 45 | .into() 46 | } 47 | 48 | #[proc_macro_derive(Errno, attributes(errno))] 49 | pub fn implement_errno(item: TokenStream) -> TokenStream { 50 | let item = parse_macro_input!(item as ItemEnum); 51 | 52 | errno::transform(item) 53 | .unwrap_or_else(Error::into_compile_error) 54 | .into() 55 | } 56 | 57 | #[proc_macro] 58 | pub fn vpath(arg: TokenStream) -> TokenStream { 59 | let arg = parse_macro_input!(arg as LitStr); 60 | 61 | vpath::transform(arg) 62 | .unwrap_or_else(Error::into_compile_error) 63 | .into() 64 | } 65 | -------------------------------------------------------------------------------- /macros/src/vpath.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote_spanned; 3 | use syn::{Error, LitStr}; 4 | 5 | pub fn transform(arg: LitStr) -> syn::Result { 6 | // TODO: Merge this with VPath::is_valid(). 7 | let span = arg.span(); 8 | let arg = arg.value(); 9 | 10 | if arg.is_empty() { 11 | return Err(Error::new(span, "cannot be an empty string")); 12 | } else if !arg.starts_with('/') { 13 | return Err(Error::new(span, "cannot begin with `/`")); 14 | } else if arg.ends_with('/') { 15 | return Err(Error::new(span, "cannot end with `/`")); 16 | } 17 | 18 | // Check thoroughly. 19 | let mut sep = 0; 20 | 21 | for (i, ch) in arg.bytes().enumerate() { 22 | if i == 0 || ch != b'/' { 23 | continue; 24 | } 25 | 26 | // Disallow a consecutive of the separator, "." and "..". 27 | let com = &arg[(sep + 1)..i]; 28 | 29 | if com.is_empty() { 30 | return Err(Error::new(span, "cannot contains consecutive of `/`")); 31 | } else if com == "." { 32 | return Err(Error::new(span, "cannot contains `/./`")); 33 | } else if com == ".." { 34 | return Err(Error::new(span, "cannot contains `/../`")); 35 | } 36 | 37 | sep = i; 38 | } 39 | 40 | Ok(quote_spanned!(span=> unsafe { crate::fs::VPath::new_unchecked(#arg) })) 41 | } 42 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Obliteration Developer 2 | nav: 3 | - 'index.md' 4 | - 'building.md' 5 | - 'Reference': 6 | - 'Kernel': 'https://dev.obliteration.net/crates/obkrnl' 7 | repo_url: https://github.com/obhq/obliteration 8 | repo_name: obhq/obliteration 9 | edit_uri: edit/main/docs/ 10 | markdown_extensions: 11 | - mdx_truly_sane_lists 12 | theme: 13 | name: readthedocs 14 | --------------------------------------------------------------------------------