diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7eabb8e..a802cdb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,11 +4,16 @@ version: 2 updates: - package-ecosystem: github-actions directory: / + target-branch: development + rebase-strategy: auto schedule: interval: weekly reviewers: - georglauterbach + assignees: + - georglauterbach labels: - area/ci + - kind/update - priority/low - meta/needs-triage diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 48bcb9c..d1d8044 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -6,7 +6,7 @@ on: # yamllint disable-line rule:truthy workflow_dispatch: push: branches: - - master + - master, - development paths: - .github/workflows/documentation.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: Check if deploy is for a `v.` tag version instead of `edge` if: startsWith(github.ref, 'refs/tags/') @@ -73,10 +73,10 @@ jobs: needs: deployment steps: - name: Checkout the tagged commit (shallow clone) - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 - name: Checkout the documentation deployment branch to a subdirectory - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: ref: gh-pages path: gh-pages diff --git a/.github/workflows/kernel-tests.yml b/.github/workflows/kernel-tests.yml index d6b6f74..5d0c15a 100644 --- a/.github/workflows/kernel-tests.yml +++ b/.github/workflows/kernel-tests.yml @@ -4,12 +4,24 @@ name: Kernel Code Tests on: # yamllint disable-line rule:truthy workflow_dispatch: pull_request: - paths: [modules/**] + paths: + - .github/workflows/kernel-tests.yml + - kernel/** + branches: ['*'] + push: + paths: [kernel/**] + branches: [master] defaults: run: shell: bash - working-directory: modules + working-directory: kernel + +env: + BUILD_FLAGS: > + -Z build-std=core,compiler_builtins,alloc + -Z build-std-features=compiler-builtins-mem + BUILD_TARGET_PATH: --target targets/x86_64-unknown-none.json jobs: linting: @@ -17,89 +29,57 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - name: Install nightly toolchain - uses: actions-rs/toolchain@v1.0.6 + uses: actions-rs/toolchain@v1.0.7 with: profile: minimal - toolchain: nightly-2021-12-02 + toolchain: nightly-2021-12-10 override: true components: rustfmt, clippy - - name: Run `cargo clippy` - run: cargo fmt --all --message-format human -- --check - continue-on-error: true + - name: Install Rustup Components + run: rustup component add rust-src llvm-tools-preview + + - name: Run `cargo check` + run: cargo check ${{ env.BUILD_TARGET_PATH }} ${{ env.BUILD_FLAGS }} - name: Run `cargo fmt` - run: cargo clippy --all-targets --all-features -- -D warnings - continue-on-error: true + run: cargo fmt --all --message-format human -- --check + + - name: Run `cargo clippy` for `boot` module + run: cargo clippy --package boot --all-features -- -D warnings + + - name: Run `cargo clippy` for kernel + run: cargo clippy --lib --all-features -- -D warnings unit-and-integration-tests: name: Run all unit- and integration-tests runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 with: fetch-depth: 0 - name: Install nightly toolchain - uses: actions-rs/toolchain@v1.0.6 + uses: actions-rs/toolchain@v1.0.7 with: profile: minimal - toolchain: nightly-2021-12-02 + toolchain: nightly-2021-12-10 override: true - - name: List changed files - uses: jitterbit/get-changed-files@v1 - id: changed-files - with: - format: space-delimited - - # yamllint enable rule:line-length - - name: Work with the changed files - continue-on-error: true - run: | - declare -a MODULES CHANGED_MODULES - - echo 'Enumerating all changed files in the kernel.' - - while read -r FILE - do - MODULE="${FILE#./}" - [[ -n ${MODULE} ]] && MODULES+=("${MODULE}") - done < <(find ./ -maxdepth 1 -type d) - - echo "All found kernel modules: ${MODULES[*]}" - - # shellcheck disable=SC2043 - for FILE in ${{ steps.changed-files.outputs.all }} - do - grep -qv 'modules' <<< "${FILE}" && continue - echo "Changed: ${FILE}" - - for MODULE in "${MODULES[@]}" - do - if grep -q "${MODULE}" <<< "${FILE}" - then - if [[ ! " ${CHANGED_MODULES[*]} " =~ ${MODULE} ]] - then - CHANGED_MODULES+=("${MODULE}") - fi - fi - done - done - - echo "Modules changed by this PR: ${CHANGED_MODULES[*]}" - - for MODULE in "${CHANGED_MODULES[@]}" - do - # cargo check --package "${MODULE}" - # cargo test --package "${MODULE}" - : - done - - # cargo test --package kernel --test '*' + - name: Install Rustup Components + run: rustup component add rust-src llvm-tools-preview + + - name: Install QEMU + run: sudo apt update && sudo apt install qemu-system-x86 + + - name: Print QEMU version + run: qemu-system-x86_64 --version + + - name: Run `cargo test` + run: cargo test --tests ${{ env.BUILD_TARGET_PATH }} ${{ env.BUILD_FLAGS }} diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index c9a7742..a0b3c92 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -4,6 +4,9 @@ name: Linting on: # yamllint disable-line rule:truthy workflow_dispatch: pull_request: + branches: ['*'] + push: + branches: ['*'] defaults: run: @@ -15,7 +18,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master with: @@ -30,7 +33,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 - name: Run YAMLLint uses: ibiqlik/action-yamllint@v3.1.0 with: @@ -44,7 +47,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 with: fetch-depth: 0 diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 771fffb..db9f3d5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -5,15 +5,16 @@ on: # yamllint disable-line rule:truthy workflow_dispatch: pull_request: paths: - - modules/**/Cargo.toml - - modules/**/Cargo.lock + - kernel/**/Cargo.toml + - kernel/**/Cargo.lock + - .github/workflows/security.yml schedule: - cron: 0 0 * * 6 defaults: run: shell: bash - working-directory: modules + working-directory: kernel jobs: cargo-audit: @@ -21,10 +22,10 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v2.4.0 - name: Install nightly toolchain - uses: actions-rs/toolchain@v1.0.6 + uses: actions-rs/toolchain@v1.0.7 with: profile: minimal toolchain: nightly-2021-12-02 diff --git a/.gitignore b/.gitignore index e53d697..648436a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ documentation/site # ---- Rust ----------------------------------- # ----------------------------------------------- -modules/**/target/ +kernel/target/ -modules/**/Cargo.lock -modules/**/*.rs.bk +kernel/**/Cargo.lock +kernel/**/*.rs.bk diff --git a/CHANGELOG.md b/CHANGELOG.md index d88a051..59f58ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,41 @@ ## Major Version 0 +### v0.3 + +#### v0.3.0 + +- major refactoring of kernel source code + - revised kernel code structure + - no individual kernel modules anymore + - sorted our compiler errors + - removed all compiler warnings + - `cargo clippy` now works without errors + - completed code documentation + - adjusted GitHub workflows accordingly + - unit-tests tests are now working +- improved GitHub workflows +- heavily extended and refactored documentation + - adjusted to new kernel structure + - added major sections + +### v0.2 + +#### v0.2.0 + +- added GitHub workflows to enable a powerful CI + - added Dependabot for a better CI experience + - added individual actions for linting and U/I testing + - added stale action + - added (preliminary) documentation deployment action +- heavily revised the documentation +- corrected minor mistakes in miscellaneous files +- added a security policy +- adjusted formatting with `rustfmt` +- heavily adjusted linting + ### v0.1 #### v0.1.0 + +- initial commit diff --git a/Justfile b/Justfile index a14e9bd..0b1cf3a 100644 --- a/Justfile +++ b/Justfile @@ -6,16 +6,23 @@ set shell := [ "bash", "-eu", "-o", "pipefail", "-c" ] set dotenv-load := false -export ROOT_DIRECTORY := justfile_directory() -export TOOLCHAIN := `tr -d '\n' < modules/rust-toolchain` - -BUILD_TOOL := "cargo" DATE := `date +'%Y-%m-%d'` GIT_REVISION_HEAD := `git rev-parse --short HEAD` -KERNEL_VERSION := `grep -m 1 'version*' modules/kernel/Cargo.toml | cut -d '"' -f 2` +KERNEL_VERSION := `grep -m 1 'version*' kernel/Cargo.toml | cut -d '"' -f 2` +export ROOT_DIRECTORY := justfile_directory() +export TOOLCHAIN := `tr -d '\n' < kernel/rust-toolchain` export VERSION := KERNEL_VERSION + ' (' + GIT_REVISION_HEAD + ' ' + DATE + ')' +BUILD_TOOL := 'cargo' +BOOTIMAGE_BUILD_TARGET := `rustc -Vv | grep 'host:' | cut -d ' ' -f 2` +KERNEL_BUILD_FLAGS_1 := ' -Z build-std=core,compiler_builtins,alloc' +KERNEL_BUILD_FLAGS_2 := ' -Z build-std-features=compiler-builtins-mem' +KERNEL_BUILD_FLAGS := KERNEL_BUILD_FLAGS_1 + KERNEL_BUILD_FLAGS_2 + +export KERNEL_BUILD_TARGET := `printf "${TARGET:-x86_64-unknown-none}"` +KERNEL_BUILD_TARGET_PATH := ROOT_DIRECTORY + '/kernel/targets/' + KERNEL_BUILD_TARGET + '.json' + # show this help message help: #! /bin/bash @@ -41,89 +48,93 @@ help: # ---- Build and Test ------------------------- # ----------------------------------------------- -# build all (or a dedicated) package members -build package='""': - #! /bin/bash +# compile the kernel +@build release='': + RELEASE="{{release}}" && \ + just -- _build_kernel "${RELEASE:+--release}" - cd {{ROOT_DIRECTORY}}/modules/ - if [[ -z {{package}} ]] - then - {{BUILD_TOOL}} build - else - {{BUILD_TOOL}} build --package {{package}} - fi +# create a bootable image +@build_image release='': + RELEASE="{{release}}" && \ + just -- _use_bootimage "${RELEASE:+release}" --no-run + +# run the kernel in QEMU +run: _use_bootimage -# build the kernel with optimizations -release: +# compile the kernel +@_build_kernel release='': + cd {{ROOT_DIRECTORY}}/kernel/ && \ + {{BUILD_TOOL}} build {{release}} \ + --target {{KERNEL_BUILD_TARGET_PATH}} \ + {{KERNEL_BUILD_FLAGS}} + +# use the bootloader tool to build or run the kernel +_use_bootimage release='' no_run='': #! /bin/bash - cd {{ROOT_DIRECTORY}}/modules - RUSTFLAGS="-C target-cpu=native" cargo build \ - --package kernel \ - --bin kernel \ - --release + RELEASE="{{release}}" + + just -- _build_kernel ${RELEASE:+--release} || exit ${?} + cd {{ROOT_DIRECTORY}}/kernel/ + + {{BUILD_TOOL}} run \ + --package boot \ + --target {{BOOTIMAGE_BUILD_TARGET}} \ + ${RELEASE:+--release} \ + -- \ + target/{{KERNEL_BUILD_TARGET}}/${RELEASE:-debug}/kernel {{no_run}} -# clean output produced during building and tetsing +# remove the kernel/target/ directory @clean: - cd {{ROOT_DIRECTORY}}/modules/ && {{BUILD_TOOL}} clean + cd {{ROOT_DIRECTORY}}/kernel/ && {{BUILD_TOOL}} clean -# run all tests for all package members -test package='""': +# run tests workspace members +test test='': #! /bin/bash - cd {{ROOT_DIRECTORY}}/modules/ - if [[ {{package}} == 'kernel' ]] - then - cd {{ROOT_DIRECTORY}}/modules/kernel/ - {{BUILD_TOOL}} test --bin kernel - {{BUILD_TOOL}} test --lib - {{BUILD_TOOL}} test --test '*' - elif [[ -n {{package}} ]] + cd {{ROOT_DIRECTORY}}/kernel/ + + # --tests runs all tests, i.e. the kernel library (`lib.rs`) + # effectivly running all unit-tests, the kernel main binary + # (`main.rs`) and all integration tests (under `tests/`) + + if [[ -z "{{test}}" ]] then - cd {{package}} - {{BUILD_TOOL}} check --package {{package}} - {{BUILD_TOOL}} test --package {{package}} + {{BUILD_TOOL}} test --tests \ + --target {{KERNEL_BUILD_TARGET_PATH}} \ + {{KERNEL_BUILD_FLAGS}} else - {{BUILD_TOOL}} check - {{BUILD_TOOL}} test + {{BUILD_TOOL}} test --test {{test}} \ + --target {{KERNEL_BUILD_TARGET_PATH}} \ + {{KERNEL_BUILD_FLAGS}} fi -# create the complete QEMU boot image -@bootimage: - cd {{ROOT_DIRECTORY}}/modules/kernel/ && {{BUILD_TOOL}} bootimage + printf '\nTests passed.\n' # ----------------------------------------------- # ---- Format and Lint ------------------------ # ----------------------------------------------- -# format all package members -format package='""': - #! /bin/bash +# format the Rust code with rustfmt +@format: + cd {{ROOT_DIRECTORY}}/kernel/ \ + && {{BUILD_TOOL}} fmt --message-format human - cd {{ROOT_DIRECTORY}}/modules/ - if [[ -z {{package}} ]] - then - {{BUILD_TOOL}} fmt --message-format human - else - {{BUILD_TOOL}} fmt --message-format human --package {{package}} - fi +alias fmt := format -# lint against rustfmt and Clippy everwhere -check-format package='""': +# lint against rustfmt and Clippy +check: #! /bin/bash - cd {{ROOT_DIRECTORY}}/modules/ - if [[ -z {{package}} ]] - then - {{BUILD_TOOL}} fmt --all --message-format human -- --check - {{BUILD_TOOL}} clippy \ - --all-targets --all-features -- -D warnings - else - {{BUILD_TOOL}} fmt --all --package {{package}} \ - --message-format human -- --check - {{BUILD_TOOL}} clippy --package {{package}} \ - --all-targets --all-features -- -D warnings - fi + cd {{ROOT_DIRECTORY}}/kernel/ + + {{BUILD_TOOL}} check \ + --target {{KERNEL_BUILD_TARGET_PATH}} \ + {{KERNEL_BUILD_FLAGS}} + + {{BUILD_TOOL}} fmt --all --message-format human -- --check + {{BUILD_TOOL}} clippy --lib --all-features -- -D warnings + {{BUILD_TOOL}} clippy --package boot --all-features -- -D warnings # lint against EditorConfig, ShellCheck and YAMLLint @lint: diff --git a/README.md b/README.md index bed7a4e..9e9c0a7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![License][badge::license]][badge::licence::link] [![Documentation][badge::documentation]][badge::documentation::link] -[![CI Linting][badge::ci::linting]][badge::ci::link] +[![CI][badge::ci::kernel-code-tests]][badge::ci::kernel-code-tests::link] [![CI Linting][badge::ci::security]][badge::ci::security::link] [![CI Linting][badge::ci::linting]][badge::ci::linting::link] ## About @@ -17,7 +17,7 @@ _unCORE_ is an [operating system] [kernel] completely written in pure, idiomatic ## Getting Started -The documentation has a dedicated [Getting Started][docs-getting-started] section. You will need to have [Rust]'s basic tools (in the form of `rustc`, `rustup` and `cargo`) installed. To run a pre-defined set of commands (building, linting, formatting, testing, etc.), you may use [Just], a command runner. You can then run `just help` to get an overview of available commands to run. +The documentation has a dedicated [Getting Started][docs-getting-started] section. You will need to have [Rust]'s basic tools (in the form of `rustc`, `rustup` and `cargo`) installed. To run a pre-defined set of commands (building, linting, formatting, testing, etc.), you may use [Just], a command runner. It is **highly recommended** to install [Just] in order to make working with _unCORE_ easier. You can then run `just help` to get an overview of available commands to run. To check whether you have all needed tools, and install them if you do not already have them installed, run @@ -30,40 +30,43 @@ tools INFO Setting Rust toolchain and ... from this repository's root directory. +## Documentation and Licensing + +The documentation is written in Markdown, built with [MkDocs] and can be found under `documentation/`. You may build and serve the documentation locally with a container runtime (like [Docker] or [Podman]) by running `./scripts/documentation.sh`, serving it under . + +This project is licensed under the [GNU General Public License v3], except for those parts (lines of code from libraries used in this project) already licensed under other licenses. Moreover, code taken from [_Phillip Oppermann_'s _BlogOS_ project][blog-os] is not covered by the license of this project as well. + ## Repository Structure -This repository is structured into different modules: +This repository is structured into different subdirectories: -``` TXT +``` BASH / -├── .github/ # GitHub's issue and pull request templates -├── documentation/ # documentation resides here -├── modules/ # kernel modules and Rust code +├── .github/ # GitHub's templates and CI workflows +├── documentation/ # full kernel documentation +├── kernel/ # kernel files including all Rust code └── scripts/ # holds all Bash scripts for administration ``` -A dedicated [Modules][docs-modules] section in the documentation covers the contents of the modules in which [Rust] code resides. - -## Documentation and Licensing - -The documentation is written in Markdown, built with [MkDocs] and can be found under `documentation/`. You may build and serve the documentation locally with a container runtime (like [Docker] or [Podman]) by running `./scripts/documentation.sh`, serving it under . - -This project is licensed under the [GNU General Public License v3], except for those parts (lines of code from libraries used in this project) already licensed under other licenses. Moreover, code taken from [_Phillip Oppermann_'s _BlogOS_ project][blog-os] is not covered by the license of this project as well. - [//]: # (Badges) [badge::license]: https://img.shields.io/github/license/georglauterbach/uncore.svg?label=LICENSE&color=informational&style=for-the-badge -[badge::licence::link]: https://github.com/georglauterbach/uncore/blob/master/LICENSE +[badge::licence::link]: ./LICENSE [badge::documentation]: https://img.shields.io/badge/DOCUMENTATION-MKDOCS-informational?style=for-the-badge -[badge::documentation::link]: https://github.com/georglauterbach/uncore/tree/master/documentation +[badge::documentation::link]: https://georglauterbach.github.io/uncore/ -[badge::ci::link]: https://github.com/docker-mailserver/docker-mailserver/actions [badge::ci::linting]: https://img.shields.io/github/workflow/status/georglauterbach/uncore/Linting?label=CI%20-%20Linting&logo=github&logoColor=white&style=for-the-badge +[badge::ci::linting::link]: https://github.com/georglauterbach/uncore/actions/workflows/linting.yml + +[badge::ci::security]: https://img.shields.io/github/workflow/status/georglauterbach/uncore/Security%20Audit?label=CI%20-%20Security%20Audit&logo=github&logoColor=white&style=for-the-badge +[badge::ci::security::link]: https://github.com/georglauterbach/uncore/actions/workflows/security.yml + +[badge::ci::kernel-code-tests]: https://img.shields.io/github/workflow/status/georglauterbach/uncore/Kernel%20Code%20Tests?label=CI%20-%20Kernel%20Code%20Tests&logo=github&logoColor=white&style=for-the-badge +[badge::ci::kernel-code-tests::link]: https://github.com/georglauterbach/uncore/actions/workflows/kernel-tests.yml [//]: # (Links) [docs-getting-started]: ./documentation/content/index.md#getting-started -[docs-modules]: ./documentation/content/modules/modules.md [operating system]: https://en.wikipedia.org/wiki/Operating_system [kernel]: https://en.wikipedia.org/wiki/Kernel_(operating_system) diff --git a/documentation/config.yml b/documentation/config.yml index a427f54..d0dbb83 100644 --- a/documentation/config.yml +++ b/documentation/config.yml @@ -16,15 +16,18 @@ docs_dir: content/ nav: - Home: index.md - Development: development.md - - Modules: - - Overview: modules/modules.md + - Building: building.md + - Kernel Structure: + - Overview: kernel_structure.md - System Call Interface: modules/syscalls.md - Kernel: modules/kernel.md + - Helper: modules/helper.md - Communication: modules/communication.md - Memory: modules/memory.md - Processes: modules/processes.md - Scheduling: modules/scheduling.md - Hardware Abstraction Layer: modules/hardware.md + - Testing: testing.md theme: name: material @@ -74,6 +77,7 @@ extra: # however we have a basic setup that only matches `version` to a subdirectory. version: provider: mike + default: edge markdown_extensions: - abbr diff --git a/documentation/content/building.md b/documentation/content/building.md new file mode 100644 index 0000000..048cb30 --- /dev/null +++ b/documentation/content/building.md @@ -0,0 +1,101 @@ +# Building _unCORE_ + +The `kernel/` directory contains all kernel code (and therefore the complete [Rust] code of _unCORE_). It is, at the same time, package and a Cargo workspace. That means, the `kernel/` directory contains a binary -- the kernel with all its source code located under `kernel/src/` -- and other workspace member, such as `boot`, located under `kernel/boot`. The final binary is obviously built from the source code located at `kernel/src/`, but `boot` handles the creation of an actually bootable image. + +We **highly recommend using [Just]** for working with _unCORE_. The following steps will just (no pun intended) explain what [Just] does in the background. With [Just] installed, you do not need to run all these long and tedious and error-prone commands yourself. + +!!! tip "Getting the Bigger Picture" + This operating system kernel bases on and is heavily inspired by _Phillip Opperman_'s _BlogOS_. You can read all about it in [his blog](https://os.phil-opp.com/). + +## Compiling the Kernel + +The kernel is compiled against a special target. This target is located under `kernel/x64_64-uncore.json`. This target does not provide a standard library, as it is a custom target. + +??? danger "`.cargo/config.toml` And Its Fallacies" + Note that we do not actually want to use a `kernel/.cargo/config.toml` file. Using this file can mess with the defaults for build / run targets and this may lead to very unpleasant outputs. We rather write the target for each compilation explicitly when writing the command -- or we let [Just] do this for us. **But** we currently cannot circumvent using because of the way tests are executed. When `cargo test ...` runs tests, we need to tell `cargo` every time which runner to use. We (currently) cannot do this explicitly on the command line. + +First of all, if you're using [Just] (which is recommended, remember), make yourself familiar with all recipes by running `#!bash just help`. The kernel itself is compiled by running + +``` CONSOLE +$ pwd +/uncore/kernel +$ cargo build --target x86_64-uncore.json -Z build-std=core,compiler_builtins -Z build-std-features=compiler-builtins-mem + Compiling kernel v0.1.0 (/uncore/kernel) + Building [=======================> ] 20/22: kernel + ... +$ # (1) +``` + +1. See how this is a very long and tedious command? Just let [Just] do this for you. + +We specify the target and on top of that, which built-in function (that is, into the compiler `rustc`) we need. That's all. This will create a debug binary of our kernel. In case you want to build a release version, add `--release` after `cargo build`. + +The equivalent for this step with [Just] is + +``` BASH +just build +``` + +## Creating a Bootable Image + +### The `boot` Workspace Member + +The `kernel/boot/` workspace member is responsible for creating a bootable image from out kernel binary (which itself is not bootable). It does this by creating a UEFI bootloader and then linking it to our binary. _unCORE_ will, on purpose, not look into the details of how this works. If you are curious, have a look at [this blog post by _Phillip Opperman_](https://os.phil-opp.com/minimal-rust-kernel/#creating-a-bootimage). + +### Correctly Working With the `boot` Workspace Member + +This is where it gets just a little bit more complicated. The `boot` workspace member is not a `#!rust #![no_std]` crate, and does consequently not use the same target as the kernel - it does not even use nightly. Running it correctly, you will need to specify the location of your kernel binary: + +``` CONSOLE +$ pwd +/uncore/kernel +$ cargo run --package boot \ + --target \ + -- \ + target/x86_64-uncore//kernel [--no-run] + +Creating disk image... [ok] +Created disk image at '/kernel/target/x86_64-uncore/debug/boot-bios-kernel.img' +``` + +Here, [Just] will try to find your default target and use it. On Linux, this is most likely `x86_64-unknown-linux-gnu`. After this process has finished, we have an image located at `kernel/target/x86_64-uncore/debug/boot-bios-kernel.img`. + +The equivalent for this step with [Just] is + +``` BASH +just build_image +``` + +## Running in QEMU + +We can now run the image created in the step above: + +``` CONSOLE +$ pwd +/uncore/kernel +$ qemu-system-x86_64 \ + --no-reboot -s \ + -drive format=raw,file=/kernel/target/x86_64-uncore/debug/boot-uefi-kernel.img +``` + +Note how this requires you to have `qemu-system-x86_64` installed. Executing the command above will open a new window. We'd much rather like to stay on our beloved command line. We can add more options to do so: + +``` CONSOLE +$ pwd +/uncore/kernel +$ qemu-system-x86_64 \ + --no-reboot -s \ + -serial stdio -display none \ + -drive format=raw,file=/kernel/target/x86_64-uncore/debug/boot-uefi-kernel.img +``` + +The equivalent for this step with [Just] is + +``` BASH +just run +``` + +[//]: # (Links) + +[Rust]: https://www.rust-lang.org/ +[Just]: https://github.com/casey/just diff --git a/documentation/content/development.md b/documentation/content/development.md index d49f3b8..5d9e38f 100644 --- a/documentation/content/development.md +++ b/documentation/content/development.md @@ -13,7 +13,7 @@ There are various tests in place to check your code, not the least of which are ## Git Flow -The `master` branch contains the current stable code base. The `development` branch contains the latest changes, which may not be as stable as `master`. For every new version, there is a `version/X.Y.Z` branch, that is first merged into `development`, and then into `master`. The `X.Y.Z` follows the semantic versioning guidelines strictly! New features are being added through `feature/name-of-the-feature` branches. Hotfixes can be merged either into a `version/` branch or into `development`, but not into `master`. The following illustration shows the order of merging a feature. +Please sign your commits with `GPG` / `PGP` so GitHub can verify them. The `master` branch contains the current stable code base. The `development` branch contains the latest changes, which may not be as stable as `master`. For every new version, there is a `version/X.Y.Z` branch, that is first merged into `development`, and then into `master`. The `X.Y.Z` follows the semantic versioning guidelines strictly! New features are being added through `feature/name-of-the-feature` branches. Hotfixes can be merged either into a `version/` branch or into `development`, but not into `master`. The following illustration shows the order of merging a feature. ``` TXT feature/name-of-the-feature ─── > version/X.Y.Z ─── > development ─── > master @@ -21,23 +21,36 @@ feature/name-of-the-feature ─── > version/X.Y.Z ─── > develo hotfix > ────────┘ > ────────┘ ``` -Please sign your commits with `GPG` / `PGP` so GitHub can verify them. +One can then rebase onto `development` if something has been merged into development (e.g. a new version). ## Coding Style -When writing code, adhere to the style provided in the miscellaneous configuration files and to what is already written in all the files, even if this is not your preferred style. When altering files, look how it has been written and stay true to these design decisions. Make sure your IDE uses the provided `.editorconfig`. +### General -Rust if formatted using `rustfmt`, which is installed with [Rust] itself. You can format your code using `just format` or `cargo fmt` in the repository modules containing [Rust] code. The style definition is found under `modules/.rustfmt.toml`. Make sure to adjust your style to the already present style. +When writing code, adhere to the style provided in the miscellaneous configuration files and to what is already written in all the files, even if this is not your preferred style. When altering files, look how it has been written and stay true to these design decisions. Make sure your IDE uses the provided `.editorconfig`. When writing YAML, code is formatted with [Prettier]. When writing Bash scripts, make sure, especially with Bash, that you stick to the already present style! -When writing YAML, code is formatted with [Prettier]. When writing Bash scripts, make sure, especially with Bash, that you stick to the already present style! +**Avoid** the following under all circumstances: + +1. C/C++ idiosyncrasies, such as stacked `#!c typedef`s or preprocessor chaos with `#!c define`s +2. Using abbreviations everywhere, which makes code unreadable for others; we are trying to write _concise_ code, not necessarily short code - concise and short are not always the same +3. Using `#!rust type` to define type aliases for very simple types; only use this when absolutely appropriate, otherwise, write out the whole type + +### Rust Conventions + +Rust if formatted using `rustfmt`, which is installed with [Rust] itself. You can format your code using `just format` or `cargo fmt` in the repository modules containing [Rust] code. The style definition is found under `kernel/.rustfmt.toml`. Make sure to adjust your style to the already present style. The [Rust naming convention] is strictly adhered to. + +Crate-level / global `lib.rs` (or in case of the kernel, also `main.rs`) are formatted in a special way. We start by declaring crate-level attributes and crate-level documentation, then modules and exports and last but not least, global functions. You may want to have a look at the `helper/` module's `lib.rs` for a concise example. + +We want to ensure, at all cost, that code in this project becomes as unreadable as some Linux kernel code. This has nothing to do with formatting taste, but with problems inherent to C and how programmers are used to writing C. ## Miscellaneous -You may run any IDE you like, of course. We recommend [Visual Studio Code] or [NeoVIM]. +You may run any IDE you like, of course. We recommend [Visual Studio Code] or [NeoVIM]. We do not make any assumptions about the style of working that you prefer most - use what suits you best. [//]: # (Links) [Rust]: https://www.rust-lang.org/ [Prettier]: https://prettier.io/ +[Rust naming convention]: https://doc.rust-lang.org/1.0.0/style/style/naming/README.html [Visual Studio Code]: https://code.visualstudio.com/ [NeoVIM]: https://neovim.io/ diff --git a/documentation/content/index.md b/documentation/content/index.md index d4a51a3..79bea1f 100644 --- a/documentation/content/index.md +++ b/documentation/content/index.md @@ -2,7 +2,7 @@ ## Introduction -Welcome to the official _unCORE_ operating system kernel documentation. _unCORE_ is an [operating system] [kernel] completely written in pure, idiomatic [Rust]. _unCORE_ makes use of the [Rust] ecosystem, avoiding unnecessary complexity while being stable and performant. If you're new to this project, we highly recommend reading the [Getting Started][docs-getting-started] section. Everything you need to know about development guidelines can be found under [Development][docs-development]. The [Modules][docs-modules] section contains all the information about the kernel modules in _unCORE_. This documentation is only one half of the whole documentation that is available. The other part is the code documentation which can be built with `#!bash cargo doc --open`. +Welcome to the official _unCORE_ operating system kernel documentation. _unCORE_ is an [operating system] [kernel] completely written in pure, idiomatic [Rust]. _unCORE_ makes use of the [Rust] ecosystem, avoiding unnecessary complexity while being stable and performant. If you're new to this project, we highly recommend reading the [Getting Started][docs-getting-started] section. Everything you need to know about development guidelines can be found under [Development][docs-development]. The [Building][docs-building] site contains information on how to build and run (with QEMU) the kernel. The [Structure][docs-structure] section contains all the information about the kernel's internal structure and composition. This documentation is only one half of the whole documentation that is available. The other part is the code documentation which can be built with `#!bash cargo doc --open`. !!! check "Code of Conduct and Contributing Guidelines" By working on this projects and with other participants, you agree to the **code of conduct** and the **contributing guidelines** set by this project. @@ -31,6 +31,10 @@ The script will also check whether you have [Docker] or [Podman] installed. Thes If you're on Windows or macOS, you will need to install these tools yourself. +## Vision + +_unCORE_ is not trying to invent the wheel anew. As of now, _unCORE_ is an educational project that does not run real software. We want to change this in the future. _unCORE_ shall make use of well-known and common concepts used in _UNIX_ / _GNU-Linux_. But, we acknowledge that modern software development is heavily benefitting of CI pipelines, GIT platforms (such as _GitHub_) and collaboration in the form of issues, pull requests, projects and other actions. While we know that mailing lists work, we belief that modern software development can do better. One aspect we heavily focus on is code quality (in the same way that _Linus Torvalds_ has ensured the code quality in the _Linux_ kernel). We are using automated CI to achieve this goal too. You will, when you start out, notice that CI is very restrictive. This may get on your nerves, but ensures all code in this project is as clean as possible. The motto here is: **We either do it right or not at all**. Please also read the [conventions set in this project](development.md#miscellaneous) to ensure you're up-to-date when it comes to writing real code. + ## Goals _unCORE_ makes use of the [Rust] ecosystem, avoiding unnecessary complexity while being stable and fast. The main goals are @@ -44,12 +48,10 @@ _unCORE_ has set itself some more goals, of course: 2. Correctness - _unCORE_ has high test standards, see [the documentation and testing advise](./development.md#code-documentation-testing). 3. Simplicity - We want to make use of [Rust]'s high level of abstraction to write clean and concise code. -## Vision - -_unCORE_ is not trying to invent the wheel anew. As of now, _unCORE_ is an educational project that does not run real software. We want to change this in the future. _unCORE_ shall make use of well-known and common concepts used in UNIX / GNU-Linux. - ## Architecture +_unCORE_ is neither a microkernel -- in the sense that there is no policy in the kernel -- not a complete monolithic kernel. This has several reasons: Firstly, we think that some of the ideas of microkernels are worthwhile using, such as concepts like POLA (Principle of Least Authority). On the other hand, there is currently not enough development power to make _unCORE_ a monolithic kernel. + _unCORE_ aspires to be cleanly separated into **loosely coupled modules with high cohesion**. Loose coupling ensures that it is possible to change modules themselves or for one another without major code changes in other modules. High cohesion ensures that a single module does not integrate functionality that does fall under the area of responsibility of another modules. The architectural overview in a "lateral view" is depicted in the following illustration where `─` depicts the component separation and `┃` information and control flow: @@ -98,7 +100,8 @@ This work was and is heavily inspired by [_Phillip Oppermann_'s _BlogOS_][blog-o [docs-getting-started]: #getting-started [docs-development]: ./development.md -[docs-modules]: ./modules/modules.md +[docs-building]: ./building.md +[docs-structure]: ./kernel_structure.md [operating system]: https://en.wikipedia.org/wiki/Operating_system [kernel]: https://en.wikipedia.org/wiki/Kernel_(operating_system) diff --git a/documentation/content/kernel_structure.md b/documentation/content/kernel_structure.md new file mode 100644 index 0000000..c506138 --- /dev/null +++ b/documentation/content/kernel_structure.md @@ -0,0 +1,30 @@ +# The Internal Kernel Structure + +The kernel is composed of different, so-called "modules". These big building blocks are all found in the source code under `kernel/src/library/`. Here are all modules nicely listed: + +``` BASH +kernel/src/library/ +├── syscalls +├── helpers +├── communication +├── memory +├── processes +├── scheduling +└── hardware +``` + +Some of these modules are so-called kernel core components. This is just a fancy way of saying that they are very important to the kernel's main functionality, and without them, the kernel would not work. Core components include (non-exhaustivly) `helper`, `processes`, `scheduling`, ... There are dedicated pages for every module: Each module is listed to the right of this text in a navigation menu. The interplay of all modules is somewhat obvious, and all modules have a rather concise name so everyone knows what their purpose is. + +[//]: # (Links) + +[docs-syscalls-module]: ./modules/syscalls.md +[docs-kernel-module]: ./modules/kernel.md +[docs-communication-module]: ./modules/communication.md +[docs-memory-module]: ./modules/memory.md +[docs-processes-module]: ./modules/processes.md +[docs-scheduling-module]: ./modules/scheduling.md +[docs-hardware-module]: ./modules/hardware.md +[docs-architecture]: ./index.md#architecture + +[Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction +[Rust]: https://www.rust-lang.org/ diff --git a/documentation/content/modules/hardware.md b/documentation/content/modules/hardware.md index e69de29..53740b0 100644 --- a/documentation/content/modules/hardware.md +++ b/documentation/content/modules/hardware.md @@ -0,0 +1,13 @@ +# Hardware + +The `hardware` module provides the mechanisms to isolate the actual kernel code from the underlying hardware. It provides a unified interface for the kernel to work with, see [the kernel architecture][docs-architecture]. The hardware module uses **conditional compilation** depending on the compilation target. When the kernel starts, it will print the build target triple: + +``` LOG +INFO | Target triple reads 'x86_64-unknown-none' +``` + +which shows us that the target architecture in this case is `x86_64`. + +[//]: # (Links) + +[docs-architecture]: ../index.md#architecture diff --git a/documentation/content/modules/helper.md b/documentation/content/modules/helper.md new file mode 100644 index 0000000..0c8e0da --- /dev/null +++ b/documentation/content/modules/helper.md @@ -0,0 +1,3 @@ +# Generic Helper + +The `helpers` module provides very generic function all other workspace members use, for example test runners, `#!rust panic` functionality and a function that does not return. diff --git a/documentation/content/modules/kernel.md b/documentation/content/modules/kernel.md index e69de29..7d3a8d4 100644 --- a/documentation/content/modules/kernel.md +++ b/documentation/content/modules/kernel.md @@ -0,0 +1,5 @@ +# The Final Binary + +## Introduction + +The `kernel/` workspace member compiles into the final kernel binary that is directly run by QEMU / bare-metal hardware. diff --git a/documentation/content/modules/memory.md b/documentation/content/modules/memory.md index e69de29..358f3ee 100644 --- a/documentation/content/modules/memory.md +++ b/documentation/content/modules/memory.md @@ -0,0 +1,3 @@ +# Memory + +The `memory` module provides main memory management functionality. It is a kernel core component. diff --git a/documentation/content/modules/modules.md b/documentation/content/modules/modules.md deleted file mode 100644 index ea9b4f8..0000000 --- a/documentation/content/modules/modules.md +++ /dev/null @@ -1,38 +0,0 @@ -# Modules - -All kernel modules reside in the directory `modules/`. - -``` BASH -modules/ -├── syscalls # (1) -├── kernel # (2) -├── communication # (3) -├── memory # (4) -├── processes # (5) -├── scheduling # (6) -└── hardware # (7) -``` - -1. [The `syscalls` module][docs-syscalls-module] provides a unified interface for user space to interoperate with the kernel, see [the kernel architecture][docs-architecture]. -2. [The `kernel` module][docs-kernel-module] provides the actual, complete kernel. It can be compiled as a binary to run as an operating system kernel. -3. [The `communication` module][docs-communication-module] provides inter-process communication (IPC). It is a kernel core component. -4. [The `memory` module][docs-memory-module] provides main memory management functionality. It is a kernel core component. -5. [The `processes` module][docs-processes-module] provides the abstractions for processes and threads. It is a kernel core component. -6. [The `scheduling` module][docs-scheduling-module] provides scheduling functionality. It is a kernel core component. -7. [The `hardware` module][docs-hardware-module] provides the mechanisms to isolate the actual kernel code from the underlying hardware. It provides a unified interface for the kernel to work with, see [the kernel architecture][docs-architecture]. - -This directory contains all kernel code (and therefore the complete [Rust] code of _unCORE_). The `modules/` directory is a Cargo workspace that contains all of the above mentioned modules as workspace members. The final kernel binary is built from the source in `modules/kernel/`. - -[//]: # (Links) - -[docs-syscalls-module]: ./syscalls.md -[docs-kernel-module]: ./kernel.md -[docs-communication-module]: ./communication.md -[docs-memory-module]: ./memory.md -[docs-processes-module]: ./processes.md -[docs-scheduling-module]: ./scheduling.md -[docs-hardware-module]: ./hardware.md -[docs-architecture]: ../index.md#architecture - -[Hardware Abstraction Layer]: https://en.wikipedia.org/wiki/Hardware_abstraction -[Rust]: https://www.rust-lang.org/ diff --git a/documentation/content/modules/processes.md b/documentation/content/modules/processes.md index e69de29..3cfb516 100644 --- a/documentation/content/modules/processes.md +++ b/documentation/content/modules/processes.md @@ -0,0 +1,3 @@ +# Processes + +The `processes` module provides the abstractions for processes and threads. It is a kernel core component. diff --git a/documentation/content/modules/scheduling.md b/documentation/content/modules/scheduling.md index e69de29..98b18e1 100644 --- a/documentation/content/modules/scheduling.md +++ b/documentation/content/modules/scheduling.md @@ -0,0 +1,3 @@ +# Scheduling + +The `scheduling` module provides scheduling functionality. It is a kernel core component. diff --git a/documentation/content/modules/syscalls.md b/documentation/content/modules/syscalls.md index e69de29..2d5d258 100644 --- a/documentation/content/modules/syscalls.md +++ b/documentation/content/modules/syscalls.md @@ -0,0 +1,7 @@ +# System Calls + +The `syscalls` module provides a unified interface for user space to interoperate with the kernel, see [the kernel architecture][docs-architecture]. + +[//]: # (Links) + +[docs-architecture]: ../index.md#architecture diff --git a/documentation/content/testing.md b/documentation/content/testing.md new file mode 100644 index 0000000..352912c --- /dev/null +++ b/documentation/content/testing.md @@ -0,0 +1,33 @@ +# Testing the Kernel + +_unCORE_ provides unit- and integration-tests. All unit-test are located "inside" the kernel itself, all integration tests are found under `kernel/tests/`. Note that linting the kernel is an important part of code quality analysis. + +??? hint "Using a Pre-Commit Hook" + You may run a pre-commit hook to verify your code before committing. If you are using [Just], the hook can be created like this: + + ``` BASH + cat >.git/hooks/pre-commit << "EOM" + #! /bin/bash + + set -euEo pipefail + just fmt check test + + EOM + ``` + + If you are not using [Just], you may copy the targets from the `Justfile` manually into the pre-commit hook script. + +!!! missing "Missing Documentation" + **This documentation is missing major parts**. You could contribute here yourself. + +## Unit Tests + +## Integration Tests + +## Running Tests + +We are running tests with `cargo test ...`. This requires us to use a `.cargo/config.toml` file, as we need to specify the test runner (which is the `boot` workspace member) explicitly. + +[//]: # (Links) + +[Just]: https://github.com/casey/just diff --git a/kernel/.cargo/config.toml b/kernel/.cargo/config.toml new file mode 100644 index 0000000..73c09c1 --- /dev/null +++ b/kernel/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.'cfg(target_os = "none")'] +runner = "cargo run --package boot --" diff --git a/modules/.rustfmt.toml b/kernel/.rustfmt.toml similarity index 82% rename from modules/.rustfmt.toml rename to kernel/.rustfmt.toml index 68a9db8..04af27a 100644 --- a/modules/.rustfmt.toml +++ b/kernel/.rustfmt.toml @@ -5,15 +5,16 @@ unstable_features = true edition = "2021" brace_style = "AlwaysNextLine" +control_brace_style = "AlwaysSameLine" imports_layout = "Vertical" indent_style = "Block" imports_granularity = "Crate" -newline_style = "Native" +newline_style = "Auto" hex_literal_case = "Upper" binop_separator = "Front" - hard_tabs = true +format_code_in_doc_comments = false combine_control_expr = false reorder_imports = false reorder_modules = false @@ -22,7 +23,6 @@ condense_wildcard_suffixes = true error_on_line_overflow = true error_on_unformatted = true fn_single_line = true -format_code_in_doc_comments = true format_macro_bodies = true format_macro_matchers = true format_strings = true @@ -33,10 +33,11 @@ reorder_impl_items = true use_field_init_shorthand = true wrap_comments = true -inline_attribute_width = 0 +blank_lines_lower_bound = 0 blank_lines_upper_bound = 1 tab_spaces = 8 enum_discrim_align_threshold = 45 struct_field_align_threshold = 45 -comment_width = 99 -max_width = 99 +inline_attribute_width = 70 +comment_width = 70 +max_width = 100 diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..8b65b80 --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "kernel" +edition = "2021" +version = "0.3.0" + +authors = ["The unCORE Kernel Community"] + +# Rust toolchain releases can be found under +# https://rust-lang.github.io/rustup-components-history/ + +description = "An Operating System Microkernel written in pure, idiomatic Rust" +documentation = "../documentation/" +license = "GPL-3.0" +readme = "../README.md" + +homepage = "https://uncore-kernel.org" +repository = "https://github.com/georglauterbach/uncore" + +keywords = [ + "operating-system", + "os", + "kernel", + "no-std", +] + +categories = [ + "no-std", + "config", +] + +publish = false + +# Cargo's auto-detection of library files is turned on. Therefore, +# `src/lib.rs` is automatically detected by Cargo as a +# (freestanding) library. We need to define some code segments +# twice, here as well as an in `src/main.rs` as this file is tested +# by Cargo separately. This file can then be used in integration tests +# as well. +autobenches = true +autobins = true +autoexamples = true +autotests = true + +[profile.release] +codegen-units = 1 +incremental = true +lto = true + +# DO NOT USE this or you will not be able to run tests +# panic = "abort" + +[profile.dev] +lto = false + +# DO NOT USE this or you will not be able to run tests +# panic = "abort" + +[dependencies] +bootloader = "0.10.6" +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +pic8259 = "0.10.1" +pc-keyboard = "0.5.1" +volatile = "0.4.4" +spin = { version = "0.9.2", features = ["lazy"] } +x86_64 = "0.14.6" +uart_16550 = "0.2.14" + +# [package.metadata.bootimage] +# test-success-exit-code = 33 # (0x10 << 1) | 1 +# test-timeout = 100 # in seconds +# test-args = [ +# "-device", +# "isa-debug-exit,iobase=0xf4,iosize=0x04", +# "-serial", +# "stdio", +# "-display", +# "none", +# ] + +[workspace] + +members = [ + "boot", +] diff --git a/modules/hardware/Cargo.toml b/kernel/boot/Cargo.toml similarity index 55% rename from modules/hardware/Cargo.toml rename to kernel/boot/Cargo.toml index 5e677b8..caa0713 100644 --- a/modules/hardware/Cargo.toml +++ b/kernel/boot/Cargo.toml @@ -1,37 +1,34 @@ [package] +name = "boot" +version = "0.2.0" edition = "2021" -name = "hardware" -version = "0.1.0" - workspace = "../" -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel Hardware Abstraction Layer" +authors = ["The unCORE Kernel Community"] +description = "unCORE Bootimage Creation" documentation = "../../documentation/" - +license = "GPL-3.0" readme = "../../README.md" homepage = "https://uncore-kernel.org" repository = "https://github.com/georglauterbach/uncore" -license = "GPL-3.0" - keywords = [ "operating-system", - "no-std", - "hardware", - "abstraction", - "hal", + "os", + "kernel", + "bootimage", + "creation", ] categories = [ "no-std", - "config", + "config" ] publish = false [dependencies] +bootloader-locator = "0.0.4" +locate-cargo-manifest = "0.2.0" +runner-utils = "0.0.2" diff --git a/kernel/boot/src/main.rs b/kernel/boot/src/main.rs new file mode 100644 index 0000000..8733434 --- /dev/null +++ b/kernel/boot/src/main.rs @@ -0,0 +1,305 @@ +// ? GLOBAL CRATE ATTRIBUTES AND DOCUMENTATION +// ? --------------------------------------------------------------------- + +// Clippy lint target one. Enables all lints that are on by +// default (correctness, suspicious, style, complexity, perf) . +#![deny(clippy::all)] +// Clippy lint target two. Enables lints which are rather strict +// or have occasional false positives. +#![deny(clippy::nursery)] +// Clippy lint target three. Enables new lints that are still +// under development +#![deny(clippy::pedantic)] +// Clippy lint target four. Enable lints for the cargo manifest +// file, a.k.a. Cargo.toml. +#![deny(clippy::cargo)] +// Lint target for code documentation. This lint enforces code +// documentation on every code item. +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +// Lint target for code documentation. When running `rustdoc`, +// show an error when using broken links. +#![deny(rustdoc::broken_intra_doc_links)] + +//! # The `unCORE` Bootimage Creation +//! +//! This small application builds the bootimage for `unCORE`, and if +//! demanded, runs it with QEMU too. + +// ? MODULES and GLOBAL / CRATE-LEVEL FUNCTIONS +// ? --------------------------------------------------------------------- + +use std::{ + path, + process, +}; + +/// # Entrypoint +/// +/// Parses arguments, runs the bootimage creation subroutine and runs +/// QEMU if demanded. +fn main() +{ + println!("\nINFO | Starting to build the unCORE kernel image. This may take some time."); + + // skip executable name + let mut arguments = std::env::args().skip(1); + + let kernel_path = if let Some(path) = arguments.next() { + path::PathBuf::from(path) + } else { + eprintln!("[ERROR] No path to the kernel binary provided."); + std::process::exit(1); + }; + + let kernel_path = if let Ok(path) = kernel_path.canonicalize() { + path + } else { + eprintln!( + "[ERROR] Path to kernel ('{}') seems to be wrong or file does not exist.", + kernel_path.display() + ); + std::process::exit(1); + }; + + let mut no_run = false; + + for argument in arguments { + match argument.as_str() { + "--no-run" => no_run = true, + "--do-no-more" => break, + _ => { + eprintln!("[ERROR] Argument '{}' is unknown.", argument); + std::process::exit(1); + }, + } + } + + // create the bootable image + let bios_image = create_disk_images(&kernel_path); + + if runner_utils::binary_kind(&kernel_path).is_test() { + run_tests(&bios_image); + } else if no_run { + println!(); + } else { + run_in_qemu(&bios_image); + } +} + +/// ### Create the Bootable Image +/// +/// Actually runs the bootimage creation process. Returns the path to +/// the bootable image. +#[must_use] +pub fn create_disk_images(kernel_binary_path: &path::Path) -> path::PathBuf +{ + print!("INFO | Creating disk image... "); + + let bootloader_manifest_path = + if let Ok(bootloader_path) = bootloader_locator::locate_bootloader("bootloader") { + bootloader_path + } else { + print_abort_message_and_exit("Could not locate bootloader."); + }; + + let kernel_manifest_path = + if let Ok(cargo_manifest) = locate_cargo_manifest::locate_manifest() { + cargo_manifest + } else { + print_abort_message_and_exit("Could not locate Cargo manifest."); + }; + + let mut build_command = process::Command::new(env!("CARGO")); + + let bootloader_manifest_path_parent = if let Some(path) = bootloader_manifest_path.parent() + { + path + } else { + print_abort_message_and_exit("Could not located Cargo.toml parent directory."); + }; + + let kernel_manifest_path_parent = if let Some(path) = kernel_manifest_path.parent() { + path.join("target") + } else { + print_abort_message_and_exit( + "Could not get the parent directory of the kernel manifest.", + ); + }; + + let kernel_binary_path_parent = if let Some(path) = kernel_binary_path.parent() { + path + } else { + print_abort_message_and_exit( + "Could not get the parent directory of the kernel binary.", + ); + }; + + build_command.current_dir(bootloader_manifest_path_parent); + build_command + .arg("builder") + .arg("--kernel-manifest") + .arg(&kernel_manifest_path) + .arg("--firmware") + .arg("bios") + .arg("--kernel-binary") + .arg(&kernel_binary_path) + .arg("--target-dir") + .arg(kernel_manifest_path_parent) + .arg("--out-dir") + .arg(kernel_binary_path_parent) + .arg("--quiet"); + + let exit_status = if let Ok(status) = build_command.status() { + status + } else { + print_abort_message_and_exit("Could not run the image builder."); + }; + + if !exit_status.success() { + print_abort_message_and_exit(format!( + "Running the image creation process resulted in non-zero exit status '{}'", + exit_status + )); + } + + let kernel_binary_name = if let Some(name) = kernel_binary_path.file_name() { + if let Some(name_as_string) = name.to_str() { + name_as_string + } else { + print_abort_message_and_exit( + "Could not parse the kernel binary path into a string.", + ); + } + } else { + print_abort_message_and_exit("Could not get the kernel binary's file name"); + }; + + let disk_image = if let Some(path) = kernel_binary_path.parent() { + path.join(format!("boot-bios-{}.img", kernel_binary_name)) + } else { + print_abort_message_and_exit( + "Could not locate the kernel binary's parent directory", + ); + }; + + if !disk_image.exists() { + print_abort_message_and_exit(format!( + "Disk image does not exist under '{}'.", + disk_image.display() + )); + } + + let mut disk_image_directory_path = String::new(); + for (counter, object) in disk_image.iter().rev().enumerate() { + if counter >= 5 { + break; + } + + let object = object.to_str().map_or("", |object| object); + disk_image_directory_path = format!("{}/{}", object, disk_image_directory_path); + } + + println!( + "[ok]\nINFO | Created disk image at {}", + disk_image_directory_path.trim_end_matches('/') + ); + + disk_image +} + +/// ### Run the Kernel in QEMU +/// +/// This function runs the kernel in QEMU with all parameters set +/// correctly. +fn run_in_qemu(bios_image: &path::Path) +{ + /// arguments given to QEMU when running the kernel + const QEMU_ARGUMENTS: &[&str] = + &["--no-reboot", "-s", "-serial", "stdio", "-display", "none"]; + + println!("INFO | Running the kernel in QEMU now.\n"); + + let mut run_command = process::Command::new("qemu-system-x86_64"); + run_command + .arg("-drive") + .arg(format!("format=raw,file={}", bios_image.display())) + .args(QEMU_ARGUMENTS); + + let exit_status = run_command.status().unwrap(); + if !exit_status.success() { + eprintln!( + "\nERROR | Running the kernel resulted in non-zero exit status ({})", + exit_status + ); + std::process::exit(1); + } +} + +/// ### Running Kernel Tests With QEMU +/// +/// This function runs the kernel tests inside QEMU. +fn run_tests(bios_image: &path::Path) +{ + use std::time::Duration; + + /// test timeout duration in seconds + const TIMEOUT: u64 = 10; + /// arguments given to QEMU when running the kernel + const QEMU_ARGUMENTS: &[&str] = &[ + "-device", + "isa-debug-exit,iobase=0xf4,iosize=0x04", + "--no-reboot", + "-s", + "-serial", + "stdio", + "-display", + "none", + ]; + + println!("INFO | Testing the kernel in QEMU now.\n"); + + let mut run_command = process::Command::new("qemu-system-x86_64"); + run_command + .arg("-drive") + .arg(format!("format=raw,file={}", bios_image.display())) + .args(QEMU_ARGUMENTS); + + if let Ok(exit_code) = + runner_utils::run_with_timeout(&mut run_command, Duration::from_secs(TIMEOUT)) + { + match exit_code.code() { + // we specifically configured QEMU to + // exit with exit code 33 on success + Some(33) => {}, + Some(other_exit_code) => { + eprintln!( + "ERROR | Tests failed. Exit code was {}.", + other_exit_code + ); + + std::process::exit(other_exit_code) + }, + None => { + eprintln!("ERROR | Tests failed. Exit code unknown."); + std::process::exit(-42) + }, + } + } else { + eprintln!("Could not run tests with QEMU in the first place."); + std::process::exit(-42) + }; +} + +/// ### Print Abort Message and Exot +/// +/// If there was an error, print the given message that describes the +/// error and exit the whole process. +fn print_abort_message_and_exit(message: F) -> ! +where + F: std::fmt::Display, +{ + println!("[not ok]"); + eprintln!("ERROR | {}", message); + std::process::exit(1) +} diff --git a/kernel/rust-toolchain b/kernel/rust-toolchain new file mode 100644 index 0000000..77ffa96 --- /dev/null +++ b/kernel/rust-toolchain @@ -0,0 +1 @@ +nightly-2021-12-10 diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs new file mode 100644 index 0000000..14999c6 --- /dev/null +++ b/kernel/src/lib.rs @@ -0,0 +1,84 @@ +// ? GLOBAL CRATE ATTRIBUTES AND DOCUMENTATION +// ? --------------------------------------------------------------------- + +// This crate does not and cannot use the standard library. +#![no_std] +// If we're testing, there is no main function, but a custom +// entrypoint `_start`. +#![cfg_attr(test, no_main)] +// Clippy lint target one. Enables all lints that are on by +// default (correctness, suspicious, style, complexity, perf) . +#![deny(clippy::all)] +// Clippy lint target two. Enables lints which are rather strict +// or have occasional false positives. +#![deny(clippy::nursery)] +// Clippy lint target three. Enables new lints that are still +// under development +#![deny(clippy::pedantic)] +// Clippy lint target four. Enable lints for the cargo manifest +// file, a.k.a. Cargo.toml. +#![deny(clippy::cargo)] +#![allow(clippy::multiple_crate_versions)] +// Lint target for code documentation. This lint enforces code +// documentation on every code item. +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +// Lint target for code documentation. When running `rustdoc`, +// show an error when using broken links. +#![deny(rustdoc::broken_intra_doc_links)] +// Use custom test runners. Since we cannot use the standard +// library, we have to use our own test framework. +#![feature(custom_test_frameworks)] +// With our own test framework, we have to define which function +// runs our tests. +#![test_runner(crate::library::test_runner)] +// We will have to re-export the actual test runner above with +// a new name so cargo is not confused. +#![reexport_test_harness_main = "__test_runner"] +// Since the `x86-interrupt` calling convention is still unstable, we +// have to opt-in. +#![feature(abi_x86_interrupt)] +// Since retrieving the message during a call to `panic!` is +// still unstable, we have to opt-in. +#![feature(panic_info_message)] + +//! # The `unCORE` Operating System Kernel +//! +//! This is `unCORE`, an operating system kerne completely written in +//! pure, idiomatic Rust. +//! +//! This file provides the library and modules for the actual binary. + +// ? MODULES and GLOBAL / CRATE-LEVEL FUNCTIONS +// ? --------------------------------------------------------------------- + +/// # The Core Library Path +/// +/// This module has been created to give the kernel source code a +/// well-defined structure and layout. The `library` module is used as +/// the child of the `src/lib.rs` "crate", not of `src/main.rs`. This +/// is important, and we are not allowed to mix them up. +pub mod library; + +/// # Kernel Library Testing Entrypoint +/// +/// This is the kernel's entry point called after the bootloader has +/// finished its setup. It is kept short on purpose. The +/// `library::init()` function takes care of initialization. +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start(boot_information: &'static mut bootloader::BootInfo) -> ! +{ + library::init(boot_information); + __test_runner(); + library::never_return() +} + +/// ### Default Panic Handler +/// +/// This function provides a very basic panic handler, that, depending +/// on whether you are running tests or not, writes an exit code and +/// does not return afterwards. Note that we do not unwind the stack. +#[cfg(test)] +#[panic_handler] +fn panic(panic_info: &::core::panic::PanicInfo) -> ! { library::panic_callback(false, panic_info) } diff --git a/kernel/src/library/hardware/architectures/arch_x86_32/mod.rs b/kernel/src/library/hardware/architectures/arch_x86_32/mod.rs new file mode 100644 index 0000000..718c6b3 --- /dev/null +++ b/kernel/src/library/hardware/architectures/arch_x86_32/mod.rs @@ -0,0 +1,3 @@ +#[allow(dead_code)] +#[allow(clippy::missing_const_for_fn)] +pub fn init() {} diff --git a/kernel/src/library/hardware/architectures/arch_x86_64/exceptions.rs b/kernel/src/library/hardware/architectures/arch_x86_64/exceptions.rs new file mode 100644 index 0000000..df8eb40 --- /dev/null +++ b/kernel/src/library/hardware/architectures/arch_x86_64/exceptions.rs @@ -0,0 +1,69 @@ +/// ### Double Fault Interrupt Stack Table Index +/// +/// This constant defines the stack to use in the Interrupt Stack +/// Table (IST) field on the TSS for the double fault handler. The +/// first index is chosen. +/// +/// The `interrupt_stack_table` is a field in the Task State Segment +/// (TSS) struct. It can be used to switch kernel stacks. +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +/// ## CPU Exception Handlers +/// +/// This module contains all CPU exception handler callback functions. +/// They are registered in the Interrupt Descriptor Table (IDT). +pub(super) mod handlers +{ + use x86_64::structures::idt::{ + InterruptStackFrame, + PageFaultErrorCode, + }; + + /// ### CPU Exception - Breakpoint Handler + /// + /// This is the handler callback function for the Breakpoint + /// CPU Exception. + pub extern "x86-interrupt" fn breakpoint(_stack_frame: InterruptStackFrame) + { + crate::log_info!("CPU exception occurred (breakpoint, no abort)"); + } + + /// ### CPU Exception - Double Fault Handler + /// + /// This is the handler callback function for the Double Fault + /// CPU Exception + /// + /// #### Trivia + /// + /// One difference to the breakpoint handler is that the + /// double fault handler is diverging. The reason is that + /// the `x86_64` architecture does not permit returning from + /// a double fault exception. + pub extern "x86-interrupt" fn double_fault( + stack_frame: InterruptStackFrame, + _error_code: u64, + ) -> ! + { + crate::log_fatal!( + "Fatal CPU exception occurred (double fault, abort through panic)\n{:#?}\n", + stack_frame + ); + panic!("received fatal double fault exception"); + } + + /// ### CPU Exception - Page Fault Handler + /// + /// This is the handler callback function for the page fault + /// CPU exception. + pub extern "x86-interrupt" fn page_fault( + _stack_frame: InterruptStackFrame, + _error_code: PageFaultErrorCode, + ) + { + crate::log_warning!("CPU exception occurred (page fault, no abort)"); + crate::library::never_return(); + } + + #[test_case] + fn breakpoint_exception() { x86_64::instructions::interrupts::int3(); } +} diff --git a/kernel/src/library/hardware/architectures/arch_x86_64/general.rs b/kernel/src/library/hardware/architectures/arch_x86_64/general.rs new file mode 100644 index 0000000..c51d4dc --- /dev/null +++ b/kernel/src/library/hardware/architectures/arch_x86_64/general.rs @@ -0,0 +1,128 @@ +/// ## Global Descriptor Table Setup +/// +/// This module handles the setup of the Global Descriptor Table (GDT) +/// and relates structures such as the Task State Segment (TSS) and +/// Interrupt Stack Table (IST). +pub(super) mod gdt +{ + use super::super::exceptions; + + use x86_64::{ + instructions::{ + tables, + segmentation::{ + self, + Segment, + }, + }, + structures::{ + gdt::{ + Descriptor, + GlobalDescriptorTable, + SegmentSelector, + }, + tss, + }, + }; + + lazy_static::lazy_static! { + + /// ### Task State Segment (TSS) + /// + /// On x86_64, the TSS holds two stack tables (the IST is one of them). + /// It is used to setup the IST to switch to a new table so exceptions + /// can be handled properly on a new stack. + static ref TSS: tss::TaskStateSegment = { + let mut tss = tss::TaskStateSegment::new(); + + // we now define the kernel stack to use when a double + // fault exception occurs to prevent fatal triple fault + // exceptions (e.g. due to hitting the guard page) + tss.interrupt_stack_table[exceptions::DOUBLE_FAULT_IST_INDEX as usize] = { + /// The size of the stack used during + /// the CPU double fault exception. + const STACK_SIZE: usize = 4096 * 10; + + /// Size-aligned representation of the stack used + /// during the CPU double fault exception. + #[repr(align(16))] + struct Stack([u8; STACK_SIZE]); + + /// The stack representation of the actual stack + /// used during the CPU double fault exception. + static mut STACK: Stack = Stack([0; STACK_SIZE]); + + // on x86_64 the stack grows downwards, therefore, the + // "start" is the lowest address and we return the + // "end" address which is the highest + let stack_start = x86_64::VirtAddr::from_ptr(unsafe { &STACK }); + stack_start + STACK_SIZE + }; + + tss + }; + + /// ### Global Descriptor Table (GDT) + /// + /// The GDT is a relict that was used for memory segmentation before + /// paging became the de facto standard. It is still needed in 64-bit + /// mode for various things such as kernel/user mode configuration or + /// TSS loading. + /// + /// While segmentation is no longer supported in 64-bit mode, the GDT + /// still exists. It is mostly used for two things: Switching between + /// kernel space and user space, and loading a TSS structure. + static ref GDT: (GlobalDescriptorTable, Selectors) = { + let mut gdt = GlobalDescriptorTable::new(); + + let code_segment = gdt.add_entry(Descriptor::kernel_code_segment()); + let tss_segment = gdt.add_entry(Descriptor::tss_segment(&TSS)); + let stack_segment = SegmentSelector(0); + + ( + gdt, + Selectors { + code_segment, + stack_segment, + tss_segment, + }, + ) + }; + } + + /// ### GDT Selectors + /// + /// This struct holds the necessary selectors which need to + /// be loaded. This makes sure the correct GDT and TSS are + /// used. + struct Selectors + { + /// The Code Segment (`cs`) register selector + code_segment: SegmentSelector, + /// The Stack Segment (`ss`) register selector + stack_segment: SegmentSelector, + /// The [`TSS`] selector + tss_segment: SegmentSelector, + } + + /// ### Loading the GDT + /// + /// The Global Descriptor Table (GDT) is loaded here. + /// Furthermore, the Code Segment register (`cs`) is set, the + /// Stack Segment register (`ss`) is loaded and the correct + /// TSS is selected. + pub(in super::super) fn init() + { + crate::log_trace!( + "Initializing Global Descriptor Table (GDT) and Task State Segment (TSS)" + ); + GDT.0.load(); + + unsafe { + segmentation::CS::set_reg(GDT.1.code_segment); + segmentation::SS::set_reg(GDT.1.stack_segment); + + tables::load_tss(GDT.1.tss_segment); + } + } +} diff --git a/kernel/src/library/hardware/architectures/arch_x86_64/interrupts.rs b/kernel/src/library/hardware/architectures/arch_x86_64/interrupts.rs new file mode 100644 index 0000000..86784ea --- /dev/null +++ b/kernel/src/library/hardware/architectures/arch_x86_64/interrupts.rs @@ -0,0 +1,186 @@ +use x86_64::structures::idt::InterruptDescriptorTable; + +lazy_static::lazy_static! { + + /// ### Interrupt Descriptor Table (IDT) + /// + /// "In order to catch and handle exceptions, we have to set up a + /// so-called Interrupt Descriptor Table (IDT). In this table we + /// can specify a handler function for each CPU exception." [1] + /// + /// Moreover, interrupt handler callback functions are also + /// registered in the IDT. + /// + /// [1]: https://os.phil-opp.com/cpu-exceptions/#the-interrupt-descriptor-table + static ref IDT: InterruptDescriptorTable = { + use super::exceptions; + + let mut idt = InterruptDescriptorTable::new(); + + // register exception handler callback functions + idt.breakpoint.set_handler_fn( + exceptions::handlers::breakpoint + ); + + idt.page_fault.set_handler_fn( + exceptions::handlers::page_fault + ); + + unsafe { + idt.double_fault + .set_handler_fn(exceptions::handlers::double_fault) + .set_stack_index(exceptions::DOUBLE_FAULT_IST_INDEX); + } + + // register interrupt handler callback functions + idt[handlers::InterruptIndex::Timer as usize] + .set_handler_fn(handlers::timer); + + idt[handlers::InterruptIndex::Keyboard as usize] + .set_handler_fn(handlers::keyboard); + + idt + }; +} + +/// ### Initializing Interrupts +/// +/// This function initializes the Interrupt Descriptor Table (IDT), +/// all programmable interrupt controllers (PICs) and enabled +/// interrupts in the end. +pub(super) fn init() +{ + crate::log_trace!("Initializing Interrupt Descriptor Table (IDT)"); + IDT.load(); + + crate::log_trace!("Initializing programmable interrupt controllers (PICs)"); + unsafe { + handlers::PICS.lock().initialize(); + } + + crate::log_trace!("Enabling interrupts"); + x86_64::instructions::interrupts::enable(); +} + +/// ## CPU Interrupt Handlers +/// +/// This module contains all CPU interrupt handler callback functions. +/// They are registered in the Interrupt Descriptor Table (IDT). +/// +/// We use programmable interrupt controllers (PICs) to notify out +/// kernel of these hardware interrupts. +mod handlers +{ + use pic8259::ChainedPics; + use spin::Mutex; + use x86_64::structures::idt::InterruptStackFrame; + + /// ### Offset for PIC No. 1 + /// + /// The default configuration of the PICs is not usable, + /// because it sends interrupt vector numbers in the range + /// 0–15 to the CPU. These numbers are already occupied by CPU + /// exceptions, for example number 8 corresponds to a double + /// fault. To fix this overlapping issue, we need to remap the + /// PIC interrupts to different numbers. The actual range + /// doesn't matter as long as it does not overlap with the + /// exceptions, but typically the range 32–47 is chosen, + /// because these are the first free numbers after the 32 + /// exception slots. + const PIC_1_OFFSET: u8 = 32; + + /// ### Offset for PIC No. 2 + /// + /// Adds on top of [`PIC_1_OFFSET`] + const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + + /// ### Interrupt Indices + /// + /// Instead of hardcoding interrupt indices, we store them in + /// this enum for each interrupt. + #[derive(Debug, Clone, Copy)] + #[repr(u8)] + pub enum InterruptIndex + { + /// The timer interrupt index for the chained PIC + Timer = PIC_1_OFFSET, + /// The keyboard interrupt index for the chained PIC + Keyboard, + } + + /// ### Chained PICs + /// + /// This structure provides access to the two chained PICs we + /// use for notifying the CPU that an interrupt has occurred. + pub(super) static PICS: Mutex = + Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + + /// ### End of Interrupt Signalization + /// + /// Every hardware (I/O) interrupt handler must issue an "end + /// of interrupt" (EOI) signal at the end to signal that we're + /// finished with processing the interrupt. + /// + /// This function provides a safe wrapper around the unsafe + /// method. + fn notify_end_of_interrupt(interrupt_index: InterruptIndex) + { + unsafe { + PICS.lock().notify_end_of_interrupt(interrupt_index as u8); + } + } + + /// ### CPU Interrupt - Timer Handler + /// + /// This is the handler function for timer interrupts. + pub extern "x86-interrupt" fn timer(_stack_frame: InterruptStackFrame) + { + notify_end_of_interrupt(InterruptIndex::Timer); + } + + /// ### CPU Interrupt - Keyboard Handler + /// + /// This is the handler function which reacts to keyboard + /// input. Currently, every keystroke is printed directly on + /// the screen. + pub extern "x86-interrupt" fn keyboard(_stack_frame: InterruptStackFrame) + { + // use pc_keyboard::{ + // layouts::Us104Key, + // DecodedKey, + // HandleControl, + // Keyboard, + // ScancodeSet1, + // }; + + // lazy_static::lazy_static! { + + // /// ### The Keyboard Representation + // /// + // /// This + // static ref KEYBOARD: Mutex> = + // Mutex::new(Keyboard::new(Us104Key, ScancodeSet1, + // HandleControl::Ignore)); } + + // let mut keyboard = KEYBOARD.lock(); + // let mut port = x86_64::instructions::port::Port::new(0x60); + // let scancode: u8 = unsafe { port.read() }; + + // if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { + // if let Some(key) = keyboard.process_keyevent(key_event) { + // match key { + // DecodedKey::Unicode(character) => { + // crate::log!("{}", character); + // }, + // DecodedKey::RawKey(key) => { + // crate::log!("{:?}", key); + // }, + // } + // } + // } + + crate::log_info!("Keyboard interrupt occurred"); + + notify_end_of_interrupt(InterruptIndex::Keyboard); + } +} diff --git a/kernel/src/library/hardware/architectures/arch_x86_64/mod.rs b/kernel/src/library/hardware/architectures/arch_x86_64/mod.rs new file mode 100644 index 0000000..f0c1f5d --- /dev/null +++ b/kernel/src/library/hardware/architectures/arch_x86_64/mod.rs @@ -0,0 +1,31 @@ +/// ## CPU Exception Callbacks +/// +/// This module contains the callback functions executed when a CPU +/// exception is thrown. +mod exceptions; + +/// ## General CPU Initialization +/// +/// This module contains code for general setup code, such as Global +/// Descriptor Table (GDT) setup. +mod general; + +/// ## CPU Interrupt Callbacks +/// +/// This module contains the callback functions executed when CPU +/// interrupts arrive. +mod interrupts; + +/// ### `x86_64` Setup +/// +/// The `x86_64`-specific setup is handled by this function. +pub fn init() +{ + crate::log_trace!("Architecture specific initialization started"); + crate::log_trace!("Running on x68_64"); + + general::gdt::init(); + interrupts::init(); + + crate::log_trace!("Architecture specific initialization finished"); +} diff --git a/kernel/src/library/hardware/architectures/mod.rs b/kernel/src/library/hardware/architectures/mod.rs new file mode 100644 index 0000000..c6137f5 --- /dev/null +++ b/kernel/src/library/hardware/architectures/mod.rs @@ -0,0 +1,17 @@ +/// ## The `x86_64` Architecture +/// +/// This module contains `x86_64` specific initialization and setup +/// code. Compiled conditionally. +#[cfg(target_arch = "x86_64")] +mod arch_x86_64; + +#[cfg(target_arch = "x86_64")] pub use arch_x86_64::init; + +/// ## The `x86_32` Architecture +/// +/// This module contains `x86_32` specific initialization and setup +/// code. Compiled conditionally. +#[cfg(target_arch = "x86_32")] +mod arch_x86_32; + +#[cfg(target_arch = "x86_32")] pub use arch_x86_32::init; diff --git a/kernel/src/library/hardware/mod.rs b/kernel/src/library/hardware/mod.rs new file mode 100644 index 0000000..052e3cd --- /dev/null +++ b/kernel/src/library/hardware/mod.rs @@ -0,0 +1,17 @@ +/// ## Architectural Differences +/// +/// This module contains architecture specific initialization code and +/// uses conditional compilation. +mod architectures; + +/// ### Hardware Initialization +/// +/// This function initializes the hardware module by +/// +/// 1. Calling the architecture-specific initialization subroutines +pub(super) fn init() +{ + crate::log_debug!("Initializing hardware"); + + architectures::init(); +} diff --git a/kernel/src/library/helper/log.rs b/kernel/src/library/helper/log.rs new file mode 100644 index 0000000..2bb9cef --- /dev/null +++ b/kernel/src/library/helper/log.rs @@ -0,0 +1,227 @@ +use ::core::fmt; + +/// ### The Logging Severity Level +/// +/// The `LOG_LEVEL` is used to define which messages are being logged. +/// All messaged with an equal or higher priority are logged when not +/// running tests. When running tests, all messages with a severity of +/// `Level::Warning` or higher are logged. +static mut LOG_LEVEL: Level = if super::test::IS_TEST { + Level::Trace +} else { + Level::Info +}; + +/// ### Log Level +/// +/// This struct holds the five well-known log levels `Trace`, `Debug`, +/// `info`, `Warning`, `Error` and `Fatal` as well as the `Test` log +/// level which is for logging during tests. +#[doc(hidden)] +#[allow(dead_code)] +#[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)] +pub enum Level +{ + Trace, + Debug, + Info, + Warning, + Error, + Fatal, + Test, + None, +} + +impl fmt::Display for Level +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + match &self { + Self::Trace => write!(f, "TRACE | "), + Self::Debug => write!(f, "DEBUG | "), + Self::Info => write!(f, "INFO | "), + Self::Warning => write!(f, "WARNING | "), + Self::Error => write!(f, "ERROR | "), + Self::Fatal => write!(f, "FATAL | "), + Self::Test => write!(f, "TEST | "), + Self::None => write!(f, ""), + } + } +} + +/// ### Set the Kernel Log Level +/// +/// This function adjusts the kernel log level. Only call this +/// function once and at the very start if necessary. +pub fn set_log_level(new_log_level: Level) +{ + unsafe { + LOG_LEVEL = new_log_level; + } +} + +/// ### Log Indirection +/// +/// An indirection that is used in order to make it easy to switch to +/// different loggers or log to multiple interfaces. The indirection +/// is nice because now we do not need to make the different output +/// modules (such as `serial`) in this module public. +#[doc(hidden)] +pub fn __log(log_level: Level, arguments: fmt::Arguments) +{ + if log_level < unsafe { LOG_LEVEL } { + return; + } + + serial::print(format_args!("{}{}\n", log_level, arguments)); +} + +/// ### Print Welcome +/// +/// This function is just here to log an unmodified line. It exists to +/// print the welcome message of the kernel and should not be used +/// somewhere else. Please use the log macros provided below that +/// designate a certain log level. It is used for tests to show the +/// `[ok]` message after a test has finished successfully. +#[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::None, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::TRACE` +/// +/// The highest log level here for very, very detailed output. +#[macro_export] +macro_rules! log_trace { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Trace, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::DEBUG` +/// +/// For debugging purposes and very detailed output. +#[macro_export] +macro_rules! log_debug { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Debug, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::INFO` +/// +/// Log informational output with this macro. +#[macro_export] +macro_rules! log_info { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Info, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::WARNING` +/// +/// This log level makes the reader aware something may not be +/// correct. +#[macro_export] +macro_rules! log_warning { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Warning, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::ERROR` +/// +/// Show that something definitely is incorrect, but not +/// unrecoverable. +#[macro_export] +macro_rules! log_error { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Error, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::FATAL` +/// +/// Show that something went very wrong. This indicates a +/// unrecoverable situation, such as a double fault. +#[macro_export] +macro_rules! log_fatal { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Fatal, + format_args!($($arg)*) + ) + }; +} + +/// ### Log with `Level::TEST` +/// +/// This log level shall be used when running tests. +#[macro_export] +macro_rules! log_test { + ($($arg:tt)*) => { + $crate::library::helper::log::__log( + $crate::library::helper::log::Level::Test, + format_args!($($arg)*) + ) + }; +} + +/// ## A Serial Device Interface +/// +/// The `write` module makes heavy use of this module +/// as it `serial` provides the ability to write to +/// a serial device which is "forwarded" to `stdout` +/// via QEMU. +mod serial +{ + use spin::{ + Lazy, + Mutex, + }; + use uart_16550::SerialPort; + + /// ### Serial Writer + /// + /// With this port, we can write to the serial output. + static SERIAL0: Lazy> = Lazy::new(|| { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }); + + /// ### Write to Serial Output + /// + /// This function prints its arguments to the serial output. + pub(super) fn print(arguments: ::core::fmt::Arguments) + { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + SERIAL0.lock() + .write_fmt(arguments) + .expect("Printing to serial failed"); + }); + } +} diff --git a/kernel/src/library/helper/miscellaneous.rs b/kernel/src/library/helper/miscellaneous.rs new file mode 100644 index 0000000..43442a9 --- /dev/null +++ b/kernel/src/library/helper/miscellaneous.rs @@ -0,0 +1,98 @@ +/// ### Kernel Version +/// +/// The `VERSION` variable contains the kernel version in the semantic +/// versioning format, the git commit id the kernel was built with and +/// the build date. If `VERSION` was not available during build-time, +/// a default value is provided, namely "testing". +const VERSION: Option<&str> = option_env!("VERSION"); + +/// ### The Kernel Target +/// +/// The kernel target is a triple consisting of +/// +/// 1. The hardware architecture +/// 2. The vendor (manufacturer) (optional and omitted in our case) +/// 3. Operating system +/// 4. ABI +/// +/// The target triple reads as `ARCH-VENDOR-SYS-ABI` and you can read +/// about it [here](https://docs.rust-embedded.org/embedonomicon/custom-target.html). +/// +/// The default case for `unCORE` is `x86_64-unknown-none`. This +/// target is for freestanding / bare-metal `x86-64` binaries in ELF +/// format, i.e. firmware, kernels, etc. +const TARGET: Option<&str> = option_env!("KERNEL_BUILD_TARGET"); + +/// ### The Event Horizon +/// +/// This function is just a nice abstraction of the call to `loop +/// {...}`, making it absolutely clear what the intend of calling this +/// function is, using its name. +/// +/// We use the `hlt` instruction to "halt" the CPU to not burn through +/// CPU time, as a call to `loop {}` would do. +#[inline] +pub fn never_return() -> ! +{ + loop { + x86_64::instructions::hlt(); + } +} + +/// ### Print Important Initial Information +/// +/// We print out information about +/// +/// - the kernel version +/// - the bootloader +/// +/// on the serial interface with this function. +pub(in super::super) fn display_initial_information(boot_information: &bootloader::BootInfo) +{ + crate::log!("This is unCORE {}\n", VERSION.unwrap_or("testing")); + crate::log_info!( + "Target triple reads '{}'", + TARGET.unwrap_or("x86_64-unknown-none (defaulted)") + ); + crate::log_info!( + "Bootloader version {}.{}.{}", + boot_information.version_major, + boot_information.version_minor, + boot_information.version_patch + ); +} + +/// ## QEMU Abstractions +/// +/// Contains helpers for running the kernel with QEMU. +pub mod qemu +{ + /// ### Write An Exit Code + /// + /// Looks up `[package.metadata.bootimage]` in `Cargo.toml` to + /// use the `isa-debug-exit` device, located on port `0xf4` + /// with a four byte size. + fn exit(exit_code: u32) + { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xF4); + port.write(exit_code); + } + } + + /// ### Exit QEMU With Success + /// + /// Write a success exit code for QEMU to recognize and exit. + #[allow(dead_code)] + #[inline] + pub fn exit_with_success() { exit(0x10); } + + /// ### Exit QEMU Without Success + /// + /// Write a failure exit code for QEMU to recognize and exit. + #[allow(dead_code)] + #[inline] + pub fn exit_with_failure() { exit(0x11); } +} diff --git a/kernel/src/library/helper/mod.rs b/kernel/src/library/helper/mod.rs new file mode 100644 index 0000000..5acf061 --- /dev/null +++ b/kernel/src/library/helper/mod.rs @@ -0,0 +1,29 @@ +/// ## Uniform Logging +/// +/// This module exports the `log_!` macros with different log levels. +pub mod log; + +/// ## Miscellaneous Helpers +/// +/// Provides various of the most generic helper functions, such as +/// `never_return()`. +mod miscellaneous; + +pub(super) use miscellaneous::display_initial_information; +pub use miscellaneous::never_return; +pub use miscellaneous::qemu; + +/// ## Provides the API for Panicking +/// +/// This module provides the implementation for the panic macro and +/// panic behavior. +mod panic; + +pub use panic::panic_callback; + +/// ## Providing Support for Tests +/// +/// This module provides the implementation to run tests. This +/// includes unit-tests as well as integration tests. +mod test; +pub use test::test_runner; diff --git a/kernel/src/library/helper/panic.rs b/kernel/src/library/helper/panic.rs new file mode 100644 index 0000000..c9a9778 --- /dev/null +++ b/kernel/src/library/helper/panic.rs @@ -0,0 +1,87 @@ +use ::core::panic::PanicInfo; + +/// ### Panic Handler when Running Tests that Should Not Panic +/// +/// This function is marked for conditional compilation, and +/// is used when running the custom tests suite. +#[cfg(test)] +#[inline] +fn __default_panic(_panic_info: &PanicInfo) -> ! +{ + crate::log_error!("Last test did not finish. FAILURE."); + crate::log_fatal!("Received panic"); + + super::miscellaneous::qemu::exit_with_failure(); + super::miscellaneous::never_return() +} + +/// ### Panic Handler when not Running Tests +/// +/// This function is marked for conditional compilation, and +/// is used when running the binary natively, i.e. not the +/// tests. +#[cfg(not(test))] +#[inline] +fn __default_panic(panic_info: &PanicInfo) -> ! +{ + crate::log_fatal!( + "Received panic (message = {:?})", + panic_info + .message() + .unwrap_or(&format_args!("no message provided")) + ); + + super::miscellaneous::never_return() +} + +/// ### Panic Handler that Should Panic +/// +/// This function provides a panic handler that should panic and that +/// will therefore show success. +#[inline] +fn __should_panic(_panic_info: &PanicInfo) -> ! +{ + crate::log_test!("Received panic. SUCCESS."); + + super::miscellaneous::qemu::exit_with_success(); + super::miscellaneous::never_return() +} + +/// ### Callback Panic Handler +/// +/// This function exists because we want to write integration tests +/// for which certain tests should panic. Without this function, we'd +/// need to repeat the panic code for each integration test. If a test +/// should panic, set `should_panic` to `true` and the code will take +/// care of the rest. Now, only small code repetition is necessary: +/// +/// +/// If tests SHOULD NOT panic, write this in your application +/// +/// ``` rust +/// #[panic_handler] +/// pub fn panic(panic_info: &::core::panic::PanicInfo) -> ! +/// { +/// kernel::panic_callback(false, panic_info) +/// } +/// ``` +/// +/// If tests SHOULD panic, write this in your application +/// +/// ``` rust +/// #[panic_handler] +/// pub fn panic(panic_info: &::core::panic::PanicInfo) -> ! +/// { +/// kernel::panic_callback(true, panic_info) +/// } +/// ``` +#[inline] +#[allow(clippy::module_name_repetitions)] +pub fn panic_callback(should_panic: bool, panic_info: &PanicInfo) -> ! +{ + if should_panic { + __should_panic(panic_info); + } else { + __default_panic(panic_info); + } +} diff --git a/kernel/src/library/helper/test.rs b/kernel/src/library/helper/test.rs new file mode 100644 index 0000000..0996294 --- /dev/null +++ b/kernel/src/library/helper/test.rs @@ -0,0 +1,71 @@ +/// ### Are We Running Tests? +/// +/// Can be used to get information about whether tests are run or not. +#[allow(dead_code)] +#[cfg(test)] +pub const IS_TEST: bool = true; + +/// ### Are We Running Tests? +/// +/// Can be used to get information about whether tests are run or not. +#[allow(dead_code)] +#[cfg(not(test))] +pub const IS_TEST: bool = false; + +/// ### A (Very) Simple Test Runner Implementation +/// +/// This function is registered as the tests runner when executing +/// Cargo test's unit tests. +/// +/// It will just execute all functions marked with `#[test_case]` one +/// by one. +#[allow(clippy::module_name_repetitions)] +pub fn test_runner(tests: &[&dyn Testable]) +{ + crate::log_test!("Starting tests"); + + for test in tests { + test.run(); + } + + super::miscellaneous::qemu::exit_with_success(); +} + +/// ### Streamlining Testing +/// +/// This trait provides the tests runner with the ability to `.run` +/// tests. This is done for all functions in the `impl` block, so they +/// can be "parsed" to reduce boilerplate code. +pub trait Testable +{ + /// ### Run Tests + /// + /// The `run` function will literally just execute the + /// function it contains, as `Testable` is implemented for all + /// generics that implement `Fn()`. + fn run(&self); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) + { + crate::log_test!("Testing {}", ::core::any::type_name::()); + self(); + crate::log_test!("Last test finished. SUCCESS."); + } +} + +/// ### Sanity Check +/// +/// This tests is just here for sanity's sake to make +/// sure tests behave correctly at the most basic level. +#[test_case] +fn trivial_assertion() +{ + const ONE: u8 = 1; + assert_eq!(1, ONE); + assert_eq!(ONE, 1); +} diff --git a/kernel/src/library/mod.rs b/kernel/src/library/mod.rs new file mode 100644 index 0000000..1ca399a --- /dev/null +++ b/kernel/src/library/mod.rs @@ -0,0 +1,40 @@ +/// ## Hardware Abstractions +/// +/// This module contains all hardware-specific code. Moreover, +/// architecture-specific code is also located here. This module is +/// initialized first after booting and starting the kernel. +mod hardware; + +/// ## Generic Helper Function +/// +/// This module provides generic function used by other modules, such +/// as +/// +/// - logging +/// - not returning +/// - panicking +/// - testing +/// +/// It also provides the test runners and the kernel version +/// information. +pub mod helper; + +pub use helper::never_return; +pub use helper::panic_callback; +pub use helper::test_runner; + +/// ### Global Initialization +/// +/// This function initializes the whole kernel. It takes care of +/// +/// - printing important initial information +/// - calling the hardware initialization subroutine +pub fn init(boot_information: &bootloader::BootInfo) +{ + helper::display_initial_information(boot_information); + crate::log_info!("Initialization started"); + + hardware::init(); + + crate::log_info!("Initialization finished"); +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs new file mode 100644 index 0000000..e3778e0 --- /dev/null +++ b/kernel/src/main.rs @@ -0,0 +1,93 @@ +// ? GLOBAL CRATE ATTRIBUTES AND DOCUMENTATION +// ? --------------------------------------------------------------------- + +// This crate does not and cannot use the standard library. +#![no_std] +// As this is no ordinary program, we have a special entry-point, +// which is not the `main()` function. +#![no_main] +// Clippy lint target one. Enables all lints that are on by +// default (correctness, suspicious, style, complexity, perf) . +#![deny(clippy::all)] +// Clippy lint target two. Enables lints which are rather strict +// or have occasional false positives. +#![deny(clippy::nursery)] +// Clippy lint target three. Enables new lints that are still +// under development +#![deny(clippy::pedantic)] +// Clippy lint target four. Enable lints for the cargo manifest +// file, a.k.a. Cargo.toml. +#![deny(clippy::cargo)] +#![allow(clippy::multiple_crate_versions)] +// Lint target for code documentation. This lint enforces code +// documentation on every code item. +#![deny(missing_docs)] +#![deny(clippy::missing_docs_in_private_items)] +// Lint target for code documentation. When running `rustdoc`, +// show an error when using broken links. +#![deny(rustdoc::broken_intra_doc_links)] +// Use custom test runners. Since we cannot use the standard +// library, we have to use our own test framework. +#![feature(custom_test_frameworks)] +// With our own test framework, we have to define which function +// runs our tests. +#![test_runner(library::test_runner)] +// We will have to re-export the actual test runner above with +// a new name so cargo is not confused. +#![reexport_test_harness_main = "__test_runner"] + +//! # The `unCORE` Operating System Kernel +//! +//! This is `unCORE`, an operating system kerne completely written in +//! pure, idiomatic Rust. +//! +//! This file provides the "entrypoint" for the main binary, i.e. the +//! kernel, as well as functions for integration tests. + +// ? MODULES and GLOBAL / CRATE-LEVEL FUNCTIONS +// ? --------------------------------------------------------------------- + +/// # Imports +/// +/// The `kernel::library` is used here explicitly with the `use` +/// statement, and not with the `mod` statement. As `kernel::library` +/// is already used in `lib.rs`, we do not want to re-import it here +/// and possibly confuse Cargo. +/// +/// The only exceptions so far is the `init()` function called at the +/// beginning of `_start`. It is called vi a`kernel::init()` which is +/// perfectly fine. +/// +/// ## Macros +/// +/// We will need to re-import all needed macros, as per definition +/// they reside in `crate`, which to be exact is `lib.rs`'s root and +/// not `main.rs`'s root. +/// +/// Make sure to **always** use `library::` instead of `crate::lib::` +/// or `lib::` or something else. +use kernel::library; + +/// # Kernel Binary Entrypoint +/// +/// This is the kernel's entry point called after the bootloader has +/// finished its setup. It is kept short on purpose. The +/// `library::init()` function takes care of initialization. +#[no_mangle] +pub extern "C" fn _start(boot_information: &'static mut bootloader::BootInfo) -> ! +{ + library::init(boot_information); + + #[cfg(test)] + __test_runner(); + + library::never_return() +} + +/// ### Default Panic Handler +/// +/// This function provides a very basic panic handler, that, depending +/// on whether you are running tests or not, writes an exit code and +/// does not return afterwards. Note that we do not unwind the stack. +#[panic_handler] +fn panic(panic_info: &::core::panic::PanicInfo) -> ! { library::panic_callback(false, panic_info) } diff --git a/modules/x86_64-uncore.json b/kernel/targets/x86_64-unknown-none.json similarity index 78% rename from modules/x86_64-uncore.json rename to kernel/targets/x86_64-unknown-none.json index 7d2110d..a518474 100644 --- a/modules/x86_64-uncore.json +++ b/kernel/targets/x86_64-unknown-none.json @@ -1,6 +1,6 @@ { "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", "arch": "x86_64", "target-endian": "little", "target-pointer-width": "64", diff --git a/kernel/tests/basic_boot.rs b/kernel/tests/basic_boot.rs new file mode 100644 index 0000000..1cd630d --- /dev/null +++ b/kernel/tests/basic_boot.rs @@ -0,0 +1,55 @@ +// ? GLOBAL CRATE ATTRIBUTES AND DOCUMENTATION +// ? --------------------------------------------------------------------- + +// This crate does not and cannot use the standard library. +#![no_std] +// As this is no ordinary program, we have a special entry-point, +// which is not the `main()` function. +#![no_main] +// Clippy lint target one. Enables all lints that are on by +// default (correctness, suspicious, style, complexity, perf) . +#![deny(clippy::all)] +// Clippy lint target two. Enables lints which are rather strict +// or have occasional false positives. +#![deny(clippy::nursery)] +// Clippy lint target three. Enables new lints that are still +// under development +#![deny(clippy::pedantic)] +// Use custom test runners. Since we cannot use the standard +// library, we have to use our own test framework. +#![feature(custom_test_frameworks)] +// With our own test framework, we have to define which function +// runs our tests. +#![test_runner(library::test_runner)] +// We will have to re-export the actual test runner above with +// a new name so cargo is not confused. +#![reexport_test_harness_main = "__test_runner"] + +use kernel::library::{ + self, + helper::log, +}; + +// ? MODULES and GLOBAL / CRATE-LEVEL FUNCTIONS +// ? --------------------------------------------------------------------- + +#[no_mangle] +pub extern "C" fn _start(boot_information: &'static mut bootloader::BootInfo) -> ! +{ + log::set_log_level(log::Level::Trace); + kernel::log!("Running an integration test."); + library::init(boot_information); + + __test_runner(); + + library::never_return() +} + +#[panic_handler] +fn panic(panic_info: &::core::panic::PanicInfo) -> ! { library::panic_callback(false, panic_info) } + +#[test_case] +fn test_println() +{ + kernel::log_info!("Test log output. Does not panic."); +} diff --git a/kernel/tests/should_panic.rs b/kernel/tests/should_panic.rs new file mode 100644 index 0000000..14278be --- /dev/null +++ b/kernel/tests/should_panic.rs @@ -0,0 +1,58 @@ +// ? GLOBAL CRATE ATTRIBUTES AND DOCUMENTATION +// ? --------------------------------------------------------------------- + +// This crate does not and cannot use the standard library. +#![no_std] +// As this is no ordinary program, we have a special entry-point, +// which is not the `main()` function. +#![no_main] +// Clippy lint target one. Enables all lints that are on by +// default (correctness, suspicious, style, complexity, perf) . +#![deny(clippy::all)] +// Clippy lint target two. Enables lints which are rather strict +// or have occasional false positives. +#![deny(clippy::nursery)] +// Clippy lint target three. Enables new lints that are still +// under development +#![deny(clippy::pedantic)] +// Use custom test runners. Since we cannot use the standard +// library, we have to use our own test framework. +#![feature(custom_test_frameworks)] +// With our own test framework, we have to define which function +// runs our tests. +#![test_runner(library::test_runner)] +// We will have to re-export the actual test runner above with +// a new name so cargo is not confused. +#![reexport_test_harness_main = "__test_runner"] + +use kernel::library::{ + self, + helper::log, +}; + +// ? MODULES and GLOBAL / CRATE-LEVEL FUNCTIONS +// ? --------------------------------------------------------------------- + +#[no_mangle] +pub extern "C" fn _start(boot_information: &'static mut bootloader::BootInfo) -> ! +{ + log::set_log_level(log::Level::Trace); + kernel::log!("Running an integration test."); + library::init(boot_information); + + __test_runner(); + + kernel::log_error!("Test did not panic but was expected to. FAILURE."); + kernel::library::helper::qemu::exit_with_failure(); + + library::never_return() +} + +#[test_case] +fn this_test_should_panic() +{ + assert_eq!(0, 1); +} + +#[panic_handler] +fn panic(panic_info: &::core::panic::PanicInfo) -> ! { library::panic_callback(true, panic_info) } diff --git a/modules/.cargo/config.toml b/modules/.cargo/config.toml deleted file mode 100644 index d31fa3c..0000000 --- a/modules/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[unstable] -build-std = ["core", "compiler_builtins"] -build-std-features = ["compiler-builtins-mem"] - -[build] -target = "x86_64-uncore.json" - -[target.'cfg(target_os = "none")'] -runner = "bootimage runner" diff --git a/modules/Cargo.toml b/modules/Cargo.toml deleted file mode 100644 index 17f264d..0000000 --- a/modules/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[workspace] - -# Rust toolchain releases can be found under -# https://github.com/rust-lang/rust/blob/master/RELEASES.md - -members = [ - "communication", - "hardware", - "kernel", - "memory", - "processes", - "scheduling", - "syscalls" -] - -[profile.release] -lto = true -codegen-units = 1 diff --git a/modules/communication/Cargo.toml b/modules/communication/Cargo.toml deleted file mode 100644 index cb35837..0000000 --- a/modules/communication/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "communication" -version = "0.1.0" -edition = "2021" - -workspace = "../" - -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel IPC Facilty [core]" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = [ - "operating-system", - "no-std", - "communication", - "processes", - "ipc" -] - -categories = [ - "no-std", - "config", -] - -publish = false - -[dependencies] diff --git a/modules/communication/src/lib.rs b/modules/communication/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/communication/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/modules/hardware/src/lib.rs b/modules/hardware/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/hardware/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/modules/kernel/Cargo.toml b/modules/kernel/Cargo.toml deleted file mode 100644 index 1a6a8c3..0000000 --- a/modules/kernel/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -edition = "2021" -name = "kernel" -version = "0.1.0" - -workspace = "../" - -authors = ["The unCORE Kernel Community"] - -description = "An Operating System Microkernel written in pure, idiomatic Rust" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = ["operating-system", "os", "kernel", "no-std"] - -categories = ["no-std", "config"] - -publish = false - -autobenches = true -autobins = true -autoexamples = true -autotests = true - -[dependencies] -bootloader = "0.10.9" -lazy_static = { version = "1.4.0", features = ["spin_no_std"] } -pc-keyboard = "0.5.1" -pic8259 = "0.10.0" -spin = "0.9.2" -uart_16550 = "0.2.10" -volatile = "0.4.4" -x86_64 = "0.14.6" - -[package.metadata.bootimage] -test-args = [ - "-device", - "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-serial", - "stdio", - "-display", - "none", -] -test-success-exit-code = 33 # (0x10 << 1) | 1 -test-timeout = 100 # in seconds - -# [[test]] -# harness = false -# name = "stack_overflow" diff --git a/modules/kernel/src/core_lib/hw/cpu/exceptions_handlers.rs b/modules/kernel/src/core_lib/hw/cpu/exceptions_handlers.rs deleted file mode 100644 index b2f52a8..0000000 --- a/modules/kernel/src/core_lib/hw/cpu/exceptions_handlers.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::println; -use x86_64::structures::idt::InterruptStackFrame; - -/// # Printing Exception Information -/// -/// This function provides an interface for all CPU exception handlers to -/// uniformly print information about the exception that happened. -fn print_information( - exception_type: &str, - abort_through_panic: bool, - stack_frame: &mut InterruptStackFrame, -) -{ - println!("\n{{{{ CPU EXCEPTION }}}}\n"); - println!(" :: EXCEPTION TYPE = {}", exception_type); - println!(" :: ABORT THROUGH PANIC = {}", abort_through_panic); - println!(" :: STACK FRAME STATE = \n\n{:#?}", stack_frame); -} - -/// # CPU Exception - Double Fault Handler -/// -/// This is the handler for the Double Fault -/// CPU Exception -/// -/// ## Trivia -/// -/// One difference to the breakpoint handler is that the -/// double fault handler is diverging. The reason is that the `x86_64` -/// architecture does not permit returning from a double fault exception. -pub extern "x86-interrupt" fn double_fault_handler( - stack_frame: &mut InterruptStackFrame, - _error_code: u64, -) -> ! -{ - print_information("DOUBLE_FAULT", true, stack_frame); - - panic!("[FATAL] DOUBLE FAULT"); -} - -/// # CPU Exception - Breakpoint Handler -/// -/// This is the handler for the Breakpoint CPU Exception. -pub extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut InterruptStackFrame) -{ - print_information("BREAKPOINT", false, stack_frame); -} - -#[test_case] -fn test_breakpoint_exception() { x86_64::instructions::interrupts::int3(); } diff --git a/modules/kernel/src/core_lib/hw/cpu/gdt.rs b/modules/kernel/src/core_lib/hw/cpu/gdt.rs deleted file mode 100644 index 509b860..0000000 --- a/modules/kernel/src/core_lib/hw/cpu/gdt.rs +++ /dev/null @@ -1,63 +0,0 @@ -use lazy_static::lazy_static; -use x86_64::{ - instructions, - structures::gdt::{ - Descriptor, - GlobalDescriptorTable, - SegmentSelector, - }, -}; - -/// # Double Fault Interrupt Stack Table Index -/// -/// This constant defines the stack to use in the -/// `interrupt_stack_table` field on the TSS for the -/// double fault handler. The first index is chosen. -/// -/// The `interrupt_stack_table` is a field in the `Task -/// State Segment` struct. It can be used to switch kernel -/// stacks. -pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; - -lazy_static! { - static ref GDT: (GlobalDescriptorTable, Selectors) = { - let mut gdt = GlobalDescriptorTable::new(); - - let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); - let tss_selector = gdt.add_entry(Descriptor::tss_segment(&super::tss::TSS)); - - ( - gdt, - Selectors { - code_selector, - tss_selector, - }, - ) - }; -} - -/// # GDT Selectors -/// -/// This struct holds the necessary selectors which need to be loaded. This -/// makes sure the correct GDT and TSS are used. -struct Selectors -{ - /// The Code Segment (`cs`) register selector - code_selector: SegmentSelector, - /// The TSS selector - tss_selector: SegmentSelector, -} - -/// # Loading the GDT -/// -/// The Global Descriptor Table (GDT) is loaded here. Furthermore, the Code -/// Segment register (`cs`) is (re-)loaded and the correct TSS is selected. -pub fn init() -{ - GDT.0.load(); - - unsafe { - // instructions::segmentation::set_cs(GDT.1.code_selector); - instructions::tables::load_tss(GDT.1.tss_selector); - } -} diff --git a/modules/kernel/src/core_lib/hw/cpu/idt.rs b/modules/kernel/src/core_lib/hw/cpu/idt.rs deleted file mode 100644 index 371dbce..0000000 --- a/modules/kernel/src/core_lib/hw/cpu/idt.rs +++ /dev/null @@ -1,53 +0,0 @@ -use lazy_static::lazy_static; -use x86_64::structures::idt::InterruptDescriptorTable; - -// use super::{ -// exceptions_handlers::{ -// breakpoint_handler, -// double_fault_handler, -// }, -// gdt, -// super::io::interrupts, -// }; - -lazy_static! { - static ref IDT: InterruptDescriptorTable = { - let mut idt = InterruptDescriptorTable::new(); - - // // CPU exception handlers are now registered - // idt.breakpoint.set_handler_fn(breakpoint_handler); - - // unsafe { - // idt.double_fault - // .set_handler_fn(double_fault_handler) - // .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); - // } - - // // hardware (I/O) interrupt handlers are now registered - // idt[interrupts::InterruptIndex::Timer as usize] - // .set_handler_fn(interrupts::timer_interrupt_handler); - - // idt[interrupts::InterruptIndex::Keyboard as usize] - // .set_handler_fn(interrupts::keyboard_interrupt_handler); - - idt - }; -} - -/// # Loading the IDT -/// -/// The Interrupt Descriptor Table (IDT) is loaded here. -/// -/// ## Registered CPU Exception Handlers -/// -/// Currently, these CPU exception handlers are registered: -/// -/// - Double Fault Handler (without kernel stack switch) -/// - Breakpoint Handler -/// -/// ## Registered Hardware (I/O) Interrupt Handlers -/// -/// Currently, these hardware (I/O) interrupt handlers are registered: -/// -/// - -pub fn init() { IDT.load(); } diff --git a/modules/kernel/src/core_lib/hw/cpu/mod.rs b/modules/kernel/src/core_lib/hw/cpu/mod.rs deleted file mode 100644 index 3f5de34..0000000 --- a/modules/kernel/src/core_lib/hw/cpu/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// # CPU Exceptions & Interrupt Handlers -/// -/// This module contains all CPU exception handlers which can be registered -/// in the IDT. -mod exceptions_handlers; - -/// # Global Descriptor Table -/// -/// This modules provides the integration for the Global Descriptor Table -/// (GDT). -pub mod gdt; - -/// # Interrupt Descriptor Table -/// -/// The Interrupt Descriptor Table (IDT) is initialized here. -pub mod idt; - -/// # Task State Segment -/// -/// The Task State Segment (TSS) is initialized here. -mod tss; diff --git a/modules/kernel/src/core_lib/hw/cpu/tss.rs b/modules/kernel/src/core_lib/hw/cpu/tss.rs deleted file mode 100644 index d17dc12..0000000 --- a/modules/kernel/src/core_lib/hw/cpu/tss.rs +++ /dev/null @@ -1,30 +0,0 @@ -use lazy_static::lazy_static; -use x86_64::{ - structures::tss::TaskStateSegment, - VirtAddr, -}; - -use super::gdt; - -lazy_static! { - pub static ref TSS: TaskStateSegment = { - let mut tss = TaskStateSegment::new(); - - // we now define the kernel stack to use when a double - // fault exception occurs to prevent fatal triple fault - // exceptions (e.g. due to hitting the guard page) - tss.interrupt_stack_table[gdt::DOUBLE_FAULT_IST_INDEX as usize] = { - const STACK_SIZE: usize = 4096 * 5; - // the mut is important, as the bootloader would otherwise - // map this to a read-only page - static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; - - // on x86_64 the stack grows downwards, therefore, the - // "start" is the lowest address and we return the "end" - // address which is the highest - let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); - stack_start + STACK_SIZE // = "end" - }; - tss - }; -} diff --git a/modules/kernel/src/core_lib/hw/init.rs b/modules/kernel/src/core_lib/hw/init.rs deleted file mode 100644 index f1973fc..0000000 --- a/modules/kernel/src/core_lib/hw/init.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::{ - cpu, - io, -}; - -/// # Hardware Initialization -/// -/// This method wraps all initialization in the `hw` (hardware) module. -/// -/// ## Caller -/// -/// It is called in the global `init()` function found in `crate::lib.rs`. -/// -/// ## Callees -/// -/// The following structures are initialized -/// -/// 1. Global Descriptor Table (GDT) -/// 2. Interrupt Descriptor Table (IDT) -/// 3. Process Interrupt Controllers (PIC) -pub fn run() -{ - cpu::gdt::init(); - cpu::idt::init(); - - io::interrupts::init_pics(); -} diff --git a/modules/kernel/src/core_lib/hw/io/interrupts.rs b/modules/kernel/src/core_lib/hw/io/interrupts.rs deleted file mode 100644 index 1fa2b9f..0000000 --- a/modules/kernel/src/core_lib/hw/io/interrupts.rs +++ /dev/null @@ -1,94 +0,0 @@ -use lazy_static::lazy_static; -use pic8259::ChainedPics; -use spin::Mutex; -use x86_64::{ - instructions, - structures::idt::InterruptStackFrame, -}; - -use crate::print; - -const PIC_1_OFFSET: u8 = 32; -const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; - -pub static PICS: Mutex = - Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); - -#[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum InterruptIndex -{ - Timer = PIC_1_OFFSET, - Keyboard, -} - -/// # Loading Programmable Interrupt Controllers -/// -/// Two Programmable Interrupt Controllers (PICs) are loaded here. -/// Furthermore, the CPU is instructed to listen for interrupts. -pub fn init_pics() -{ - unsafe { - PICS.lock().initialize(); - } - - instructions::interrupts::enable(); -} - -/// # End of Interrupt Signalization -/// -/// Every hardware (I/O) interrupt handler must issue an "end of interrupt" -/// (EOI) signal at the end to signal that we're finished with processing the -/// interrupt. -/// -/// This function provides a safe wrapper around the unsafe method. -fn notify_end_of_interrupt(interrupt_index: InterruptIndex) -{ - unsafe { - PICS.lock().notify_end_of_interrupt(interrupt_index as u8); - } -} - -/// # Hardware Interrupt - Timer -/// -/// This is the handler function for timer interrupts. -pub extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut InterruptStackFrame) -{ - print!("."); - notify_end_of_interrupt(InterruptIndex::Timer); -} - -/// # Hardware Interrupt - Keyboard -/// -/// This is the handler function which reacts to keyboard input. Currently, -/// every keystroke is printed directly on the screen. -pub extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut InterruptStackFrame) -{ - use pc_keyboard::{ - layouts::Us104Key, - DecodedKey, - HandleControl, - Keyboard, - ScancodeSet1, - }; - - lazy_static! { - static ref KEYBOARD: Mutex> = - Mutex::new(Keyboard::new(Us104Key, ScancodeSet1, HandleControl::Ignore)); - } - - let mut keyboard = KEYBOARD.lock(); - let mut port = instructions::port::Port::new(0x60); - let scancode: u8 = unsafe { port.read() }; - - if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { - if let Some(key) = keyboard.process_keyevent(key_event) { - match key { - DecodedKey::Unicode(character) => print!("{}", character), - DecodedKey::RawKey(key) => print!("{:?}", key), - } - } - } - - notify_end_of_interrupt(InterruptIndex::Keyboard); -} diff --git a/modules/kernel/src/core_lib/hw/io/mod.rs b/modules/kernel/src/core_lib/hw/io/mod.rs deleted file mode 100644 index d337459..0000000 --- a/modules/kernel/src/core_lib/hw/io/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -/// # A Serial Device Interface -/// -/// The `write` module makes heavy use of this module -/// as it `serial` provides the ability to write to -/// a serial device which is "forwarded" to `stdout` -/// via QEMU. -pub mod serial; -/// # Printing Information -/// -/// This module enabled the printing of information -/// to a screen. Here, a VGA buffer is used, which -/// is displayed in `stdout` in the terminal through -/// a serial device in `QEMU`. -/// -/// ## Future -/// -/// This module will be re-written soon. Therefore, the -/// documentation is not of good quality. -pub mod write; - -/// # Hardware Interrupts -/// -/// This module handles hardware interrupts and abstracts over the hardware -/// interrupt controller. -pub mod interrupts; diff --git a/modules/kernel/src/core_lib/hw/io/serial.rs b/modules/kernel/src/core_lib/hw/io/serial.rs deleted file mode 100644 index 702369f..0000000 --- a/modules/kernel/src/core_lib/hw/io/serial.rs +++ /dev/null @@ -1,42 +0,0 @@ -use uart_16550::SerialPort; -use spin::Mutex; - -lazy_static::lazy_static! { - pub static ref SERIAL1: Mutex = { - let mut serial_port = unsafe { SerialPort::new(0x3F8) }; - serial_port.init(); - Mutex::new(serial_port) - }; -} - -#[doc(hidden)] -pub fn _print_serial_args(args: core::fmt::Arguments) -{ - use core::fmt::Write; - x86_64::instructions::interrupts::without_interrupts(|| { - SERIAL1.lock() - .write_fmt(args) - .expect("Printing to serial failed"); - }); -} - -/// Prints to the host through the serial interface. -#[macro_export] -macro_rules! serial_print -{ - ($($arg:tt)*) => { - $crate::core_lib::hw::io::serial::_print_serial_args( - format_args!($($arg)*) - ); - }; -} - -/// Prints to the host through the serial interface, appending a newline. -#[macro_export] -macro_rules! serial_println -{ - () => ($crate::serial_print!("\n")); - ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( - concat!($fmt, "\n"), $($arg)*)); -} diff --git a/modules/kernel/src/core_lib/hw/io/write.rs b/modules/kernel/src/core_lib/hw/io/write.rs deleted file mode 100644 index b9d5370..0000000 --- a/modules/kernel/src/core_lib/hw/io/write.rs +++ /dev/null @@ -1,204 +0,0 @@ -use core::fmt; -use lazy_static::lazy_static; -use spin::Mutex; -use volatile::Volatile; - -use crate::core_lib::VERSION; - -const BUFFER_HEIGHT: usize = 25; -const BUFFER_WIDTH: usize = 80; - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum Color -{ - Black = 0, - Blue = 1, - Green = 2, - Cyan = 3, - Red = 4, - Magenta = 5, - Brown = 6, - LightGray = 7, - DarkGray = 8, - LightBlue = 9, - LightGreen = 10, - LightCyan = 11, - LightRed = 12, - Pink = 13, - Yellow = 14, - White = 15, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct ColorCode(u8); - -impl ColorCode -{ - const fn new(foreground: Color, background: Color) -> Self - { - Self((background as u8) << 4 | (foreground as u8)) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -struct ScreenChar -{ - ascii_character: u8, - color_code: ColorCode, -} - -#[repr(transparent)] -struct Buffer -{ - chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], -} - -pub struct Writer -{ - column_position: usize, - color_code: ColorCode, - buffer: &'static mut Buffer, -} - -impl Writer -{ - fn write_byte(&mut self, byte: u8) - { - match byte { - b'\n' => self.new_line(), - byte => { - if self.column_position >= BUFFER_WIDTH { - self.new_line(); - } - - let row = BUFFER_HEIGHT - 1; - let col = self.column_position; - - let color_code = self.color_code; - // self.buffer.chars[row][col].write(ScreenChar { - // ascii_character: byte, - // color_code, - // }); - self.column_position += 1; - }, - } - } - - fn write_string(&mut self, s: &str) - { - for byte in s.bytes() { - match byte { - // printable ASCII byte or newline - 0x20..=0x7E | b'\n' => self.write_byte(byte), - // not part of printable ASCII range - _ => self.write_byte(0xFE), - } - } - } - - fn new_line(&mut self) - { - // for row in 1..BUFFER_HEIGHT { - // for col in 0..BUFFER_WIDTH { - // let character = self.buffer.chars[row][col].read(); - // self.buffer.chars[row - 1][col].write(character); - // } - // } - self.clear_row(BUFFER_HEIGHT - 1); - self.column_position = 0; - } - - fn clear_row(&mut self, row: usize) - { - let blank = ScreenChar { - ascii_character: b' ', - color_code: self.color_code, - }; - // for col in 0..BUFFER_WIDTH { - // self.buffer.chars[row][col].write(blank); - // } - } -} - -impl fmt::Write for Writer -{ - fn write_str(&mut self, s: &str) -> fmt::Result - { - self.write_string(s); - Ok(()) - } -} - -lazy_static! { - pub static ref WRITER: Mutex = Mutex::new(Writer { - column_position: 0, - color_code: ColorCode::new(Color::Yellow, Color::Black), - buffer: unsafe { &mut *(0xB8000 as *mut Buffer) }, - }); -} - -pub fn print_init() -{ - crate::println!("unCore\n\nversion :: {}", VERSION); -} - -#[doc(hidden)] -pub fn _print_write_args(args: fmt::Arguments) -{ - use core::fmt::Write; - x86_64::instructions::interrupts::without_interrupts(|| { - WRITER.lock().write_fmt(args).unwrap(); - }); -} - -/// Prints a line of text on the screen -#[macro_export] -macro_rules! print { - ($($arg:tt)*) => ( - $crate::core_lib::hw::io::write::_print_write_args( - format_args!($($arg)*) - ) - ); -} - -/// Prints text on the screen appending a newline. -#[macro_export] -macro_rules! println { - () => ($crate::print!("\n")); - ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); -} - -#[test_case] -fn println_does_not_panic() -{ - println!("test_println_simple output"); -} - -#[test_case] -fn println_does_not_panic_complex() -{ - for _ in 0..200 { - println!("test_println_many output"); - } -} - -#[test_case] -fn test_println_output() -{ - use core::fmt::Write; - - let s = "Some tests string that fits on a single line"; - - x86_64::instructions::interrupts::without_interrupts(|| { - let mut writer = WRITER.lock(); - writeln!(writer, "\n{}", s).expect("writeln failed"); - // for (i, c) in s.chars().enumerate() { - // let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); - // assert_eq!(char::from(screen_char.ascii_character), c); - // } - }); -} diff --git a/modules/kernel/src/core_lib/hw/mod.rs b/modules/kernel/src/core_lib/hw/mod.rs deleted file mode 100644 index 9ae8ba5..0000000 --- a/modules/kernel/src/core_lib/hw/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// # Central Processing Unit Interaction -/// -/// This module handles CPU interaction. This includes CPU exceptions, fault -/// handling or setup of descriptor tables. -pub mod cpu; - -/// # Hardware Initialization -/// -/// This module's sole propose is to provide the `run()` function -/// as the main initialization wrapper for all hardware components. -pub mod init; - -/// # I/O Device Management -/// -/// This modules handles input and output devices and their interrupts. -/// -/// ## Macros -/// -/// The macros for `print!()` and `println!()` are provided in `io::write`. -pub mod io; diff --git a/modules/kernel/src/core_lib/mem/mod.rs b/modules/kernel/src/core_lib/mem/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/modules/kernel/src/core_lib/mem/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/kernel/src/core_lib/misc/helper.rs b/modules/kernel/src/core_lib/misc/helper.rs deleted file mode 100644 index 1fbda5c..0000000 --- a/modules/kernel/src/core_lib/misc/helper.rs +++ /dev/null @@ -1,46 +0,0 @@ -/// # Providing `-> !` -/// -/// This function is just a nice abstraction of -/// the call to `loop {...}`, making it absolutely -/// clear what the intend of calling this function -/// is, using its name. -/// -/// We use the `hlt` instruction to "halt" the CPU to not burn through CPU -/// time, as a call to `loop {}` would do. -pub fn __never_return() -> ! -{ - loop { - x86_64::instructions::hlt(); - } -} - -/// # QEMU -/// -/// This module provides all necessary interaction -/// for QEMU. -pub mod qemu -{ - /// Abstracts over the exit code a number, - /// proving a clear indication of success - /// or failure. - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - #[repr(u32)] - pub enum ExitCode - { - Success = 0x10, - Failed = 0x11, - } - - /// Looks up `[package.metadata.bootimage]` in `Cargo.toml` - /// to use the `isa-debug-exit` device, located on port - /// `0xf4` with a 4 byte size. - pub fn _exit(exit_code: ExitCode) - { - use x86_64::instructions::port::Port; - - unsafe { - let mut port = Port::new(0xF4); - port.write(exit_code as u32); - } - } -} diff --git a/modules/kernel/src/core_lib/misc/mod.rs b/modules/kernel/src/core_lib/misc/mod.rs deleted file mode 100644 index ab309d4..0000000 --- a/modules/kernel/src/core_lib/misc/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -/// # Miscellaneous Helpers -/// -/// This module provides various helper functions. -pub mod helper; -/// # Provides the API for Panicking -/// -/// This module provides the implementation -/// for the panic macro and panic behavior. -pub mod panic; diff --git a/modules/kernel/src/core_lib/misc/panic.rs b/modules/kernel/src/core_lib/misc/panic.rs deleted file mode 100644 index 5d5dc78..0000000 --- a/modules/kernel/src/core_lib/misc/panic.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::core_lib::misc::helper::qemu; -use core::panic::PanicInfo; -use crate::core_lib::misc::helper; - -/// # Panic Handler when not Running Tests -/// -/// This function is marked for conditional compilation, and is used -/// when running the binary natively, i.e. not the tests. -#[cfg(not(test))] -pub fn panic(info: &PanicInfo) -> ! -{ - crate::println!("\n{{{{ PANIC }}}}\n\n{}\n\n", info); - - helper::__never_return() -} - -/// # Panic Handler when Running Tests -/// -/// This function is marked for conditional compilation, and is used -/// when running the custom tests suite. -#[cfg(test)] -pub fn panic(info: &PanicInfo) -> ! { test_panic_handler(info) } - -/// # Publicly Available Panic Handler -/// -/// This function makes sure we have a publicly -/// available panic handler function which we could -/// call from integration tests. -pub fn test_panic_handler(info: &PanicInfo) -> ! -{ - crate::serial_println!("[Failed]\n\n{{{{ PANIC }}}}\n\n{}\n\n", info); - qemu::_exit(qemu::ExitCode::Failed); - - helper::__never_return() -} diff --git a/modules/kernel/src/core_lib/mod.rs b/modules/kernel/src/core_lib/mod.rs deleted file mode 100644 index 364cf61..0000000 --- a/modules/kernel/src/core_lib/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -/// # `SemVer` Version -/// -/// This is the semantic versioning version string with added release -/// candidate, maturity grade and stability identifier. -const VERSION: &str = "v0.1.0-rc1 alpha1 unstable"; - -/// # The Hardware Interface -/// -/// The `hw` module provides support for and abstraction over hardware -/// access. This mainly includes interaction with the Central Processing Unit -/// (CPU) and I/O devices. -pub mod hw; - -/// # Memory Management -/// -/// This module handles all memory management. This includes paging and -/// allocation of all sorts, for example on the heap. -pub mod mem; - -/// # Other Functionality -/// -/// All functions and methods that are rather generic and serve general -/// purposes are placed in `misc`. The high-level implementation for -/// panicking is found here as well as various helper functions. -pub mod misc; - -/// # Providing Support for Tests -/// -/// This module provides the implementation to run tests. This includes -/// unit-tests as well as integration tests. -pub mod tests; diff --git a/modules/kernel/src/core_lib/tests/mod.rs b/modules/kernel/src/core_lib/tests/mod.rs deleted file mode 100644 index cf0d5e6..0000000 --- a/modules/kernel/src/core_lib/tests/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// # Providing Test Infrastructure -/// -/// As testing usually relies on the standard -/// library, we will need to write our own simple -/// testing framework. -pub mod testing; diff --git a/modules/kernel/src/core_lib/tests/testing.rs b/modules/kernel/src/core_lib/tests/testing.rs deleted file mode 100644 index 86b5f02..0000000 --- a/modules/kernel/src/core_lib/tests/testing.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::{ - serial_print, - serial_println, -}; -use crate::core_lib::misc::helper::qemu; - -/// # A Simple Test Runner Implementation -/// -/// This function is registered as the tests -/// runner when executing Cargo test's unit tests. -/// -/// It will just execute all functions marked with `#[test_case]` -/// one by one. -pub fn test_runner(tests: &[&dyn Testable]) -{ - serial_println!("Running {} tests.\n", tests.len()); - - for test in tests { - test.run(); - } - - qemu::_exit(qemu::ExitCode::Success); -} - -/// # Makes Testing Streamlined -/// -/// This trait provides the tests runner with the ability to -/// `.run` tests. This is done for all functions in the -/// `impl` block, so they can be "parsed" to reduce boilerplate -/// code. -pub trait Testable -{ - fn run(&self); -} - -impl Testable for T -where - T: Fn(), -{ - fn run(&self) - { - use core::any::type_name; - - serial_print!("{} ", type_name::()); - self(); - serial_println!("[ok]"); - } -} - -/// This tests is just here for sanity's sake to make -/// sure tests behave correctly at the most basic level. -#[test_case] -fn trivial_assertion() -{ - const ONE: u8 = 1; - assert_eq!(1, ONE); - assert_eq!(ONE, 1); -} diff --git a/modules/kernel/src/lib.rs b/modules/kernel/src/lib.rs deleted file mode 100644 index 5d939e4..0000000 --- a/modules/kernel/src/lib.rs +++ /dev/null @@ -1,124 +0,0 @@ -#![no_std] -#![cfg_attr(test,no_main)] -#![deny(clippy::all)] -#![deny(clippy::nursery)] -#![deny(clippy::pedantic)] -#![feature(abi_x86_interrupt)] -#![feature(custom_test_frameworks)] -#![test_runner(crate::core_lib::tests::testing::test_runner)] -#![reexport_test_harness_main = "__start_tests"] - -/// # The Library -/// -/// This file provides the "entrypoint" for the main binary, i.e. the kernel, -/// as well as functions for integration tests. -/// -/// ## Kernel Parameters -/// -/// As this is an absolutely freestanding project, -/// we cannot rely on the standard-library, and we -/// will need to use our own entrypoint, not `main()`. -/// -/// Therefore, -/// -/// ``` RUST -/// #![no_std] -/// #![cfg_attr(tests, no_main)] -/// ``` -/// -/// are used. The second attribute indicates the use of `no_main` when we are -/// executing tests. -/// -/// ## Linting Targets -/// -/// A very strict set of rules is employed to guarantee -/// clear, robust and idiomatic code. -/// -/// Therefore, -/// -/// ``` RUST -/// #![deny(clippy::all)] -/// #![deny(clippy::nursery)] -/// #![deny(clippy::pedantic)] -/// ``` -/// -/// are used. -/// -/// ## Custom Tests -/// -/// As we cannot use the standard-library, we will -/// need to use our own tests framework. -/// -/// Therefore, -/// -/// ``` RUST -/// #![feature(custom_test_frameworks)] -/// #![test_runner(core_lib::testing::test_runner)] -/// #![reexport_test_harness_main = "__start_tests"] -/// ``` -/// -/// are used. -/// -/// ## Cargo Auto-Detection -/// -/// Cargo's auto-detection of library files is turned on. -/// -/// Therefore, `src/lib.rs` is automatically detected by Cargo -/// as a (freestanding) library. We need to define some -/// code segments twice, here as well as an in `src/main.rs` as this file is -/// tested by Cargo separately. -/// -/// This file can then be used in integration tests as well. -/// -/// ## Other Features -/// -/// Since the `x86-interrupt` calling convention is still unstable, -/// we use -/// -/// ``` RUST -/// #![feature(abi_x86_interrupt)] -/// ``` -/// -/// to use it nevertheless. - -/// # The Core Library Path -/// -/// This module has been created to give the kernel source -/// code a well-defined structure and layout. The `core_lib` module -/// is used as the child of the `src/lib.rs` "crate", not of `src/main.rs`. -/// This is important, and we are not allowed to mix them up. -pub mod core_lib; - -/// # Entrypoint -/// -/// The `_start` function is the entrypoint which is directly "called" -/// after booting. The bootloader will set up a stack and call this -/// function. -#[cfg(test)] -#[no_mangle] -pub extern "C" fn _start() -> ! -{ - init(); - __start_tests(); - core_lib::misc::helper::__never_return() -} - -/// # Panic Handler -/// -/// This function uses a conditionally compiled function -/// depending on whether running the kernel or the tests -/// suite. -#[cfg(test)] -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { core_lib::misc::panic::panic(info) } - -/// # Global Initialization -/// -/// This function takes care of initialization of registers, -/// global state and values. -pub fn init() -{ - core_lib::hw::io::write::print_init(); - - core_lib::hw::init::run(); -} diff --git a/modules/kernel/src/main.rs b/modules/kernel/src/main.rs deleted file mode 100644 index b46dac0..0000000 --- a/modules/kernel/src/main.rs +++ /dev/null @@ -1,108 +0,0 @@ -#![no_std] -#![no_main] -#![deny(clippy::all)] -#![deny(clippy::nursery)] -#![deny(clippy::pedantic)] -#![feature(custom_test_frameworks)] -#![test_runner(kernel::core_lib::tests::testing::test_runner)] -#![reexport_test_harness_main = "__start_tests"] - -/// # Important Information -/// -/// ## Kernel Parameters -/// -/// As this is an absolutely freestanding project, -/// we cannot rely on the standard-library, and we -/// will need to use our own entrypoint, not `main()`. -/// -/// Therefore, -/// -/// ``` RUST -/// #![no_std] -/// #![no_main] -/// ``` -/// -/// are used. -/// -/// ## Linting Targets -/// -/// A very strict set of rules is employed to guarantee -/// clear, robust and idiomatic code. -/// -/// Therefore, -/// -/// ``` RUST -/// #![deny(clippy::all)] -/// #![deny(clippy::nursery)] -/// #![deny(clippy::pedantic)] -/// ``` -/// -/// are used. -/// -/// ## Custom Tests -/// -/// As we cannot use the standard-library, we will -/// need to use our own tests framework. -/// -/// Therefore, -/// -/// ``` RUST -/// #![feature(custom_test_frameworks)] -/// #![test_runner(kernel::core_lib::tests::test_runner)] -/// #![reexport_test_harness_main = "__start_tests"] -/// ``` -/// -/// are used. -/// -/// ## Naming Convention -/// -/// The Rust naming convention found under -/// -/// is strictly adhered to. The only exception are functions -/// which serve as a helper, such as `__never_return`. These -/// are prefixed with two underscores. - -/// # Imports -/// -/// The `kernel::core_lib` is used here explicitly with the -/// `use` statement, and not with the `mod` statement. As -/// `kernel::core_lib` is already used in `lib.rs`, we do not -/// want to re-import it here and possibly confuse Cargo. -/// -/// The only exceptions so far is the `init()` function called -/// at the beginning of `_start`. It is called vi a`kernel::init()` -/// which is perfectly fine. -/// -/// ## Macros -/// -/// We will need to re-import all needed macros, as per definition -/// they reside in `crate`, which to be exact is `lib.rs`'s root -/// and **not** `main.rs`'s root. -/// -/// Make sure to **always** use `core_lib::` instead of `crate::lib::` or -/// `lib::` or something else. -use kernel::core_lib; - -/// # Entrypoint -/// -/// The `_start` function is the entrypoint which is directly "called" -/// after booting. The bootloader will set up a stack and call this -/// function. -#[no_mangle] -pub extern "C" fn _start() -> ! -{ - kernel::init(); - - #[cfg(test)] - __start_tests(); - - core_lib::misc::helper::__never_return() -} - -/// # Panic Handler -/// -/// This function uses a conditionally compiled function -/// depending on whether running the kernel or the tests -/// suite. -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { core_lib::misc::panic::panic(info) } diff --git a/modules/memory/Cargo.toml b/modules/memory/Cargo.toml deleted file mode 100644 index af69a25..0000000 --- a/modules/memory/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "memory" -version = "0.1.0" -edition = "2021" - -workspace = "../" - -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel Hardware Abstraction Layer [core]" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = [ - "operating-system", - "no-std", - "memory", - "management" -] - -categories = [ - "no-std", - "config", -] - -publish = false - -[dependencies] diff --git a/modules/memory/src/lib.rs b/modules/memory/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/memory/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/modules/processes/Cargo.toml b/modules/processes/Cargo.toml deleted file mode 100644 index 92a3199..0000000 --- a/modules/processes/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "processes" -version = "0.1.0" -edition = "2021" - -workspace = "../" - -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel Process Abstractions [core]" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = [ - "operating-system", - "no-std", - "processes", - "threads" -] - -categories = [ - "no-std", - "config", -] - -publish = false - -[dependencies] diff --git a/modules/processes/src/lib.rs b/modules/processes/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/processes/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/modules/rust-toolchain b/modules/rust-toolchain deleted file mode 100644 index 9999297..0000000 --- a/modules/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2021-12-02 diff --git a/modules/scheduling/Cargo.toml b/modules/scheduling/Cargo.toml deleted file mode 100644 index 493e5fe..0000000 --- a/modules/scheduling/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "scheduling" -version = "0.1.0" -edition = "2021" - -workspace = "../" - -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel Schudling Implementation [core]" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = [ - "operating-system", - "no-std", - "scheduling", - "processes" -] - -categories = [ - "no-std", - "config", -] - -publish = false - -[dependencies] diff --git a/modules/scheduling/src/lib.rs b/modules/scheduling/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/scheduling/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/modules/syscalls/Cargo.toml b/modules/syscalls/Cargo.toml deleted file mode 100644 index b0d04ba..0000000 --- a/modules/syscalls/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "syscalls" -version = "0.1.0" -edition = "2021" - -workspace = "../" - -authors = [ - "The unCORE Kernel Community", -] - -description = "The unCORE OS Kernel System Call Interface" -documentation = "../../documentation/" - -readme = "../../README.md" - -homepage = "https://uncore-kernel.org" -repository = "https://github.com/georglauterbach/uncore" - -license = "GPL-3.0" - -keywords = [ - "operating-system", - "no-std", - "systemcall", - "interface" -] - -categories = [ - "no-std", - "config", -] - -publish = false - -[dependencies] diff --git a/modules/syscalls/src/lib.rs b/modules/syscalls/src/lib.rs deleted file mode 100644 index 0c9ac1a..0000000 --- a/modules/syscalls/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -#![no_std] diff --git a/scripts/documentation.sh b/scripts/documentation.sh index a314ed6..8f510bc 100755 --- a/scripts/documentation.sh +++ b/scripts/documentation.sh @@ -10,7 +10,7 @@ __BASH_LOG_LEVEL=${__BASH_LOG_LEVEL:-inf} IMAGE_NAME='uncore/documentation:latest' DOCUMENTATION_DIRECTORY="${ROOT_DIRECTORY:-$(realpath -e -L .)}/documentation" -MKDOCS_MATERIAL_TAG='8.0.3' +MKDOCS_MATERIAL_TAG='8.1.2' MKDOCS_MATERIAL_IMAGE="docker.io/squidfunk/mkdocs-material:${MKDOCS_MATERIAL_TAG}" CRI='docker' @@ -24,13 +24,12 @@ CRI='docker' function build_documentation { - "${CRI}" run \ - --rm -it \ + + "${CRI}" run --rm \ + --name "build-documentation" \ --user "$(id -u):$(id -g)" \ -v "${DOCUMENTATION_DIRECTORY}:/docs" \ - "${MKDOCS_MATERIAL_IMAGE}" build \ - --config-file config.yml \ - --strict + "${MKDOCS_MATERIAL_IMAGE}" build --config-file config.yml --strict } function cleanup_documentation_files diff --git a/scripts/lib/errors.sh b/scripts/lib/errors.sh index ba83bc7..9b1f5b0 100755 --- a/scripts/lib/errors.sh +++ b/scripts/lib/errors.sh @@ -1,18 +1,23 @@ #! /bin/bash -# version 0.1.5-stable +# version 0.2.0 # sourced by shell scripts under scripts/ that can use /bin/bash # task provides error handlers set -euEo pipefail +shopt -s inherit_errexit -trap '__log_uerror "${FUNCNAME[0]:-?}" "${BASH_COMMAND:-?}" "${LINENO:-?}" "${?:-?}"' ERR +trap '__log_uerror "${FUNCNAME[0]:-none / global}" "${BASH_COMMAND:-?}" "${LINENO:-?}" "${?:-?}"' ERR function __log_uerror { - printf "\n––– \e[1m\e[31mUNCHECKED ERROR\e[0m\n%s\n%s\n%s\n%s\n\n" \ - " – script = ${SCRIPT:-${0}}" \ - " – function = ${1} / ${2}" \ - " – line = ${3}" \ - " – exit code = ${4}" >&2 + local MESSAGE + MESSAGE='\n––– \e[1m\e[31mUNCHECKED ERROR\e[0m\n' + MESSAGE+=" – script = ${SCRIPT:-${0}}\n" + MESSAGE+=" – function = ${1}\n" + MESSAGE+=" – command = ${2}\n" + MESSAGE+=" – line = ${3}\n" + MESSAGE+=" – exit code = ${4}\n" + + echo -e "${MESSAGE}" >&2 } diff --git a/scripts/tools.sh b/scripts/tools.sh index 06f4ac2..6b97874 100755 --- a/scripts/tools.sh +++ b/scripts/tools.sh @@ -9,12 +9,10 @@ # shellcheck source=./lib/logs.sh . scripts/lib/logs.sh -set +e - export SCRIPT='tools' __BASH_LOG_LEVEL=${__BASH_LOG_LEVEL:-inf} -TOOLCHAIN=${TOOLCHAIN:-$(tr -d '\n' < modules/rust-toolchain)} +TOOLCHAIN=${TOOLCHAIN:-$(tr -d '\n' < kernel/rust-toolchain)} # --> --> --> START @@ -50,7 +48,8 @@ function check_rust return 0 else notify 'inf' "Installing additonal packages with 'cargo'" - cargo --quiet install cargo-xbuild bootimage just + cargo --quiet install cargo-xbuild bootimage + cargo --quiet install just --version 0.10.4 fi notify 'suc' 'Your Rust installation is complete'