├── .cargo └── config.toml ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── site.yml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Poplar.toml ├── README.md ├── book ├── SUMMARY.md ├── book.toml ├── introduction.md ├── journal │ ├── future_ideas.md │ ├── index.md │ ├── pci_interrupt_routing.md │ ├── riscv.md │ ├── rust_std.md │ ├── rustc_target.md │ └── usb.md ├── kernel │ ├── debugging.md │ ├── index.md │ ├── kernel_objects.md │ ├── platforms │ │ ├── index.md │ │ └── mqpro.md │ ├── seed.md │ └── system_calls.md ├── message_passing.md ├── static │ └── d1_boot_procedure.svg └── userspace │ ├── capabilities.md │ ├── index.md │ └── platform_bus.md ├── bundled ├── device_tree │ └── d1_mangopi_mq_pro.dts └── ovmf │ ├── License.txt │ ├── code.fd │ ├── shell.efi │ └── vars.fd ├── ginkgo ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples │ ├── fibonacci.ginkgo │ ├── function.ginkgo │ └── while.ginkgo ├── src │ ├── lex.rs │ ├── lib.rs │ ├── main.rs │ ├── object.rs │ ├── parse.rs │ └── vm.rs └── test.ginkgo ├── kernel ├── Cargo.lock ├── Cargo.toml ├── kernel_riscv │ ├── Cargo.toml │ ├── build.rs │ ├── mq_pro.ld │ ├── rv64_virt.ld │ └── src │ │ ├── clocksource.rs │ │ ├── interrupts.rs │ │ ├── main.rs │ │ ├── pci.rs │ │ ├── serial.rs │ │ ├── task.rs │ │ ├── task.s │ │ └── trap.rs ├── kernel_x86_64 │ ├── Cargo.toml │ ├── build.rs │ ├── link.ld │ ├── src │ │ ├── clocksource.rs │ │ ├── interrupts.rs │ │ ├── kacpi.rs │ │ ├── logger.rs │ │ ├── main.rs │ │ ├── pci.rs │ │ ├── per_cpu.rs │ │ ├── syscall.s │ │ ├── task.rs │ │ ├── task.s │ │ └── topo.rs │ └── x86_64-kernel.json └── src │ ├── clocksource.rs │ ├── lib.rs │ ├── memory │ ├── mod.rs │ ├── pmm │ │ ├── buddy.rs │ │ └── mod.rs │ ├── slab_allocator.rs │ └── vmm │ │ └── mod.rs │ ├── object │ ├── address_space.rs │ ├── channel.rs │ ├── event.rs │ ├── interrupt.rs │ ├── memory_object.rs │ ├── mod.rs │ └── task.rs │ ├── pci.rs │ ├── scheduler.rs │ ├── syscall │ ├── mod.rs │ └── validation.rs │ └── tasklets │ ├── mod.rs │ └── queue.rs ├── lib ├── gfxconsole │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── fb.rs │ │ └── lib.rs ├── gpt │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── guid.rs │ │ └── lib.rs ├── hal │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── memory │ │ ├── frame.rs │ │ ├── mod.rs │ │ ├── page.rs │ │ ├── paging.rs │ │ ├── physical_address.rs │ │ └── virtual_address.rs ├── hal_riscv │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── hw │ │ ├── aplic.rs │ │ ├── csr.rs │ │ ├── imsic.rs │ │ ├── mod.rs │ │ ├── plic.rs │ │ └── uart16550.rs │ │ ├── lib.rs │ │ ├── paging.rs │ │ ├── platform_d1.rs │ │ └── platform_virt.rs ├── hal_x86_64 │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── hw │ │ ├── cpu.rs │ │ ├── gdt.rs │ │ ├── i8259_pic.rs │ │ ├── idt.rs │ │ ├── ioapic.rs │ │ ├── lapic.rs │ │ ├── mod.rs │ │ ├── port.rs │ │ ├── qemu.rs │ │ ├── registers.rs │ │ ├── serial.rs │ │ ├── tlb.rs │ │ └── tss.rs │ │ ├── kernel_map.rs │ │ ├── lib.rs │ │ └── paging.rs ├── mer │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── header.rs │ │ ├── lib.rs │ │ ├── note.rs │ │ ├── program.rs │ │ ├── section.rs │ │ └── symbol.rs ├── mulch │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── binary_pretty_print.rs │ │ ├── bipqueue.rs │ │ ├── bitmap.rs │ │ ├── downcast.rs │ │ ├── init_guard.rs │ │ ├── lib.rs │ │ ├── linker.rs │ │ ├── math.rs │ │ ├── pin.rs │ │ └── ranges.rs ├── picotoml │ ├── Cargo.lock │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ ├── README.md │ └── src │ │ ├── de.rs │ │ ├── error.rs │ │ ├── lexer.rs │ │ ├── lib.rs │ │ └── peeking.rs ├── poplar │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── channel.rs │ │ ├── ddk │ │ ├── dma.rs │ │ ├── mod.rs │ │ └── pci.rs │ │ ├── early_logger.rs │ │ ├── event.rs │ │ ├── interrupt.rs │ │ ├── lib.rs │ │ ├── manifest.rs │ │ ├── memory_object.rs │ │ ├── rt │ │ ├── mod.rs │ │ └── reactor.rs │ │ └── syscall │ │ ├── get_framebuffer.rs │ │ ├── mod.rs │ │ ├── pci.rs │ │ ├── raw_riscv.rs │ │ ├── raw_x86_64.rs │ │ └── result.rs ├── ptah │ ├── Cargo.lock │ ├── Cargo.toml │ ├── ptah_derive │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── de.rs │ │ │ ├── lib.rs │ │ │ └── ser.rs │ ├── src │ │ ├── de │ │ │ ├── heapless.rs │ │ │ ├── impls.rs │ │ │ └── mod.rs │ │ ├── lib.rs │ │ └── ser │ │ │ ├── heapless.rs │ │ │ ├── impls.rs │ │ │ └── mod.rs │ └── tests │ │ └── wellformed.rs ├── std │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ ├── rv64.ld │ ├── src │ │ ├── alloc.rs │ │ └── lib.rs │ └── x64.ld ├── usb │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── descriptor.rs │ │ ├── hid │ │ ├── mod.rs │ │ └── report.rs │ │ ├── lib.rs │ │ └── setup.rs ├── virtio │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── block.rs │ │ ├── gpu.rs │ │ ├── lib.rs │ │ ├── mmio.rs │ │ ├── pci.rs │ │ └── virtqueue.rs └── volatile │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs ├── rustfmt.toml ├── seed ├── Cargo.lock ├── Cargo.toml ├── d1_boot0 │ ├── Cargo.toml │ ├── build.rs │ ├── link.ld │ └── src │ │ └── main.rs ├── seed_riscv │ ├── Cargo.toml │ ├── build.rs │ ├── mq_pro.ld │ ├── rv64_virt.ld │ └── src │ │ ├── block │ │ ├── mod.rs │ │ └── virtio.rs │ │ ├── fs │ │ ├── mod.rs │ │ └── ramdisk.rs │ │ ├── image.rs │ │ ├── logger.rs │ │ ├── main.rs │ │ ├── memory.rs │ │ └── pci.rs ├── seed_uefi │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ ├── allocator.rs │ │ ├── image.rs │ │ ├── logger.rs │ │ └── main.rs └── src │ ├── boot_info.rs │ ├── lib.rs │ └── ramdisk.rs ├── site ├── _config.yml ├── _layouts │ └── default.html ├── index.md ├── logo.png ├── logo.svg ├── logo_dark.png ├── poplar_text.png └── style.css ├── tools ├── rust_gdb ├── tftp_serve │ ├── .cargo │ │ └── config │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ │ └── main.rs └── xtask │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ ├── cargo.rs │ ├── config.rs │ ├── dist.rs │ ├── doc.rs │ ├── flags.rs │ ├── image.rs │ ├── main.rs │ ├── ramdisk.rs │ ├── riscv │ ├── mod.rs │ └── qemu.rs │ ├── serial.rs │ └── x64 │ ├── mod.rs │ └── qemu.rs └── user ├── Cargo.lock ├── Cargo.toml ├── fb_console ├── Cargo.toml └── src │ └── main.rs ├── hello_world ├── Cargo.toml └── src │ └── main.rs ├── platform_bus ├── Cargo.toml └── src │ ├── input.rs │ ├── lib.rs │ ├── main.rs │ └── service │ ├── mod.rs │ └── pci.rs ├── service_host ├── Cargo.toml └── src │ ├── lib.rs │ └── main.rs ├── simple_fb ├── Cargo.toml └── src │ └── main.rs ├── usb_bus_ehci ├── Cargo.toml └── src │ ├── caps.rs │ ├── controller.rs │ ├── main.rs │ ├── queue.rs │ └── reg.rs ├── usb_bus_xhci ├── Cargo.toml └── src │ ├── caps.rs │ ├── main.rs │ ├── memory.rs │ ├── operational.rs │ └── trb.rs ├── usb_hid ├── Cargo.toml └── src │ └── main.rs ├── virtio_gpu ├── Cargo.toml └── src │ └── main.rs └── x86_64-poplar.json /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path tools/xtask/Cargo.toml --" 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: IsaacWoods 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - PLATFORM: x64 19 | - PLATFORM: rv64_virt 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | with: 26 | submodules: 'recursive' 27 | 28 | - name: Install dependencies 29 | run: | 30 | sudo apt-get update 31 | sudo apt-get install -y libudev-dev 32 | 33 | - name: Install Rust 34 | uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: nightly 37 | default: true 38 | profile: minimal 39 | components: rust-src 40 | 41 | - name: Build 42 | run: cargo xtask dist -p ${{ matrix.platform }} 43 | -------------------------------------------------------------------------------- /.github/workflows/site.yml: -------------------------------------------------------------------------------- 1 | name: Deploy site 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build_and_deploy: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: 'recursive' 15 | 16 | - name: Install Rust 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: nightly 20 | target: riscv64imac-unknown-none-elf 21 | default: true 22 | profile: minimal 23 | 24 | - name: Install dependencies 25 | run: | 26 | cargo install mdbook 27 | sudo apt-get update 28 | sudo apt-get install -y libudev-dev 29 | 30 | - name: Generate rustdoc documentation 31 | run: | 32 | cargo xtask doc pages/doc/ 33 | mv site/* pages/ 34 | 35 | - name: Generate book 36 | run: | 37 | cd book && mdbook build 38 | 39 | - name: Deploy to Github Pages 40 | uses: JamesIves/github-pages-deploy-action@releases/v3 41 | with: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | BRANCH: gh-pages 44 | FOLDER: pages 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target/ 2 | **/*.rs.bk 3 | 4 | *.log 5 | 6 | *.img 7 | *.o 8 | *.bin 9 | *.elf 10 | *.dtb 11 | 12 | seed_config_* 13 | 14 | # These are built on CI and should never be committed 15 | /doc_target/ 16 | /docs/ 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/acpi"] 2 | path = lib/acpi 3 | url = https://github.com/IsaacWoods/acpi 4 | [submodule "lib/pci_types"] 5 | path = lib/pci_types 6 | url = https://github.com/rust-osdev/pci_types 7 | [submodule "bundled/opensbi"] 8 | path = bundled/opensbi 9 | url = https://github.com/riscv-software-src/opensbi 10 | [submodule "lib/fdt"] 11 | path = lib/fdt 12 | url = https://github.com/IsaacWoods/fdt.git 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code Of Conduct 2 | Some OS development communities have proven themselves to be some of the most toxic in software development. I vehemently oppose that: 3 | * Developers being nice to each other somehow decreases the quality of software 4 | * Not having a code of conduct somehow magically leads to "software egalitarianism", and so one isn't needed 5 | * Software is developed in a vacuum and people's feelings never come into account 6 | 7 | Having thick skin should not be a requirement for contributing to open-source projects, and so this document sets out expectations for all discussion and engagement within the Poplar project. If you can not 8 | abide by these expectations, your contributions are not welcome, regardless of their technical worth. 9 | 10 | ### Expectations 11 | * Poplar aims to provide a friendly, safe, and welcoming environment for all, regardless of level of experience, gender identity and expression, age, sexual orientation, disability, personal appearance, body size, race, ethnicity, religion, nationality, or other similar characteristic. As a contributor, you must do your bit to maintain this environment. 12 | * Be kind and courteous 13 | * Rarely in OS development is there a right answer - respect that people may have differences in opinion, and that all implementations have trade-offs 14 | * Keep in mind that, even if you do not see the value in a contribution, that someone else has put time and effort into it 15 | * Keep critique technical 16 | 17 | ### Examples of unwelcome behaviour 18 | * Trolling, insulting or derogatory comments, and personal attacks 19 | * Public or private harassment 20 | * Publishing of others' private information, such as physical or electronic address, without explicit permission (any form of doxxing) 21 | * Spamming, flaming, baiting, or otherwise attention-seeking behaviour 22 | * Other conduct which could reasonably be considered inappropriate in a professional setting 23 | 24 | ### Enforcement 25 | Poplar's maintainers have the right to remove, edit, or reject comments, commits, wiki edits, issues, or any other contributions that do not align with this code of conduct, or to temporarily or permanently 26 | ban any contributor for breaking these expectations, or for any other behaviour that they deem inappropriate, threatening, offensive, or harmful. They also have the right to expand this code of conduct at 27 | any time, should the community grow - the new changes come into effect from the point the change is committed to `main`. 28 | 29 | ### Scope 30 | This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. 31 | -------------------------------------------------------------------------------- /Poplar.toml: -------------------------------------------------------------------------------- 1 | platform = "x64" 2 | 3 | [x64] 4 | release = true 5 | user_tasks = [ 6 | "service_host user/service_host", 7 | "platform_bus user/platform_bus", 8 | "usb_bus_ehci user/usb_bus_ehci", 9 | "usb_hid user/usb_hid", 10 | "virtio_gpu user/virtio_gpu", 11 | "fb_console user/fb_console", 12 | ] 13 | 14 | [rv64_virt] 15 | # The release profile is heavily recommended for software-emulated targets to achieve reasonable speeds 16 | release = true 17 | user_tasks = [ 18 | "service_host user/service_host", 19 | "hello_world user/hello_world", 20 | "platform_bus user/platform_bus", 21 | "usb_bus_ehci user/usb_bus_ehci", 22 | "usb_hid user/usb_hid", 23 | "virtio_gpu user/virtio_gpu", 24 | "fb_console user/fb_console", 25 | ] 26 | # Useful values: `virtio_*`, `usb_ehci_*`, `usb_packet_*`, `usb_*` 27 | qemu_trace = "" 28 | 29 | [mq_pro] 30 | release = true 31 | user_tasks = [ 32 | "hello_world user/hello_world", 33 | "platform_bus user/platform_bus", 34 | ] 35 | 36 | [uconsole] 37 | release = true 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Poplar 2 | [![Build](https://github.com/IsaacWoods/poplar/actions/workflows/build.yml/badge.svg)](https://github.com/IsaacWoods/poplar/actions/workflows/build.yml) 3 | [![License: MPL-2.0](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://opensource.org/licenses/MPL-2.0) 4 | 5 | Poplar is a microkernel and userspace written in Rust, exploring modern ideas. It is not a UNIX, and does not aim 6 | for compatibility with existing software. It currently supports x86_64 and RISC-V. 7 | 8 | The best way to learn about Poplar is to read [the book](https://isaacwoods.github.io/poplar/book/). 9 | [The website](https://isaacwoods.github.io/poplar) also hosts some other useful resources. 10 | 11 | ## Building and running 12 | **Operating systems tend to be complex to build and run. We've tried to make this as simple as we can, but if you 13 | encounter problems or have suggestions to make it easier, feel free to file an issue :)** 14 | 15 | ### Getting the source 16 | Firstly, clone the repository and fetch the submodules: 17 | ``` 18 | git clone https://github.com/IsaacWoods/poplar.git 19 | git submodule update --init --recursive 20 | ``` 21 | 22 | ### Things you'll need 23 | - A nightly Rust toolchain 24 | - The `rust-src` component (install with `rustup component add rust-src`) 25 | - A working QEMU installation (one that provides `qemu-system-{arch}`) 26 | 27 | ### Building 28 | This repository includes an [`xtask`-based](https://github.com/matklad/cargo-xtask) build tool to simplify building and running Poplar. 29 | The tool can be configured in `Poplar.toml` - this can, for example, be used to set whether to build using the 30 | release profile, and the architecture to build for. 31 | 32 | * Running `cargo xtask dist` will build a disk image 33 | * Running `cargo xtask qemu` will build a disk image, and then start emulating it in QEMU 34 | 35 | See `cargo xtask --help` for more information about how to invoke the build system. 36 | -------------------------------------------------------------------------------- /book/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Introduction](./introduction.md) 4 | 5 | - [The Kernel](./kernel/index.md) 6 | - [Kernel objects](./kernel/kernel_objects.md) 7 | - [System calls](./kernel/system_calls.md) 8 | - [Platforms](./kernel/platforms/index.md) 9 | - [MangoPi MQ-Pro](./kernel/platforms/mqpro.md) 10 | - [Seed](./kernel/seed.md) 11 | - [Debugging the kernel](./kernel/debugging.md) 12 | 13 | - [Message Passing](./message_passing.md) 14 | 15 | - [Userspace](./userspace/index.md) 16 | - [Capabilities](./userspace/capabilities.md) 17 | - [Platform Bus](./userspace/platform_bus.md) 18 | 19 | - [Journal](./journal/index.md) 20 | - [Building a `rustc` target for Poplar](./journal/rustc_target.md) 21 | - [USB](./journal/usb.md) 22 | - [RISC-V](./journal/riscv.md) 23 | - [PCI interrupt routing](./journal/pci_interrupt_routing.md) 24 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Isaac Woods"] 3 | multilingual = false 4 | src = "." 5 | title = "Poplar" 6 | 7 | [build] 8 | build-dir = "../pages/book/" 9 | create-missing = false 10 | -------------------------------------------------------------------------------- /book/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | [Poplar](https://github.com/IsaacWoods/poplar) is a general-purpose operating system built around a microkernel and userspace written in Rust. 3 | Drivers and core services that would ordinarily be implemented as part of a traditional monolithic kernel are instead implemented as unprivileged 4 | userspace programs. 5 | 6 | Poplar is not a UNIX, and does not aim for binary or source-level compability with existing programs. While this does slow development down significantly, 7 | it gives us the opportunity to design the interfaces we provide from scratch. 8 | 9 | Poplar is targeted to run on small computers (think a SoC with a ~1GiB of RAM and a few peripherals) and larger general purpose machines (think a many-core 10 | x86_64 "PC"). It is specifically not designed for small embedded systems - other projects are more suited to this space. Currently, Poplar supports relatively 11 | modern x86_64 and 64-bit RISC-V (RV64GC) machines. -------------------------------------------------------------------------------- /book/journal/index.md: -------------------------------------------------------------------------------- 1 | # Journal 2 | This is just a place I put notes that I make during development. 3 | -------------------------------------------------------------------------------- /book/journal/pci_interrupt_routing.md: -------------------------------------------------------------------------------- 1 | # PCI interrupt routing 2 | PCI interrupt routing is the process of working out which platform-specific interrupt will fire when 3 | a given PCI device issues an interrupt. In general, we use message-signalled interrupts (MSIs) where 4 | avaiable, and fall back to the legacy interrupt pins (INTA, INTB, INTC, INTD) on devices where they are 5 | not. 6 | 7 | ### Legacy interrupt pins 8 | - Each function header contains an interrupt pin field that can be `0` (no interrupts), or `1` 9 | through `4` for each pin. 10 | - Interrupts from devices that share a pin cannot be differentiated from each other without querying 11 | the devices themselves. For us, this means usermode drivers will need to be awoken before knowing 12 | their device has actually received an interrupt. 13 | 14 | The pin used by each device is not programmable - each device hardcodes it at time of manufacture. However, 15 | they can be remapped by any bridge between the device and host, so that the interrupt signal on the 16 | upstream side of the bridge differs to the device's reported interrupt pin. This was necessitated 17 | by manufacturers defaulting to using INTA - usage of the 4 available pins was unbalanced, so 18 | firmware improves performance by rebalancing them at the bridge. 19 | 20 | How the pins have been remapped is communicated to the operating system via a platform-specific 21 | mechanism. On modern x86 systems, this is through the `_PRT` method in the ACPI namespace 22 | (before ACPI, BIOS methods and later MP tables were used). On ARM and RISC-V, the device tree 23 | specifies this mapping through the `interrupt-map` property on the platform's interrupt controllers. 24 | -------------------------------------------------------------------------------- /book/journal/rust_std.md: -------------------------------------------------------------------------------- 1 | # Building a Rust `std` implementation for Poplar 2 | 3 | ### Steps 4 | * Added a submodule `std::sys::poplar` for Poplar's platform-specifics. Included it when `target_os = "poplar"`. 5 | * Add `target_os = poplar` to the list of platforms that empty `unix_ext` and `windows_ext` modules are created 6 | for, as I doubt it will compile properly. (I'm not sure if this is correct). 7 | * Added all the boilerplate from the `unsupported` platform to Poplar's new module. 8 | * In `std`'s, `build.rs`, add `target_os = "poplar"` to the list of platforms that have no special requirements. 9 | This means we're not a `restricted_std` platform. 10 | * In `sys_common/mod.rs`, add `target_os = "poplar"` to the list of platforms that don't include the standard `sys_common/net` module. 11 | 12 | ### Making an entry point 13 | The normal entry point of a Rust executable is actually provided by the system `libc` - the `crt0.o` file. We don't 14 | want a `libc` equivalent on Poplar, so we need to find a way of defining one in the Rust `std`. This is easier than 15 | it sounds - we just define a `#[no_mangle]` symbol called `_start` in Poplar's `sys` module, and it's linked into 16 | the right place. 17 | -------------------------------------------------------------------------------- /book/journal/usb.md: -------------------------------------------------------------------------------- 1 | # USB 2 | USB has a **host** that sends requests to **devices** (devices only respond when asked something). Some devices are 3 | **dual role devices (DRD)** (previously called On-The-Go (OTG) devices), and can dynamically negotiate whether 4 | they're the host or the device. 5 | 6 | Each device can have one ore more **interfaces**, which each have one or more **endpoints**. Each endpoint has a 7 | hardcoded direction (host-to-device or device-to-host). There are a few types of endpoint (the type is decided 8 | during interface configuration): 9 | - **Control endpoints** are for configuration and control requests 10 | - **Bulk endpoints** are for bulk transfers 11 | - **Isochronous endpoints** are for periodic transfers with a reserved bandwidth 12 | - **Int endpoints** are for transfers triggered by interruptions 13 | 14 | The interfaces and endpoints a device has are described by descriptors reported by the device during configuration. 15 | 16 | Every device has a special endpoint called `ep0`. It's an in+out control endpoint, and is used to configure the 17 | other endpoints. 18 | -------------------------------------------------------------------------------- /book/kernel/debugging.md: -------------------------------------------------------------------------------- 1 | # Debugging the kernel 2 | Kernels can be difficult to debug - this page tries to collect useful techniques for debugging kernels in general, 3 | and also any Poplar specific things that might be useful. 4 | 5 | ### Poplar specific: the breakpoint exception 6 | The breakpoint exception is useful for inspecting the contents of registers at specific points, such as in sections 7 | of assembly (where it's inconvenient to call into Rust, or to use a debugger because getting `global_asm!` to play 8 | nicely with GDB is a pain). 9 | 10 | Simply use the `int3` instruction: 11 | ``` 12 | ... 13 | 14 | mov rsp, [gs:0x10] 15 | int3 // Is my user stack pointer correct? 16 | sysretq 17 | ``` 18 | 19 | ### Building OVMF 20 | Building a debug build of OVMF isn't too hard (from the base of the `edk2` repo): 21 | ``` 22 | OvmfPkg/build.sh -a X64 23 | ``` 24 | 25 | By default, debug builds of OVMF will output debugging information on the ISA `debugcon`, which is actually 26 | probably nicer for our purposes than most builds, which pass `DEBUG_ON_SERIAL_PORT` during the build. To log the 27 | output to a file, you can pass `-debugcon file:ovmf_debug.log -global isa-debugcon.iobase=0x402` to QEMU. 28 | -------------------------------------------------------------------------------- /book/kernel/index.md: -------------------------------------------------------------------------------- 1 | # The Poplar Kernel 2 | At the core of Poplar is a small Rust microkernel. The kernel's job is to multiplex access to hardware resources 3 | (such as CPU time, memory, and peripherals) between competing bits of userspace code. 4 | 5 | Poplar is a microkernel because its drivers, pieces of code for managing the many different devices a computer 6 | may have, live in userspace, and are relatively unpriviledged compared to the kernel. This provides safety benefits 7 | over a monolithic kernel because a misbehaving or malicious driver (supplied by a hardware vendor, for example) has 8 | a much more limited scope of operation. The disadvantage is that microkernels tend to be slower than monolithic 9 | kernels, due to increased overheads of communication between the kernel and userspace. 10 | 11 | ## Kernel objects 12 | The Poplar microkernel is object-based - resources managed by the kernel are represented as discrete 'objects', and 13 | are interacted with from userspace via plain integers called 'handles'. Multiple handles referring to a single object 14 | can exist, and each possesses a set of permissions that dictate how the owning task can interact with the object. 15 | 16 | Kernel objects are used for: 17 | - Task creation and management (e.g. `AddressSpace` and `Task`) 18 | - Access to hardware resources (e.g. `MemoryObject`) 19 | - Message passing between tasks (e.g. `Channel`) 20 | - Signaling and waiting (e.g. `Event`) -------------------------------------------------------------------------------- /book/kernel/kernel_objects.md: -------------------------------------------------------------------------------- 1 | # Kernel Objects 2 | Kernel objects represent resources that are managed by the kernel, that userspace tasks may want to interact with 3 | through system calls. 4 | 5 | ### Handles 6 | Kernel objects are referenced from a userspace task using *handles*. From userspace, handles are opaque 32-bit 7 | integers, and are associated within the kernel to kernel objects through a per-task mapping. 8 | 9 | A handle of value `0` is never associated with a kernel object, and can act as a sentinel value - various system 10 | calls use this value for various meanings. 11 | 12 | Each handle is associated with a series of permissions that dictate what the owning userspace task can do with 13 | the corresponding object. Some permissions are relevant to all types of kernel object, while others have meanings 14 | specific to the type of object the handle is associated with. 15 | 16 | Permissions (TODO: expand on these): 17 | - Clone (create a new handle to the referenced kernel object) 18 | - Destroy (destroy the handle, destroying the object if no other handle references it) 19 | - Send (send the handle over a `Channel` to another task) 20 | 21 | ### Address Space 22 | TODO 23 | 24 | ### Memory Object 25 | TODO 26 | 27 | ### Task 28 | TODO 29 | 30 | ### Channel 31 | TODO 32 | 33 | ### Event 34 | TODO -------------------------------------------------------------------------------- /book/kernel/platforms/index.md: -------------------------------------------------------------------------------- 1 | # Platforms 2 | A platform is a build target for the kernel. In some cases, there is only one platform for an entire architecture because the hardware is relatively standardized (e.g. x86_64). 3 | Other times, hardware is different enough between platforms that it's easier to treat them as different targets (e.g. a headless ARM server that boots using UEFI, versus a 4 | Raspberry Pi). 5 | 6 | All supported platforms are enumerated in the table below - some have their own sections with more details, while others are just described below. The platform you want to 7 | build for is specified in your `Poplar.toml` configuration file, or with the `-p`/`--platform` flag to `xtask`. Some platforms also have custom `xtask` commands to, for 8 | example, flash a device with a built image. 9 | 10 | | Platform name | Arch | Description | 11 | |----------------------------------|---------|-----------------------------------------| 12 | | `x64` | x86_64 | Modern x86_64 platform. | 13 | | `rv64_virt` | RV64 | A virtual RISC-V QEMU platform. | 14 | | [`mq_pro`](./mqpro.md) | RV64 | The MangoPi MQ-Pro RISC-V platform. | 15 | 16 | ### Platform: `x64` 17 | The vast majority of x86_64 hardware is pretty similar, and so is treated as a single platform. It uses the `hal_x86_64` HAL. We assume that the platform: 18 | - Boots using UEFI (using `seed_uefi`) 19 | - Supports the APIC 20 | - Supports the `xsave` instruction 21 | 22 | ### Platform: `rv64_virt` 23 | This is a virtual RISC-V platform emulated by `qemu-system-riscv64`'s `virt` machine. It features: 24 | - A customizable number of emulated RV64 HARTs 25 | - Is booted via QEMU's `-kernel` option and OpenSBI 26 | - A Virtio block device with attached GPT 'disk' 27 | - Support for USB devices via EHCI 28 | 29 | Devices such as the EHCI USB controller are connected to a PCIe bus, and so we use the [Advanced Interrupt Architecture](https://github.com/riscv/riscv-aia) 30 | with MSIs to avoid the complexity of shared pin-based PCI interrupts. This is done by passing the `aia=aplic-imsic` machine option to QEMU. 31 | -------------------------------------------------------------------------------- /book/kernel/seed.md: -------------------------------------------------------------------------------- 1 | # Seed 2 | Seed is Poplar's bootloader ± pre-kernel. What it is required to do varies by platform, but generally it is 3 | responsible for bringing up the system, loading the kernel and initial tasks into memory, and preparing the 4 | environment for executing the kernel. 5 | 6 | ### `x86_64` 7 | On `x86_64`, Seed is an UEFI executable that utilises boot services to load the kernel and initial tasks. The Seed 8 | exectuable, the kernel, and other files are all held in the EFI System Partition (ESP) - a FAT filesystem present 9 | in all UEFI-booted systems. 10 | 11 | ### `riscv` 12 | On RiscV, Seed is more of a pre-kernel than a traditional bootloader. It is booted into by the system firmware, and 13 | then has its own set of drivers to load the kernel and other files from the correct filesystem, or elsewhere. 14 | 15 | **The boot mechanism has not yet been fully designed for RiscV, and also will heavily depend on the hardware 16 | target, as booting different platforms is much less standardised than on x86_64.** 17 | -------------------------------------------------------------------------------- /book/userspace/capabilities.md: -------------------------------------------------------------------------------- 1 | # Capabilities 2 | Capabilities describe what a task is allowed to do, and are encoded in its image. This allows users to audit the 3 | permissions of the tasks they run at a much higher granularity than user-based permissions, and also allow us to 4 | move parts of the kernel into discrete userspace tasks by creating specialised capabilities to allow access to 5 | sensitive resources (such as the raw framebuffer) to only select tasks. 6 | 7 | ### Encoding capabilities in the ELF image 8 | Capabilities are encoded in an entry of a `PT_NOTE` segment of the ELF image of a task. This entry will have an 9 | owner (sometimes referred to in documentation as the 'name') of `POPLAR` and a type of `0`. The descriptor will be 10 | an encoding of the capabilities as described by the 'Format' section. The descriptor must be padded such that the 11 | next descriptor is 4-byte aligned, and so a value of `0x00` is reserved to be used as padding. 12 | 13 | Initial images (tasks loaded by the bootloader before filesystem drivers are working) are limited to a capabilities 14 | encoding of 32 bytes (given the variable-length encoding, this does not equate to a fixed maximum number of 15 | capabilities). 16 | 17 | ### Format 18 | The capabilities format is variable-length - simple capabilities can be encoded as a single byte, while more 19 | complex / specific ones may need multiple bytes of prefix, and can also encode fixed-length data. 20 | 21 | ### Overview of capabilities 22 | This is an overview of all the capabilities the kernel supports: 23 | 24 | | First byte | Next byte(s) | Data | Arch specific? | Description | 25 | |---------------|---------------|-----------------------|-------------------|-----------------------------------------------------------------------| 26 | | `0x00` | - | - | - | No meaning - used to pad descriptor to required length (see above) | 27 | | `0x01` | | | No | `GetFramebuffer` | 28 | | `0x02` | | | No | `EarlyLogging` | 29 | | `0x03` | | | No | `ServiceProvider` | 30 | | `0x04` | | | No | `ServiceUser` | 31 | | `0x05` | - | - | No | `PciBusDriver` | 32 | -------------------------------------------------------------------------------- /book/userspace/index.md: -------------------------------------------------------------------------------- 1 | # Poplar's userspace 2 | Poplar supports running programs in userspace on supporting architectures. This offers increased protection 3 | and separation compared to running code in kernelspace - as a microkernel, Poplar tries to run as much code 4 | in userspace as possible. 5 | 6 | ## Building a program for Poplar's userspace 7 | Currently, the only officially supported language for writing userspace programs is Rust. 8 | 9 | #### Target 10 | Poplar provides custom `rustc` target files for userspace programs. These are found in the `user/{arch}_poplar.toml` files. 11 | 12 | #### Standard library 13 | Poplar provides a Rust crate, called `std`, which replaces Rust's standard library. We've done this for a few 14 | reasons: 15 | - We originally had targets and a `std` port in a fork of `rustc`. This proved difficult to maintain and required 16 | users to build a custom Rust fork and add it as a `rustup` toolchain. This is a high barrier of entry for 17 | anyone wanting to try Poplar out. 18 | - Poplar's ideal standard library probably won't end up looking very similar to other platform's, as there are 19 | significant ideological differences in how programs should interact with the OS. This is unfortunate from a 20 | porting point of view, but does allow us to design the platform interface from the group up. 21 | 22 | The name of the crate is slightly unfortunate, but is required, as `rustc` uses the name of the crate to decide 23 | where to import the prelude from. This significantly increases the ergonomics we can provide, so is worth the 24 | tradeoff. 25 | 26 | The `std` crate does a few important things that are worth understanding to reduce the 'magic' of Poplar's 27 | userspace: 28 | - It provides a linker script - the linker script for the correct target is shipped as part of the crate, and 29 | then the build script copies it into the Cargo `OUT_DIR`. It also passes a directive to `rustc` such that 30 | you can simply pass `-Tlink.ld` to link with the correct script. This is, for example, done using `RUSTFLAGS` 31 | by Poplar's `xtask`, but you can also pass it manually or with another method, depending on your build system. 32 | - It provides a prelude that should be very similar to the official `std` prelude 33 | - It provides an entry point to the executable that does required initialisation before passing control to Rust's 34 | `main` function -------------------------------------------------------------------------------- /bundled/ovmf/License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, TianoCore and contributors. All rights reserved. 2 | 3 | SPDX-License-Identifier: BSD-2-Clause-Patent 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | Subject to the terms and conditions of this license, each copyright holder 16 | and contributor hereby grants to those receiving rights under this license 17 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable 18 | (except for failure to satisfy the conditions of this license) patent 19 | license to make, have made, use, offer to sell, sell, import, and otherwise 20 | transfer this software, where such license applies only to those patent 21 | claims, already acquired or hereafter acquired, licensable by such copyright 22 | holder or contributor that are necessarily infringed by: 23 | 24 | (a) their Contribution(s) (the licensed copyrights of copyright holders and 25 | non-copyrightable additions of contributors, in source or binary form) 26 | alone; or 27 | 28 | (b) combination of their Contribution(s) with the work of authorship to 29 | which such Contribution(s) was added by such copyright holder or 30 | contributor, if, at the time the Contribution is added, such addition 31 | causes such combination to be necessarily infringed. The patent license 32 | shall not apply to any other combinations which include the 33 | Contribution. 34 | 35 | Except as expressly stated above, no rights or licenses from any copyright 36 | holder or contributor is granted under this license, whether expressly, by 37 | implication, estoppel or otherwise. 38 | 39 | DISCLAIMER 40 | 41 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 42 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 43 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 44 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 45 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 46 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 47 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 48 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 49 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 50 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 51 | POSSIBILITY OF SUCH DAMAGE. 52 | -------------------------------------------------------------------------------- /bundled/ovmf/code.fd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/bundled/ovmf/code.fd -------------------------------------------------------------------------------- /bundled/ovmf/shell.efi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/bundled/ovmf/shell.efi -------------------------------------------------------------------------------- /bundled/ovmf/vars.fd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/bundled/ovmf/vars.fd -------------------------------------------------------------------------------- /ginkgo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ginkgo" 3 | version = "0.0.0" 4 | authors = ["Isaac Woods"] 5 | description = "Small programming language, designed for use as Poplar's shell language" 6 | license = "MPL-2.0" 7 | edition = "2021" 8 | 9 | [lib] 10 | name = "ginkgo" 11 | path = "src/lib.rs" 12 | 13 | [[bin]] 14 | name = "ginkgo" 15 | path = "src/main.rs" 16 | required-features = ["rustyline"] 17 | 18 | [dependencies] 19 | std = { path = "../lib/std", optional = true } 20 | rustyline = { version = "14.0.0", features = ["derive"], optional = true } 21 | unicode-xid = "0.2.6" 22 | 23 | [features] 24 | default = ["rustyline"] 25 | poplar = ["std"] 26 | -------------------------------------------------------------------------------- /ginkgo/README.md: -------------------------------------------------------------------------------- 1 | # Ginkgo 2 | [![License: MPL-2.0](https://img.shields.io/badge/license-MPL--2.0-blue.svg)](https://opensource.org/licenses/MPL-2.0) 3 | 4 | Ginkgo is a small programming language, designed for general purpose scripting-esque use but also as the shell language for the Poplar operating 5 | system. 6 | -------------------------------------------------------------------------------- /ginkgo/examples/fibonacci.ginkgo: -------------------------------------------------------------------------------- 1 | fn fib(up_to) { 2 | let a = 0; 3 | let temp = 0; 4 | let b = 1; 5 | 6 | while a < up_to { 7 | print(a); 8 | temp = a; 9 | a = b; 10 | b = temp + b; 11 | } 12 | } 13 | 14 | fib(10000); 15 | -------------------------------------------------------------------------------- /ginkgo/examples/function.ginkgo: -------------------------------------------------------------------------------- 1 | fn foo(x, y) { 2 | let z = x + y; 3 | return z + 5; 4 | } 5 | 6 | fn bar(x) { 7 | let bar_var = foo(x, 4); 8 | return bar_var; 9 | } 10 | 11 | let x = bar(7); -------------------------------------------------------------------------------- /ginkgo/examples/while.ginkgo: -------------------------------------------------------------------------------- 1 | let i = 0; 2 | while i <= 10 { 3 | print(i); 4 | i = i + 1; 5 | } -------------------------------------------------------------------------------- /ginkgo/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature( 2 | let_chains, 3 | try_trait_v2, 4 | allocator_api, 5 | str_from_raw_parts, 6 | arbitrary_self_types_pointers, 7 | unsigned_signed_diff 8 | )] 9 | 10 | extern crate alloc; 11 | 12 | pub mod lex; 13 | pub mod object; 14 | pub mod parse; 15 | pub mod vm; 16 | -------------------------------------------------------------------------------- /ginkgo/src/main.rs: -------------------------------------------------------------------------------- 1 | use ginkgo::{ 2 | parse::Parser, 3 | vm::{Value, Vm}, 4 | }; 5 | use rustyline::{ 6 | error::ReadlineError, 7 | validate::MatchingBracketValidator, 8 | Completer, 9 | Editor, 10 | Helper, 11 | Highlighter, 12 | Hinter, 13 | Validator, 14 | }; 15 | use std::{io, path::Path}; 16 | 17 | fn main() -> io::Result<()> { 18 | /* 19 | * TODO: things to experiment with: `gc-arena` crate for garbage-collected values long-term + 20 | * `rustyline` for a decent REPL interface (either wholesale or being inspired by it (at least 21 | * for the Poplar version this will probs be required)) 22 | * - miette for fancy diagnostic reporting 23 | */ 24 | 25 | let mut vm = Vm::new(); 26 | 27 | vm.define_native_fn("print", |args| { 28 | assert!(args.len() == 1); 29 | let value = args.get(0).unwrap(); 30 | println!("PRINT: {:?}", value); 31 | Value::Unit 32 | }); 33 | 34 | // If we were passed a path, load and run that file 35 | if std::env::args().count() > 1 { 36 | let path = std::env::args().nth(1).unwrap(); 37 | println!("Executing script at {}", path); 38 | 39 | let source = std::fs::read_to_string(Path::new(&path)).unwrap(); 40 | let parser = Parser::new(&source); 41 | let chunk = parser.parse().unwrap(); 42 | vm.interpret(chunk); 43 | } 44 | 45 | let mut rl = Editor::new().unwrap(); 46 | rl.set_helper(Some(ReplHelper { validator: MatchingBracketValidator::new() })); 47 | // TODO: can load history here if wanted 48 | 49 | loop { 50 | let line = rl.readline("> "); 51 | match line { 52 | Ok(line) => { 53 | rl.add_history_entry(line.as_str()).unwrap(); 54 | 55 | let parser = Parser::new(&line); 56 | let chunk = parser.parse().unwrap(); 57 | 58 | vm.interpret(chunk); 59 | } 60 | Err(ReadlineError::Interrupted) => { 61 | println!("Ctrl-C"); 62 | break Ok(()); 63 | } 64 | Err(ReadlineError::Eof) => { 65 | println!("Ctrl-D"); 66 | break Ok(()); 67 | } 68 | Err(err) => { 69 | panic!("Error: {:?}", err); 70 | } 71 | } 72 | } 73 | } 74 | 75 | // TODO: not sure I love using a derive-macro here. We could just impl it for good. 76 | #[derive(Helper, Completer, Hinter, Highlighter, Validator)] 77 | pub struct ReplHelper { 78 | #[rustyline(Validator)] 79 | validator: MatchingBracketValidator, 80 | } 81 | -------------------------------------------------------------------------------- /ginkgo/test.ginkgo: -------------------------------------------------------------------------------- 1 | let x = 4; 2 | let foo = 0; 3 | 4 | if x > 8 { 5 | foo = 1; 6 | } else { 7 | foo = 2; 8 | } 9 | 10 | foo -------------------------------------------------------------------------------- /kernel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hal = { path = "../lib/hal" } 9 | seed = { path = "../seed" } 10 | mulch = { path = "../lib/mulch", features = ["has_alloc"] } 11 | cfg-if = "0.1" 12 | bitflags = "1" 13 | bit_field = "0.10" 14 | linked_list_allocator = "0.10.5" 15 | poplar = { path = "../lib/poplar", features = ["ddk"] } 16 | ptah = { path = "../lib/ptah" } 17 | pci_types = { path = "../lib/pci_types" } 18 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 19 | spinning_top = "0.3.0" 20 | maitake = { git = "https://github.com/hawkw/mycelium", features = ["alloc", "tracing-02"] } 21 | 22 | [workspace] 23 | members = ["kernel_x86_64", "kernel_riscv"] 24 | resolver = "2" 25 | 26 | [patch.crates-io] 27 | pci_types = { path = "../lib/pci_types" } 28 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel_riscv" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | hal = { path = "../../lib/hal" } 9 | hal_riscv = { path = "../../lib/hal_riscv" } 10 | kernel = { path = "../" } 11 | seed = { path = "../../seed" } 12 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 13 | tracing-core = { git = "https://github.com/tokio-rs/tracing", default-features = false } 14 | spinning_top = { version = "0.3" } 15 | mulch = { path = "../../lib/mulch/" } 16 | bit_field = "0.10.2" 17 | fdt = { path = "../../lib/fdt/", features = ["pretty-printing"] } 18 | sbi = "0.2.0" 19 | pci_types = { path = "../../lib/pci_types/" } 20 | maitake = { git = "https://github.com/hawkw/mycelium", features = ["alloc", "tracing-02"] } 21 | 22 | [features] 23 | platform_rv64_virt = ["hal_riscv/platform_rv64_virt"] 24 | platform_mq_pro = ["hal_riscv/platform_mq_pro"] 25 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=rv64_virt.ld"); 3 | println!("cargo:rerun-if-changed=mq_pro.ld"); 4 | } 5 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/mq_pro.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | OUTPUT_ARCH("riscv") 7 | OUTPUT_FORMAT("elf64-littleriscv") 8 | ENTRY(kentry) 9 | 10 | KERNEL_VMA = 0xffffffffc0000000; 11 | 12 | PHDRS { 13 | text PT_LOAD; 14 | rodata PT_LOAD FLAGS(4); 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS { 19 | . = KERNEL_VMA; 20 | 21 | .text : ALIGN(16) { 22 | *(.text.start) 23 | *(.text .text.*) 24 | . = ALIGN(4K); 25 | } :text 26 | 27 | .srodata : ALIGN(16) { 28 | *(.srodata .srodata.*) 29 | } :rodata 30 | 31 | .rodata : ALIGN(16) { 32 | *(.rodata .rodata.*) 33 | . = ALIGN(4K); 34 | } :rodata 35 | 36 | .sdata : ALIGN(16) { 37 | *(.sdata .sdata.*) 38 | } :data 39 | 40 | __global_pointer$ = .; 41 | PROVIDE(_bss_start = .); 42 | 43 | .sbss : ALIGN(16) { 44 | *(.sbss .sbss.*) 45 | } :data 46 | 47 | .bss : ALIGN(16) { 48 | *(.bss .bss.*) 49 | . = ALIGN(4K); 50 | 51 | _guard_page = .; 52 | . += 4K; 53 | PROVIDE(_stack_bottom = .); 54 | . += 64K; 55 | _stack_top = .; 56 | } :data 57 | 58 | PROVIDE(_bss_end = .); 59 | 60 | .data : ALIGN(16) { 61 | *(.data .data.*) 62 | . = ALIGN(4K); 63 | } :data 64 | 65 | /DISCARD/ : { *(.eh_frame_hdr .eh_frame) } 66 | } 67 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/rv64_virt.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | OUTPUT_ARCH("riscv") 7 | OUTPUT_FORMAT("elf64-littleriscv") 8 | ENTRY(kentry) 9 | 10 | KERNEL_VMA = 0xffffffff80000000; 11 | 12 | PHDRS { 13 | text PT_LOAD; 14 | rodata PT_LOAD FLAGS(4); 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS { 19 | . = KERNEL_VMA; 20 | 21 | .text : ALIGN(16) { 22 | *(.text.start) 23 | *(.text .text.*) 24 | . = ALIGN(4K); 25 | } :text 26 | 27 | .srodata : ALIGN(16) { 28 | *(.srodata .srodata.*) 29 | } :rodata 30 | 31 | .rodata : ALIGN(16) { 32 | *(.rodata .rodata.*) 33 | . = ALIGN(4K); 34 | } :rodata 35 | 36 | .sdata : ALIGN(16) { 37 | *(.sdata .sdata.*) 38 | } :data 39 | 40 | __global_pointer$ = .; 41 | PROVIDE(_bss_start = .); 42 | 43 | .sbss : ALIGN(16) { 44 | *(.sbss .sbss.*) 45 | } :data 46 | 47 | .bss : ALIGN(16) { 48 | *(.bss .bss.*) 49 | . = ALIGN(4K); 50 | 51 | _guard_page = .; 52 | . += 4K; 53 | PROVIDE(_stack_bottom = .); 54 | . += 64K; 55 | _stack_top = .; 56 | } :data 57 | 58 | PROVIDE(_bss_end = .); 59 | 60 | .data : ALIGN(16) { 61 | *(.data .data.*) 62 | . = ALIGN(4K); 63 | } :data 64 | 65 | /DISCARD/ : { *(.eh_frame_hdr .eh_frame) } 66 | } 67 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/src/clocksource.rs: -------------------------------------------------------------------------------- 1 | use core::cell::SyncUnsafeCell; 2 | use fdt::Fdt; 3 | use kernel::clocksource::FractionalFreq; 4 | use tracing::info; 5 | 6 | static NS_PER_TICK: SyncUnsafeCell = SyncUnsafeCell::new(FractionalFreq::zero()); 7 | 8 | pub struct Clocksource; 9 | 10 | impl Clocksource { 11 | pub fn initialize(device_tree: &Fdt) { 12 | let timebase_freq = device_tree 13 | .find_node("/cpus") 14 | .and_then(|cpus| cpus.property("timebase-frequency")) 15 | .and_then(|freq| freq.as_usize()) 16 | .unwrap(); 17 | info!("Timebase frequency: {:?} Hz", timebase_freq); 18 | 19 | // Find the inverse of the frequency and convert from Hz to nHz in one step. 20 | let ns_per_tick = FractionalFreq::new(1_000_000_000, timebase_freq as u64); 21 | unsafe { 22 | core::ptr::write(NS_PER_TICK.get() as *mut _, ns_per_tick); 23 | } 24 | } 25 | } 26 | 27 | impl kernel::clocksource::Clocksource for Clocksource { 28 | fn nanos_since_boot() -> u64 { 29 | let raw = hal_riscv::hw::csr::Time::read() as u64; 30 | let ns_per_tick = unsafe { *NS_PER_TICK.get() }; 31 | ns_per_tick * raw 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /kernel/kernel_riscv/src/task.s: -------------------------------------------------------------------------------- 1 | .global task_entry_trampoline 2 | task_entry_trampoline: 3 | // Clear SPP in `sstatus` - this makes `sret` return to U-mode 4 | li t6, 1<<8 5 | csrc sstatus, t6 6 | // Set SPIE in `sstatus` - this enables S-mode interrupts upon return to U-mode 7 | li t6, 1<<5 8 | csrs sstatus, t6 9 | 10 | // Load `sepc` to userspace's entry point 11 | csrw sepc, s0 12 | 13 | // Switch to the user's stack 14 | mv sp, s1 15 | 16 | sret 17 | 18 | .global do_drop_to_userspace 19 | do_drop_to_userspace: 20 | // Clear SPP in `sstatus` - this makes `sret` return to U-mode 21 | li t6, 1<<8 22 | csrc sstatus, t6 23 | // Set SPIE in `sstatus` - this enables S-mode interrupts upon return to U-mode 24 | li t6, 1<<5 25 | csrs sstatus, t6 26 | 27 | // Load registers from context-switch frame 28 | ld ra, 0(a0) 29 | ld sp, 8(a0) 30 | ld s0, 16(a0) 31 | ld s1, 24(a0) 32 | ld s2, 32(a0) 33 | ld s3, 40(a0) 34 | ld s4, 48(a0) 35 | ld s5, 56(a0) 36 | ld s6, 64(a0) 37 | ld s7, 72(a0) 38 | ld s8, 80(a0) 39 | ld s9, 88(a0) 40 | ld s10, 96(a0) 41 | ld s11, 104(a0) 42 | 43 | // TODO: load other registers with zero/known-values to avoid leaking stuff to userspace? 44 | 45 | // Load `sepc` to userspace's entry point 46 | csrw sepc, s0 47 | 48 | // Switch to the user's stack 49 | mv sp, s1 50 | 51 | sret 52 | 53 | .global do_context_switch 54 | do_context_switch: 55 | sd ra, 0(a0) 56 | sd sp, 8(a0) 57 | sd s0, 16(a0) 58 | sd s1, 24(a0) 59 | sd s2, 32(a0) 60 | sd s3, 40(a0) 61 | sd s4, 48(a0) 62 | sd s5, 56(a0) 63 | sd s6, 64(a0) 64 | sd s7, 72(a0) 65 | sd s8, 80(a0) 66 | sd s9, 88(a0) 67 | sd s10, 96(a0) 68 | sd s11, 104(a0) 69 | 70 | ld ra, 0(a1) 71 | ld sp, 8(a1) 72 | ld s0, 16(a1) 73 | ld s1, 24(a1) 74 | ld s2, 32(a1) 75 | ld s3, 40(a1) 76 | ld s4, 48(a1) 77 | ld s5, 56(a1) 78 | ld s6, 64(a1) 79 | ld s7, 72(a1) 80 | ld s8, 80(a1) 81 | ld s9, 88(a1) 82 | ld s10, 96(a1) 83 | ld s11, 104(a1) 84 | 85 | ret 86 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel_x86_64" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | kernel = { path = "../" } 9 | hal = { path = "../../lib/hal" } 10 | hal_x86_64 = { path = "../../lib/hal_x86_64" } 11 | seed = { path = "../../seed" } 12 | spinning_top = { version = "0.3" } 13 | log = "0.4" 14 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 15 | tracing-core = { git = "https://github.com/tokio-rs/tracing", default-features = false } 16 | bit_field = "0.10" 17 | acpi = { path = "../../lib/acpi" } 18 | mulch = { path = "../../lib/mulch" } 19 | gfxconsole = { path = "../../lib/gfxconsole" } 20 | pci_types = { path = "../../lib/pci_types" } 21 | maitake = { git = "https://github.com/hawkw/mycelium", features = ["alloc", "tracing-02"] } 22 | 23 | [features] 24 | qemu_exit = ["hal_x86_64/qemu"] 25 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=link.ld"); 3 | } 4 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/link.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | ENTRY(kentry) 7 | OUTPUT_FORMAT(elf64-x86-64) 8 | 9 | KERNEL_VMA = 0xffffffff80000000; 10 | 11 | PHDRS { 12 | text PT_LOAD; 13 | rodata PT_LOAD FLAGS(4); 14 | data PT_LOAD; 15 | } 16 | 17 | SECTIONS 18 | { 19 | . = KERNEL_VMA; 20 | _kernel_start = .; 21 | 22 | .text : 23 | { 24 | *(.text .text.*) 25 | . = ALIGN(4K); 26 | } :text 27 | 28 | .rodata : 29 | { 30 | *(.rodata .rodata.*) 31 | /* We don't need to align to 4K here because the rodata segment is aligned by .got below */ 32 | } :rodata 33 | 34 | .got : 35 | { 36 | *(.got) 37 | . = ALIGN(4K); 38 | } :rodata 39 | 40 | .data : 41 | { 42 | *(.data .data.*) 43 | /* We don't need to align to 4K here because it's done by .bss below */ 44 | } :data 45 | 46 | .bss : 47 | { 48 | *(.bss .bss.*) 49 | . = ALIGN(4K); 50 | 51 | /* 52 | * We reserve a guard page that should be unmapped by the bootloader. This will cause a 53 | * page-fault if accessed and so will detect a stack overflow. 54 | * TODO: maybe manually allocate a stack in seed_uefi for each CPU? 55 | */ 56 | _guard_page = .; 57 | . += 4K; 58 | _stack_bottom = .; 59 | . += 4M; 60 | _stack_top = .; 61 | /* No need to add more alignment here - it will already be page-aligned */ 62 | } :data 63 | 64 | _kernel_end = .; 65 | 66 | /DISCARD/ : { 67 | *(.comment*) 68 | *(.gcc_except_table*) 69 | *(.eh_frame*) 70 | *(.note*) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/src/clocksource.rs: -------------------------------------------------------------------------------- 1 | //! As Poplar only targets relatively modern x86 systems, we can always rely on the TSC to be a 2 | //! reasonable clocksource. All Intel CPUs since Nehalem and AMD since Bulldozer have supported 3 | //! invariant TSCs, which makes it a nice timer for wall-clock measurements. 4 | //! 5 | //! Where supported, we use information from `cpuid` to calculate the TSC frequency, either 6 | //! reported directly, or derived from the frequency of the ART (Always-Running Timer). Where this 7 | //! is not possible, we use another clock to calibrate the frequency of the TSC. 8 | 9 | use core::{arch::asm, cell::SyncUnsafeCell}; 10 | use hal_x86_64::hw::cpu::CpuInfo; 11 | use kernel::clocksource::{Clocksource, FractionalFreq}; 12 | use tracing::info; 13 | 14 | static TSC_NS_PER_TICK: SyncUnsafeCell = SyncUnsafeCell::new(FractionalFreq::zero()); 15 | 16 | pub struct TscClocksource; 17 | 18 | impl TscClocksource { 19 | pub fn init(cpu_info: &CpuInfo) { 20 | let tsc_freq = cpu_info 21 | .tsc_frequency() 22 | .expect("No TSC frequency in CPUID; need to implement alternative calibration") 23 | as u64; 24 | info!("TSC frequency: {} Hz", tsc_freq); 25 | 26 | // TODO: if cpuid doesn't report the TSC frequency, we'll need to calibrate it ourselves 27 | // with another timer 28 | 29 | // Find the inverse of the frequency and convert from Hz to nHz in one step. 30 | let tsc_ns_per_tick = FractionalFreq::new(1_000_000_000, tsc_freq); 31 | unsafe { 32 | core::ptr::write(TSC_NS_PER_TICK.get() as *mut _, tsc_ns_per_tick); 33 | } 34 | } 35 | } 36 | 37 | impl Clocksource for TscClocksource { 38 | fn nanos_since_boot() -> u64 { 39 | let raw = read_tsc(); 40 | let tsc_ns_per_tick = unsafe { *TSC_NS_PER_TICK.get() }; 41 | tsc_ns_per_tick * raw 42 | } 43 | } 44 | 45 | pub fn read_tsc() -> u64 { 46 | let (high, low): (u32, u32); 47 | unsafe { 48 | /* 49 | * The `rdtsc` instruction is not serializing, and may be speculatively executed before 50 | * preceding loads. This can, in theory, produce non-monotonically-increasing TSC values 51 | * when reads are performed between CPUs. To avoid having to consider whether this could be 52 | * a problem for us, we perform a load fence before to serialize all loads before the 53 | * `rdtsc`. 54 | * 55 | * This may not be necessary / not necessary all the time. 56 | * 57 | * TODO: `rdtscp` is also not serializing, but seems to do the equivalent of a load fence 58 | * before it by default. If we're happy to assume it exists, we could do `rdtscp` instead 59 | * here (this does clobber `ecx`)? 60 | */ 61 | asm!("lfence; rdtsc", 62 | out("eax") low, 63 | out("edx") high 64 | ); 65 | } 66 | (high as u64) << 32 | (low as u64) 67 | } 68 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/src/syscall.s: -------------------------------------------------------------------------------- 1 | .extern rust_syscall_entry 2 | 3 | /* 4 | * This is the code that is run when a task executes the `syscall` instruction. The `syscall` 5 | * instruction: 6 | * - has put the `rip` to return to in `rcx` 7 | * - has put the `rflags` to return with in `r11`, masked with `IA32_FMASK` 8 | * - does not save `rsp`. It is our responsibility to deal with the stack(s). 9 | * 10 | * Register summary: 11 | * rax => system call result 12 | * rbx - MUST BE PRESERVED 13 | * rcx - users' rip 14 | * rdx - b 15 | * rdi - system call number 16 | * rsi - a 17 | * rbp - MUST BE PRESERVED 18 | * r8 - d 19 | * r9 - e 20 | * r10 - c 21 | * r11 - users' rflags 22 | * r12 - MUST BE PRESERVED 23 | * r13 - MUST BE PRESERVED 24 | * r14 - MUST BE PRESERVED 25 | * r15 - MUST BE PRESERVED 26 | * 27 | * This is only different from the Sys-V ABI in that `c` is in `r10` and not `rcx` (because `rcx` is being 28 | * used by syscall). To call into the Rust function (as long as it is using the C ABI), we only need to 29 | * move that one parameter. 30 | */ 31 | .global syscall_handler 32 | syscall_handler: 33 | // Save the task's user rsp in the per-cpu data 34 | mov gs:0x10, rsp 35 | // Move to the task's kernel stack 36 | mov rsp, gs:0x8 37 | 38 | // We're now on the kernel stack, so interrupts are okay now 39 | sti 40 | 41 | // The `syscall` instruction puts important stuff in `rcx` and `r11`, so we save them and restore them 42 | // before calling `sysretq`. 43 | push rcx 44 | push r11 45 | 46 | // Save registers 47 | push rbp 48 | push rbx 49 | push rdx 50 | push rdi 51 | push rsi 52 | push r8 53 | push r9 54 | push r10 55 | push r12 56 | push r13 57 | push r14 58 | push r15 59 | 60 | // Move `c` into the right register. This is fine now because we've saved syscall's expected `rcx` on the 61 | // stack. 62 | mov rcx, r10 63 | 64 | // Call the Rust handler. From this point, `rax` contains the return value, so musn't be trashed! 65 | call rust_syscall_entry 66 | 67 | // Restore registers 68 | pop r15 69 | pop r14 70 | pop r13 71 | pop r12 72 | pop r10 73 | pop r9 74 | pop r8 75 | pop rsi 76 | pop rdi 77 | pop rdx 78 | pop rbx 79 | pop rbp 80 | 81 | // Restore state needed for `sysretq` 82 | pop r11 83 | pop rcx 84 | 85 | // Disable interrupts again while we mess around with the stacks 86 | cli 87 | 88 | // Save the kernel's stack back into per-cpu data 89 | mov gs:0x8, rsp 90 | // Move back to the task's user stack 91 | mov rsp, gs:0x10 92 | 93 | sysretq 94 | -------------------------------------------------------------------------------- /kernel/kernel_x86_64/x86_64-kernel.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "executables": true, 5 | "pre-link-args": { 6 | "ld.lld": [ 7 | "-Tkernel_x86_64/link.ld" 8 | ] 9 | }, 10 | "linker-flavor": "ld.lld", 11 | "linker": "rust-lld", 12 | "code-model": "kernel", 13 | "target-endian": "little", 14 | "target-pointer-width": "64", 15 | "target-c-int-width": "32", 16 | "arch": "x86_64", 17 | "os": "none", 18 | "disable-redzone": true, 19 | "features": "-mmx,-sse,-sse2,-sse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", 20 | "rustc-abi": "x86-softfloat", 21 | "panic-strategy": "abort" 22 | } 23 | -------------------------------------------------------------------------------- /kernel/src/clocksource.rs: -------------------------------------------------------------------------------- 1 | use core::ops; 2 | 3 | pub trait Clocksource { 4 | fn nanos_since_boot() -> u64; 5 | } 6 | 7 | /// `FractionalFreq` allows a fraction to be performed using integer maths of the form `x * (scalar 8 | /// / 2^shift)`. This is useful for doing `ticks -> ns` conversions for timers where you're given a 9 | /// frequency, or have calibrated it against another clock with known frequency. 10 | /// 11 | /// This methodology is also used by Linux's clocksource subsystem. 12 | #[derive(Clone, Copy)] 13 | pub struct FractionalFreq { 14 | scalar: u64, 15 | shift: u64, 16 | } 17 | 18 | const fn ceiling_log2(value: u64) -> u64 { 19 | 64 - value.leading_zeros() as u64 20 | } 21 | 22 | impl FractionalFreq { 23 | pub const fn zero() -> FractionalFreq { 24 | FractionalFreq { scalar: 0, shift: 0 } 25 | } 26 | 27 | pub const fn new(numerator: u64, denominator: u64) -> FractionalFreq { 28 | let shift = 63 - ceiling_log2(numerator); 29 | let scalar = (numerator << shift) / denominator; 30 | FractionalFreq { scalar, shift } 31 | } 32 | } 33 | 34 | impl ops::Mul for FractionalFreq { 35 | type Output = u64; 36 | 37 | fn mul(self, rhs: u64) -> Self::Output { 38 | (((rhs as u128) * (self.scalar as u128)) >> self.shift) as u64 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /kernel/src/memory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pmm; 2 | pub mod slab_allocator; 3 | pub mod vmm; 4 | 5 | pub use pmm::Pmm; 6 | pub use slab_allocator::SlabAllocator; 7 | pub use vmm::Vmm; 8 | -------------------------------------------------------------------------------- /kernel/src/memory/pmm/mod.rs: -------------------------------------------------------------------------------- 1 | mod buddy; 2 | 3 | use buddy::BuddyAllocator; 4 | use core::ops::Range; 5 | use hal::memory::{Frame, FrameAllocator, FrameSize, PAddr, Size4KiB}; 6 | use seed::boot_info::BootInfo; 7 | use spinning_top::Spinlock; 8 | 9 | /// The Physical Memory Manager (PMM) manages the system's supply of physical memory. It operates 10 | /// in **frames** of 4KiB, which matches the base frame size on the architectures we're interested 11 | /// in. 12 | pub struct Pmm { 13 | buddy: Spinlock, 14 | } 15 | 16 | impl Pmm { 17 | pub fn new(boot_info: &BootInfo) -> Pmm { 18 | let mut buddy_allocator = BuddyAllocator::new(); 19 | 20 | for entry in &boot_info.memory_map { 21 | if entry.typ == seed::boot_info::MemoryType::Conventional { 22 | buddy_allocator.free_range(entry.frame_range()); 23 | } 24 | } 25 | 26 | Pmm { buddy: Spinlock::new(buddy_allocator) } 27 | } 28 | 29 | /// Allocate `count` frames. 30 | pub fn alloc(&self, count: usize) -> PAddr { 31 | self.buddy.lock().alloc(count).expect("Failed to allocate requested physical memory") 32 | } 33 | 34 | /// Free `count` frames, starting at address `base`. 35 | pub fn free(&self, base: PAddr, count: usize) { 36 | self.buddy.lock().free(base, count) 37 | } 38 | } 39 | 40 | impl FrameAllocator for Pmm 41 | where 42 | S: FrameSize, 43 | { 44 | fn allocate_n(&self, n: usize) -> Range> { 45 | let start = 46 | self.buddy.lock().alloc(n * S::SIZE / Size4KiB::SIZE).expect("Failed to allocate physical memory!"); 47 | Frame::::starts_with(start)..(Frame::::starts_with(start) + n) 48 | } 49 | 50 | fn free_n(&self, start: Frame, num_frames: usize) { 51 | self.buddy.lock().free(start.start, num_frames * S::SIZE / Size4KiB::SIZE); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /kernel/src/memory/slab_allocator.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use hal::memory::VAddr; 3 | use mulch::{bitmap::BitmapSlice, math::ceiling_integer_divide}; 4 | 5 | pub struct SlabAllocator { 6 | pub bottom: VAddr, 7 | pub top: VAddr, 8 | slab_size: usize, 9 | bitmap: Vec, 10 | } 11 | 12 | impl SlabAllocator { 13 | pub fn new(bottom: VAddr, top: VAddr, slab_size: usize) -> SlabAllocator { 14 | let num_bytes_needed = ceiling_integer_divide(usize::from(top) - usize::from(bottom), slab_size) / 8; 15 | SlabAllocator { bottom, top, slab_size, bitmap: vec![0; num_bytes_needed] } 16 | } 17 | 18 | /// Try to allocate a slab out of the allocator. Returns `None` if no slabs are available. 19 | pub fn alloc(&mut self) -> Option { 20 | let index = self.bitmap.alloc(1)?; 21 | Some(self.bottom + index * self.slab_size) 22 | } 23 | 24 | pub fn free(&mut self, start: VAddr) { 25 | assert_eq!((usize::from(start) - usize::from(self.bottom)) % self.slab_size, 0); 26 | let index = (usize::from(start) - usize::from(self.bottom)) / self.slab_size; 27 | self.bitmap.free(index, 1); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /kernel/src/memory/vmm/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::Platform; 2 | 3 | use super::{Pmm, SlabAllocator}; 4 | use hal::memory::{FrameSize, PAddr, Size4KiB, VAddr}; 5 | use spinning_top::Spinlock; 6 | 7 | pub struct Vmm { 8 | kernel_stack_slots: Spinlock, 9 | kernel_stack_slot_size: usize, 10 | } 11 | 12 | impl Vmm { 13 | pub fn new(kernel_stacks_bottom: VAddr, kernel_stacks_top: VAddr, kernel_stack_slot_size: usize) -> Vmm { 14 | Vmm { 15 | kernel_stack_slots: Spinlock::new(SlabAllocator::new( 16 | kernel_stacks_bottom, 17 | kernel_stacks_top, 18 | kernel_stack_slot_size, 19 | )), 20 | kernel_stack_slot_size, 21 | } 22 | } 23 | 24 | pub fn alloc_kernel_stack

( 25 | &self, 26 | initial_size: usize, 27 | physical_memory_manager: &Pmm, 28 | kernel_page_table: &mut P::PageTable, 29 | ) -> Option 30 | where 31 | P: Platform, 32 | { 33 | use hal::memory::{Flags, PageTable}; 34 | 35 | let slot_bottom = self.kernel_stack_slots.lock().alloc()?; 36 | let top = slot_bottom + self.kernel_stack_slot_size - 1; 37 | let stack_bottom = top - initial_size + 1; 38 | 39 | let physical_start = physical_memory_manager.alloc(initial_size / Size4KiB::SIZE); 40 | // TODO: bring "master" kernel page tables into this struct? 41 | kernel_page_table 42 | .map_area( 43 | stack_bottom, 44 | physical_start, 45 | initial_size, 46 | Flags { writable: true, ..Default::default() }, 47 | physical_memory_manager, 48 | ) 49 | .unwrap(); 50 | 51 | Some(Stack { top, slot_bottom, stack_bottom, physical_start }) 52 | } 53 | } 54 | 55 | /// Represents a stack, either in kernel-space or user-space. Stacks are allocated in "slots" of fixed size, but 56 | /// only a subset of the slot may be mapped initially (to reduce physical memory usage). Stacks can't grow above 57 | /// the size of their slot. 58 | #[derive(Clone, Debug)] 59 | pub struct Stack { 60 | pub top: VAddr, 61 | pub slot_bottom: VAddr, 62 | pub stack_bottom: VAddr, 63 | 64 | pub physical_start: PAddr, 65 | } 66 | -------------------------------------------------------------------------------- /kernel/src/object/event.rs: -------------------------------------------------------------------------------- 1 | use super::{KernelObject, KernelObjectId, KernelObjectType}; 2 | use alloc::sync::Arc; 3 | use core::sync::atomic::{AtomicBool, Ordering}; 4 | 5 | #[derive(Debug)] 6 | pub struct Event { 7 | pub id: KernelObjectId, 8 | pub signalled: AtomicBool, 9 | } 10 | 11 | impl Event { 12 | pub fn new() -> Arc { 13 | Arc::new(Event { id: super::alloc_kernel_object_id(), signalled: AtomicBool::new(false) }) 14 | } 15 | 16 | pub fn signal(&self) { 17 | // TODO: ordering? 18 | self.signalled.store(true, Ordering::SeqCst); 19 | } 20 | 21 | pub fn clear(&self) { 22 | // TODO: ordering? 23 | self.signalled.store(false, Ordering::SeqCst); 24 | } 25 | } 26 | 27 | impl KernelObject for Event { 28 | fn id(&self) -> KernelObjectId { 29 | self.id 30 | } 31 | 32 | fn typ(&self) -> KernelObjectType { 33 | KernelObjectType::Event 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /kernel/src/object/interrupt.rs: -------------------------------------------------------------------------------- 1 | use super::{KernelObject, KernelObjectId, KernelObjectType}; 2 | use crate::Platform; 3 | use alloc::sync::Arc; 4 | use core::sync::atomic::{AtomicBool, Ordering}; 5 | 6 | #[derive(Debug)] 7 | pub struct Interrupt { 8 | pub id: KernelObjectId, 9 | pub triggered: AtomicBool, 10 | 11 | /// The vector that this interrupt is triggered by. The value of this is determined by the 12 | /// platform-specific interrupt layer, and is effectively opaque to the common kernel. 13 | pub rearm_irq: Option, 14 | } 15 | 16 | impl Interrupt { 17 | pub fn new(rearm_irq: Option) -> Arc { 18 | Arc::new(Interrupt { id: super::alloc_kernel_object_id(), triggered: AtomicBool::new(false), rearm_irq }) 19 | } 20 | 21 | pub fn trigger(&self) { 22 | // TODO: ordering? 23 | self.triggered.store(true, Ordering::SeqCst); 24 | } 25 | 26 | pub fn rearm

(&self) 27 | where 28 | P: Platform, 29 | { 30 | // TODO: ordering? 31 | self.triggered.store(false, Ordering::SeqCst); 32 | 33 | if let Some(irq) = self.rearm_irq { 34 | P::rearm_interrupt(irq); 35 | } 36 | } 37 | } 38 | 39 | impl KernelObject for Interrupt { 40 | fn id(&self) -> KernelObjectId { 41 | self.id 42 | } 43 | 44 | fn typ(&self) -> KernelObjectType { 45 | KernelObjectType::Interrupt 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /kernel/src/object/memory_object.rs: -------------------------------------------------------------------------------- 1 | use super::{alloc_kernel_object_id, KernelObject, KernelObjectId, KernelObjectType}; 2 | use alloc::{sync::Arc, vec::Vec}; 3 | use hal::memory::{Flags, PAddr}; 4 | use seed::boot_info::Segment; 5 | use spinning_top::Spinlock; 6 | 7 | #[derive(Debug)] 8 | pub struct MemoryObject { 9 | pub id: KernelObjectId, 10 | pub owner: KernelObjectId, 11 | pub inner: Spinlock, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct Inner { 16 | /// Size of this MemoryObject in bytes. 17 | pub size: usize, 18 | pub flags: Flags, 19 | pub backing: Vec<(PAddr, usize)>, 20 | } 21 | 22 | impl MemoryObject { 23 | pub fn new(owner: KernelObjectId, physical_address: PAddr, size: usize, flags: Flags) -> Arc { 24 | Arc::new(MemoryObject { 25 | id: alloc_kernel_object_id(), 26 | owner, 27 | inner: Spinlock::new(Inner { size, flags, backing: vec![(physical_address, size)] }), 28 | }) 29 | } 30 | 31 | pub fn from_boot_info(owner: KernelObjectId, segment: &Segment) -> Arc { 32 | Arc::new(MemoryObject { 33 | id: alloc_kernel_object_id(), 34 | owner, 35 | inner: Spinlock::new(Inner { 36 | size: segment.size, 37 | flags: segment.flags, 38 | backing: vec![(segment.physical_address, segment.size)], 39 | }), 40 | }) 41 | } 42 | 43 | /// Extend this `MemoryObject` by `extend_by` bytes. The new portion of the object is backed 44 | /// by physical memory starting at `new_backing`. 45 | /// 46 | /// ### Note 47 | /// Note that this does not map the new portion of the object into address spaces that this 48 | /// memory object is already mapped into. 49 | pub unsafe fn extend(&self, extend_by: usize, new_backing: PAddr) { 50 | assert!(extend_by > 0); 51 | let mut inner = self.inner.lock(); 52 | inner.size += extend_by; 53 | inner.backing.push((new_backing, extend_by)); 54 | } 55 | 56 | pub fn size(&self) -> usize { 57 | self.inner.lock().size 58 | } 59 | 60 | pub fn flags(&self) -> Flags { 61 | self.inner.lock().flags 62 | } 63 | } 64 | 65 | impl KernelObject for MemoryObject { 66 | fn id(&self) -> KernelObjectId { 67 | self.id 68 | } 69 | 70 | fn typ(&self) -> KernelObjectType { 71 | KernelObjectType::MemoryObject 72 | } 73 | } 74 | 75 | impl PartialEq for MemoryObject { 76 | fn eq(&self, other: &Self) -> bool { 77 | self.id == other.id 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /kernel/src/object/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod address_space; 2 | pub mod channel; 3 | pub mod event; 4 | pub mod interrupt; 5 | pub mod memory_object; 6 | pub mod task; 7 | 8 | use core::sync::atomic::{AtomicU64, Ordering}; 9 | use mulch::{downcast::DowncastSync, impl_downcast}; 10 | 11 | /// Each kernel object is assigned a unique 64-bit ID, which is never reused. An ID of `0` is never allocated, and 12 | /// is used as a sentinel value. 13 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] 14 | pub struct KernelObjectId(u64); 15 | 16 | /// A kernel object ID of `0` is reserved as a sentinel value that will never point to a real kernel object. It is 17 | /// used to mark things like the `owner` of a kernel object being the kernel itself. 18 | pub const SENTINEL_KERNEL_ID: KernelObjectId = KernelObjectId(0); 19 | 20 | /// The next available `KernelObjectId`. It is shared between all the CPUs, and so is incremented atomically. 21 | static KERNEL_OBJECT_ID_COUNTER: AtomicU64 = AtomicU64::new(1); 22 | 23 | pub fn alloc_kernel_object_id() -> KernelObjectId { 24 | // TODO: this wraps, so we should manually detect when it wraps around and panic to prevent ID reuse 25 | KernelObjectId(KERNEL_OBJECT_ID_COUNTER.fetch_add(1, Ordering::Relaxed)) 26 | } 27 | 28 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 29 | pub enum KernelObjectType { 30 | AddressSpace, 31 | Task, 32 | MemoryObject, 33 | Channel, 34 | Event, 35 | Interrupt, 36 | } 37 | 38 | /// This trait should be implemented by all types that implement kernel objects, and allows common code to 39 | /// be generic over all kernel objects. Kernel objects are generally handled as `Arc` where `T` is the type 40 | /// implementing `KernelObject`, and so interior mutability should be used for data that needs to be mutable within 41 | /// the kernel object. 42 | pub trait KernelObject: DowncastSync { 43 | fn id(&self) -> KernelObjectId; 44 | fn typ(&self) -> KernelObjectType; 45 | // fn owner(&self) -> KernelObjectId; 46 | } 47 | 48 | impl_downcast!(sync KernelObject); 49 | -------------------------------------------------------------------------------- /kernel/src/tasklets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod queue; 2 | 3 | use crate::clocksource::Clocksource; 4 | use core::{future::Future, time::Duration}; 5 | use maitake::task::JoinHandle; 6 | use spinning_top::Spinlock; 7 | 8 | /// Poplar supports running asynchronous tasks (which we call *tasklets* to differentiate from 9 | /// our userspace Task objects) in kernelspace through a [`maitake`](https://github.com/hawkw/mycelium/tree/main/maitake)-based 10 | /// runtime. 11 | pub struct TaskletScheduler { 12 | scheduler: Spinlock, 13 | pub timer: maitake::time::Timer, 14 | } 15 | 16 | impl TaskletScheduler { 17 | pub fn new() -> TaskletScheduler 18 | where 19 | T: Clocksource, 20 | { 21 | let clock = maitake::time::Clock::new(Duration::from_nanos(1), || T::nanos_since_boot()); 22 | 23 | TaskletScheduler { 24 | scheduler: Spinlock::new(maitake::scheduler::Scheduler::new()), 25 | timer: maitake::time::Timer::new(clock), 26 | } 27 | } 28 | 29 | pub fn spawn(&self, future: F) -> JoinHandle 30 | where 31 | F: Future + Send + 'static, 32 | F::Output: Send + 'static, 33 | { 34 | let scheduler = self.scheduler.lock(); 35 | scheduler.spawn(future) 36 | } 37 | 38 | pub fn tick(&self) { 39 | self.scheduler.lock().tick(); 40 | self.timer.turn(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/gfxconsole/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.10.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a165d606cf084741d4ac3a28fb6e9b1eb0bd31f6cd999098cfddb0b2ab381dc0" 10 | 11 | [[package]] 12 | name = "font8x8" 13 | version = "0.2.5" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "44226c40489fb1d602344a1d8f1b544570c3435e396dda1eda7b5ef010d8f1be" 16 | 17 | [[package]] 18 | name = "gfxconsole" 19 | version = "0.1.0" 20 | dependencies = [ 21 | "bit_field", 22 | "font8x8", 23 | ] 24 | -------------------------------------------------------------------------------- /lib/gfxconsole/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfxconsole" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | bit_field = "0.10" 9 | font8x8 = { version = "0.2", default-features = false, features = ["unicode"] } 10 | -------------------------------------------------------------------------------- /lib/gfxconsole/src/fb.rs: -------------------------------------------------------------------------------- 1 | use bit_field::BitField; 2 | use font8x8::UnicodeFonts; 3 | 4 | pub type Rgb32 = u32; 5 | pub type PixelFormat = u32; 6 | 7 | pub struct Framebuffer { 8 | fb: *mut PixelFormat, 9 | 10 | pub width: usize, 11 | pub height: usize, 12 | pub stride: usize, 13 | 14 | // Pixel format 15 | red_shift: u8, 16 | green_shift: u8, 17 | blue_shift: u8, 18 | } 19 | 20 | unsafe impl Send for Framebuffer {} 21 | 22 | impl Framebuffer { 23 | pub fn new( 24 | fb: *mut u32, 25 | width: usize, 26 | height: usize, 27 | stride: usize, 28 | red_shift: u8, 29 | green_shift: u8, 30 | blue_shift: u8, 31 | ) -> Framebuffer { 32 | Framebuffer { fb, width, height, stride, red_shift, green_shift, blue_shift } 33 | } 34 | 35 | pub fn draw_rect(&mut self, start_x: usize, start_y: usize, width: usize, height: usize, fill: Rgb32) { 36 | assert!((start_x + width) <= self.width); 37 | assert!((start_y + height) <= self.height); 38 | 39 | let fill = self.rgb_to_pixel_format(fill); 40 | 41 | for y in start_y..(start_y + height) { 42 | for x in start_x..(start_x + width) { 43 | unsafe { 44 | *(self.fb.offset((y * self.stride + x) as isize)) = fill; 45 | } 46 | } 47 | } 48 | } 49 | 50 | pub fn clear(&mut self, fill: Rgb32) { 51 | self.draw_rect(0, 0, self.width, self.height, fill); 52 | } 53 | 54 | pub fn draw_glyph(&mut self, key: char, x: usize, y: usize, fill: Rgb32) { 55 | let fill = self.rgb_to_pixel_format(fill); 56 | for (line, line_data) in font8x8::BASIC_FONTS.get(key).unwrap().iter().enumerate() { 57 | // TODO: this is amazingly inefficient. We could replace with a lookup table and multiply by the color 58 | // if this is too slow. 59 | for bit in 0..8 { 60 | if line_data.get_bit(bit) { 61 | unsafe { 62 | *(self.fb.offset(((y + line) * self.stride + (x + bit)) as isize)) = fill; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | pub fn draw_string(&mut self, string: &str, start_x: usize, start_y: usize, fill: Rgb32) { 70 | for (index, c) in string.chars().enumerate() { 71 | self.draw_glyph(c, start_x + (index * 8), start_y, fill); 72 | } 73 | } 74 | 75 | fn rgb_to_pixel_format(&self, color: Rgb32) -> PixelFormat { 76 | let r = ((color >> 16) & 0xff) as u32; 77 | let g = ((color >> 8) & 0xff) as u32; 78 | let b = (color & 0xff) as u32; 79 | (r << self.red_shift) | (g << self.green_shift) | (b << self.blue_shift) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/gpt/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "gpt" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /lib/gpt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gpt" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /lib/gpt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | mod guid; 4 | 5 | pub use guid::Guid; 6 | 7 | use core::ffi::CStr; 8 | 9 | #[derive(Clone, Debug)] 10 | #[repr(C)] 11 | pub struct GptHeader { 12 | pub signature: [u8; 8], 13 | pub revision: u32, 14 | pub header_size: u32, 15 | pub header_crc: u32, 16 | _reserved0: u32, 17 | pub my_lba: u64, 18 | pub alternate_lba: u64, 19 | pub first_usable_lba: u64, 20 | pub last_usable_lba: u64, 21 | pub disk_guid: Guid, 22 | pub partition_entry_lba: u64, 23 | pub num_partition_entries: u32, 24 | pub size_of_partition_entry: u32, 25 | pub partition_entry_array_crc: u32, 26 | } 27 | 28 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 29 | pub enum HeaderError { 30 | InvalidSignature, 31 | InvalidCrc, 32 | } 33 | 34 | impl GptHeader { 35 | pub fn validate(&self) -> Result<(), HeaderError> { 36 | if self.signature != *b"EFI PART" { 37 | return Err(HeaderError::InvalidSignature); 38 | } 39 | 40 | // TODO: validate crc 41 | 42 | Ok(()) 43 | } 44 | } 45 | 46 | #[derive(Clone, Debug)] 47 | #[repr(C)] 48 | pub struct PartitionEntry { 49 | pub partition_type_guid: Guid, 50 | pub unique_partition_guid: Guid, 51 | pub starting_lba: u64, 52 | pub ending_lba: u64, 53 | pub attributes: PartitionAttributes, 54 | pub partition_name: [u8; 72], 55 | } 56 | 57 | impl PartitionEntry { 58 | pub fn name(&self) -> Result<&str, ()> { 59 | CStr::from_bytes_until_nul(&self.partition_name).map_err(|_| ())?.to_str().map_err(|_| ()) 60 | } 61 | } 62 | 63 | #[derive(Clone, Debug)] 64 | #[repr(transparent)] 65 | pub struct PartitionAttributes(u64); 66 | -------------------------------------------------------------------------------- /lib/hal/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.10.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 10 | 11 | [[package]] 12 | name = "cfg-if" 13 | version = "0.1.10" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 16 | 17 | [[package]] 18 | name = "hal" 19 | version = "0.1.0" 20 | dependencies = [ 21 | "bit_field", 22 | "cfg-if", 23 | "log", 24 | ] 25 | 26 | [[package]] 27 | name = "log" 28 | version = "0.4.20" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 31 | -------------------------------------------------------------------------------- /lib/hal/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hal" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cfg-if = "0.1" 9 | log = "0.4" 10 | bit_field = "0.10" 11 | 12 | [features] 13 | platform_rv64_virt = [] 14 | platform_mq_pro = [] 15 | -------------------------------------------------------------------------------- /lib/hal/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(decl_macro, step_trait)] 3 | 4 | #[cfg(test)] 5 | #[macro_use] 6 | extern crate std; 7 | 8 | pub mod memory; 9 | -------------------------------------------------------------------------------- /lib/hal/src/memory/frame.rs: -------------------------------------------------------------------------------- 1 | use super::{FrameSize, PAddr, Size4KiB}; 2 | use core::{ 3 | iter::Step, 4 | marker::PhantomData, 5 | ops::{Add, AddAssign}, 6 | }; 7 | 8 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 9 | pub struct Frame 10 | where 11 | S: FrameSize, 12 | { 13 | pub start: PAddr, 14 | _phantom: PhantomData, 15 | } 16 | 17 | impl Frame 18 | where 19 | S: FrameSize, 20 | { 21 | pub fn starts_with(address: PAddr) -> Frame { 22 | assert!( 23 | address.is_aligned(S::SIZE), 24 | "Tried to create frame of size {:#x} starting at invalid address: {:#x}", 25 | S::SIZE, 26 | address, 27 | ); 28 | Frame { start: address, _phantom: PhantomData } 29 | } 30 | 31 | pub fn contains(address: PAddr) -> Frame { 32 | Frame { start: address.align_down(S::SIZE), _phantom: PhantomData } 33 | } 34 | } 35 | 36 | impl Add for Frame 37 | where 38 | S: FrameSize, 39 | { 40 | type Output = Frame; 41 | 42 | fn add(self, num_frames: usize) -> Self::Output { 43 | assert!(PAddr::new(usize::from(self.start) + num_frames * S::SIZE).is_some()); 44 | Frame { start: self.start + num_frames * S::SIZE, _phantom: PhantomData } 45 | } 46 | } 47 | 48 | impl AddAssign for Frame 49 | where 50 | S: FrameSize, 51 | { 52 | fn add_assign(&mut self, num_frames: usize) { 53 | assert!(PAddr::new(usize::from(self.start) + num_frames * S::SIZE).is_some()); 54 | self.start += num_frames * S::SIZE; 55 | } 56 | } 57 | 58 | impl Step for Frame 59 | where 60 | S: FrameSize, 61 | { 62 | fn steps_between(start: &Self, end: &Self) -> (usize, Option) { 63 | let Some(address_difference) = usize::from(end.start).checked_sub(usize::from(start.start)) else { 64 | return (0, None); 65 | }; 66 | assert!(address_difference % S::SIZE == 0); 67 | (address_difference / S::SIZE, Some(address_difference / S::SIZE)) 68 | } 69 | 70 | fn forward_checked(start: Self, count: usize) -> Option { 71 | Some(Frame { start: start.start.checked_add(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData }) 72 | } 73 | 74 | fn backward_checked(start: Self, count: usize) -> Option { 75 | Some(Frame { start: start.start.checked_sub(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/hal/src/memory/page.rs: -------------------------------------------------------------------------------- 1 | use super::{FrameSize, Size4KiB, VAddr}; 2 | use core::{ 3 | iter::Step, 4 | marker::PhantomData, 5 | ops::{Add, AddAssign}, 6 | }; 7 | 8 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 9 | pub struct Page { 10 | pub start: VAddr, 11 | _phantom: PhantomData, 12 | } 13 | 14 | impl Page 15 | where 16 | S: FrameSize, 17 | { 18 | pub fn starts_with(address: VAddr) -> Page { 19 | assert!(usize::from(address) % S::SIZE == 0, "Address is not at the start of a page"); 20 | Page { start: address, _phantom: PhantomData } 21 | } 22 | 23 | pub fn contains(address: VAddr) -> Page { 24 | Page { start: address.align_down(S::SIZE), _phantom: PhantomData } 25 | } 26 | } 27 | 28 | impl Add for Page 29 | where 30 | S: FrameSize, 31 | { 32 | type Output = Page; 33 | 34 | fn add(self, num_pages: usize) -> Self::Output { 35 | Page::contains(self.start + num_pages * S::SIZE) 36 | } 37 | } 38 | 39 | impl AddAssign for Page 40 | where 41 | S: FrameSize, 42 | { 43 | fn add_assign(&mut self, num_pages: usize) { 44 | *self = *self + num_pages; 45 | } 46 | } 47 | 48 | impl Step for Page 49 | where 50 | S: FrameSize, 51 | { 52 | fn steps_between(start: &Self, end: &Self) -> (usize, Option) { 53 | let Some(address_difference) = usize::from(end.start).checked_sub(usize::from(start.start)) else { 54 | return (0, None); 55 | }; 56 | assert!(address_difference % S::SIZE == 0); 57 | (address_difference / S::SIZE, Some(address_difference / S::SIZE)) 58 | } 59 | 60 | fn forward_checked(start: Self, count: usize) -> Option { 61 | Some(Page { start: start.start.checked_add(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData }) 62 | } 63 | 64 | fn backward_checked(start: Self, count: usize) -> Option { 65 | Some(Page { start: start.start.checked_sub(S::SIZE.checked_mul(count)?)?, _phantom: PhantomData }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/hal_riscv/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bit_field" 13 | version = "0.10.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.3.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "0.1.10" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 28 | 29 | [[package]] 30 | name = "cfg-if" 31 | version = "1.0.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 34 | 35 | [[package]] 36 | name = "hal" 37 | version = "0.1.0" 38 | dependencies = [ 39 | "bit_field", 40 | "cfg-if 0.1.10", 41 | "log", 42 | ] 43 | 44 | [[package]] 45 | name = "hal_riscv" 46 | version = "0.1.0" 47 | dependencies = [ 48 | "bit_field", 49 | "bitflags", 50 | "cfg-if 1.0.0", 51 | "hal", 52 | "mulch", 53 | "tracing", 54 | "volatile", 55 | ] 56 | 57 | [[package]] 58 | name = "log" 59 | version = "0.4.20" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 62 | 63 | [[package]] 64 | name = "mulch" 65 | version = "0.1.0" 66 | dependencies = [ 67 | "bit_field", 68 | "cfg-if 0.1.10", 69 | "num-traits", 70 | ] 71 | 72 | [[package]] 73 | name = "num-traits" 74 | version = "0.2.17" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 77 | dependencies = [ 78 | "autocfg", 79 | ] 80 | 81 | [[package]] 82 | name = "pin-project-lite" 83 | version = "0.2.13" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 86 | 87 | [[package]] 88 | name = "tracing" 89 | version = "0.2.0" 90 | source = "git+https://github.com/tokio-rs/tracing#e06201ca27eb5bb2b7574acf7a085e71ece821a9" 91 | dependencies = [ 92 | "pin-project-lite", 93 | "tracing-core", 94 | ] 95 | 96 | [[package]] 97 | name = "tracing-core" 98 | version = "0.2.0" 99 | source = "git+https://github.com/tokio-rs/tracing#e06201ca27eb5bb2b7574acf7a085e71ece821a9" 100 | 101 | [[package]] 102 | name = "volatile" 103 | version = "0.1.0" 104 | -------------------------------------------------------------------------------- /lib/hal_riscv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hal_riscv" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | hal = { path = "../hal" } 9 | volatile = { path = "../volatile" } 10 | bitflags = "1.3.2" 11 | bit_field = "0.10.1" 12 | mulch = { path = "../mulch" } 13 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 14 | cfg-if = "1.0" 15 | 16 | [features] 17 | platform_rv64_virt = ["hal/platform_rv64_virt"] 18 | platform_mq_pro = ["hal/platform_mq_pro"] 19 | -------------------------------------------------------------------------------- /lib/hal_riscv/src/hw/imsic.rs: -------------------------------------------------------------------------------- 1 | use super::csr::{Sireg, Siselect, Stopei}; 2 | use bit_field::BitField; 3 | 4 | /// The Incoming Message-Signalled Interrupt Controller (IMSIC) is a hardware component associated 5 | /// with a hart that coordinates incoming message-signalled interrupts (MSIs), and signals to the 6 | /// hart when pending interrupts need to be serviced. 7 | /// 8 | /// Each IMSIC has a register file in memory for devices to write to (to trigger an interrupt), as 9 | /// well as a CSR interface for the hart to configure it via. There are separate interrupt files 10 | /// for each privilege level. 11 | pub struct Imsic {} 12 | 13 | impl Imsic { 14 | pub fn init() { 15 | unsafe { 16 | // Enable the IMSIC 17 | Siselect::write(Siselect::EIDELIVERY); 18 | Sireg::write(1); 19 | 20 | // Set the priority to see all interrupts 21 | Siselect::write(Siselect::EITHRESHOLD); 22 | Sireg::write(0); 23 | } 24 | } 25 | 26 | pub fn enable(number: usize) { 27 | /* 28 | * On RV64, the even-numbered registers are not selectable. We need to skip over one 29 | * register for each 64-bit block of interrupts. 30 | */ 31 | let eie_reg = Siselect::EIE_BASE + 2 * (number / 64); 32 | let eie_bit = number % 64; 33 | 34 | unsafe { 35 | Siselect::write(eie_reg); 36 | let mut value = Sireg::read(); 37 | value.set_bit(eie_bit, true); 38 | Sireg::write(value); 39 | } 40 | } 41 | 42 | pub fn pop() -> u16 { 43 | let stopei = Stopei::read() as u32; 44 | /* 45 | * Bits 0..11 = interrupt priority (should actually be the same as the identity) 46 | * Bits 16..27 = interrupt identity 47 | */ 48 | stopei.get_bits(16..27) as u16 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/hal_riscv/src/hw/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aplic; 2 | pub mod csr; 3 | pub mod imsic; 4 | pub mod plic; 5 | pub mod uart16550; 6 | -------------------------------------------------------------------------------- /lib/hal_riscv/src/platform_d1.rs: -------------------------------------------------------------------------------- 1 | use crate::paging::Level3; 2 | 3 | pub mod memory { 4 | use hal::memory::{kibibytes, mebibytes, PAddr}; 5 | 6 | pub const DRAM_START: PAddr = PAddr::new(0x4000_0000).unwrap(); 7 | pub const OPENSBI_ADDR: PAddr = DRAM_START; 8 | // TODO: when const traits are implemented, this should be rewritten in terms of DRAM_START 9 | pub const SEED_ADDR: PAddr = PAddr::new(0x4000_0000 + kibibytes(512)).unwrap(); 10 | pub const RAMDISK_ADDR: PAddr = PAddr::new(0x4000_0000 + mebibytes(1)).unwrap(); 11 | } 12 | 13 | pub const VIRTUAL_ADDRESS_BITS: usize = 39; 14 | pub type PageTableImpl = crate::paging::PageTableImpl; 15 | 16 | /// This module contains constants that define how the kernel address space is laid out on RISC-V, 17 | /// using the Sv39 paging model. The Sv39 model provides us with a 512GiB address space, which is a 18 | /// little more compact than the layout we have on other architectures or with Sv48, but still more 19 | /// than sufficient for the vast majority of platforms. 20 | /// 21 | /// For simplicity, we reserve the top half of the address space, from `0xffff_ffc0_0000_0000` to 22 | /// `0xffff_ffff_ffff_ffff`, for the kernel - this makes it easy to distinguish kernel addresses 23 | /// from userspace ones (both visually, and in code by testing a single sign-extended bit). 24 | /// 25 | /// All of physical memory is mapped at the base of kernel-space. 26 | /// 27 | /// The top 1GiB is reserved for the kernel itself, starting at `0xffff_ffff_c000_0000`. 28 | pub mod kernel_map { 29 | use hal::memory::{PAddr, VAddr}; 30 | 31 | pub const KERNEL_ADDRESS_SPACE_START: VAddr = VAddr::new(0xffff_ffc0_0000_0000); 32 | pub const PHYSICAL_MAP_BASE: VAddr = KERNEL_ADDRESS_SPACE_START; 33 | 34 | /// Access a given physical address through the physical mapping. This cannot be used until the kernel page tables 35 | /// have been switched to. 36 | /// 37 | /// # Safety 38 | /// This itself is safe, because to cause memory unsafety a raw pointer must be created and accessed from the 39 | /// `VAddr`, which is unsafe. 40 | pub fn physical_to_virtual(address: PAddr) -> VAddr { 41 | PHYSICAL_MAP_BASE + usize::from(address) 42 | } 43 | 44 | /// The kernel starts at -1GiB. The kernel image is loaded directly at this address, and the following space until 45 | /// the top of memory is managed dynamically and contains the boot info structures, memory map, and kernel heap. 46 | pub const KERNEL_BASE: VAddr = VAddr::new(0xffff_ffff_c000_0000); 47 | } 48 | -------------------------------------------------------------------------------- /lib/hal_x86_64/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hal_x86_64" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | hal = { path = "../hal" } 9 | bitflags = "1" 10 | bit_field = "0.10" 11 | spinning_top = "0.3.0" 12 | mulch = { path = "../mulch" } 13 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 14 | 15 | [features] 16 | default_features = [] 17 | qemu = [] 18 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/i8259_pic.rs: -------------------------------------------------------------------------------- 1 | use super::port::Port; 2 | 3 | pub struct Pic { 4 | primary_command: Port, 5 | primary_data: Port, 6 | secondary_command: Port, 7 | secondary_data: Port, 8 | } 9 | 10 | impl Pic { 11 | pub const unsafe fn new() -> Pic { 12 | unsafe { 13 | Pic { 14 | primary_command: Port::new(0x20), 15 | primary_data: Port::new(0x21), 16 | secondary_command: Port::new(0xa0), 17 | secondary_data: Port::new(0xa1), 18 | } 19 | } 20 | } 21 | 22 | /// Remap and disable the PIC. It is necessary to remap the PIC even if we don't want to use it because 23 | /// otherwise spurious interrupts can cause exceptions. 24 | pub fn remap_and_disable(&mut self, primary_vector_offset: u8, secondary_vector_offset: u8) { 25 | unsafe { 26 | /* 27 | * 0x80 is a port used by POST. It shouldn't do anything, but it'll take long enough to execute writes 28 | * to it that we should block for long enough for the PICs to actually do what we ask them to. 29 | */ 30 | let mut wait_port: Port = Port::new(0x80); 31 | let mut wait = || wait_port.write(0); 32 | 33 | // Tell the PICs to start their initialization sequences in cascade mode 34 | self.primary_command.write(0x11); 35 | self.secondary_command.write(0x11); 36 | wait(); 37 | 38 | // Tell the PICs their new interrupt vectors 39 | self.primary_data.write(primary_vector_offset); 40 | self.secondary_data.write(secondary_vector_offset); 41 | wait(); 42 | 43 | // Tell the primary PIC that the secondary is at IRQ2 44 | self.primary_data.write(0b100); 45 | wait(); 46 | 47 | // Tell the secondary PIC its cascade identity 48 | self.secondary_data.write(0b10); 49 | wait(); 50 | 51 | // Tell the PICs to go into 8086/88 MCS-80/85 mode 52 | self.primary_data.write(0x1); 53 | self.secondary_data.write(0x1); 54 | wait(); 55 | 56 | // Mask both PICs 57 | self.primary_data.write(0xff); 58 | self.secondary_data.write(0xff); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cpu; 2 | pub mod gdt; 3 | pub mod i8259_pic; 4 | pub mod idt; 5 | pub mod ioapic; 6 | pub mod lapic; 7 | pub mod port; 8 | pub mod registers; 9 | pub mod serial; 10 | pub mod tlb; 11 | pub mod tss; 12 | 13 | #[cfg(feature = "qemu")] 14 | pub mod qemu; 15 | 16 | use hal::memory::VAddr; 17 | 18 | #[repr(C, packed)] 19 | pub struct DescriptorTablePointer { 20 | /// `base + limit` is the last addressable byte of the descriptor table. 21 | pub limit: u16, 22 | 23 | /// Virtual address of the start of the descriptor table. 24 | pub base: VAddr, 25 | } 26 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/port.rs: -------------------------------------------------------------------------------- 1 | use core::{arch::asm, marker::PhantomData}; 2 | 3 | /// Implemented by the types used to represent 8-bit, 16-bit, and 32-bit IO ports. Should not be 4 | /// implemented for any types apart from `u8`, `u16`, and `u32`. 5 | pub trait PortSize { 6 | unsafe fn port_read(port: u16) -> Self; 7 | unsafe fn port_write(port: u16, value: Self); 8 | } 9 | 10 | impl PortSize for u8 { 11 | unsafe fn port_read(port: u16) -> u8 { 12 | let result: u8; 13 | unsafe { 14 | asm!("in al, dx", in("dx") port, out("al") result); 15 | } 16 | result 17 | } 18 | 19 | unsafe fn port_write(port: u16, value: u8) { 20 | unsafe { 21 | asm!("out dx, al", in("dx") port, in("al") value); 22 | } 23 | } 24 | } 25 | 26 | impl PortSize for u16 { 27 | unsafe fn port_read(port: u16) -> u16 { 28 | let result: u16; 29 | unsafe { 30 | asm!("in ax, dx", in("dx") port, out("ax") result); 31 | } 32 | result 33 | } 34 | 35 | unsafe fn port_write(port: u16, value: u16) { 36 | unsafe { 37 | asm!("out dx, ax", in("dx") port, in("ax") value); 38 | } 39 | } 40 | } 41 | 42 | impl PortSize for u32 { 43 | unsafe fn port_read(port: u16) -> u32 { 44 | let result: u32; 45 | unsafe { 46 | asm!("in eax, dx", in("dx") port, out("eax") result); 47 | } 48 | result 49 | } 50 | 51 | unsafe fn port_write(port: u16, value: u32) { 52 | unsafe { 53 | asm!("out dx, eax", in("dx") port, in("eax") value); 54 | } 55 | } 56 | } 57 | 58 | /// Represents an IO port that can be read and written to using the `in` and `out` instructions. 59 | pub struct Port { 60 | port: u16, 61 | phantom: PhantomData, 62 | } 63 | 64 | impl Port 65 | where 66 | T: PortSize, 67 | { 68 | /// Create a new `Port` at the specified I/O address. Unsafe because writing to random IO ports 69 | /// is bad. 70 | pub const unsafe fn new(port: u16) -> Port { 71 | Port { port, phantom: PhantomData } 72 | } 73 | 74 | pub unsafe fn read(&self) -> T { 75 | unsafe { T::port_read(self.port) } 76 | } 77 | 78 | pub unsafe fn write(&mut self, value: T) { 79 | unsafe { 80 | T::port_write(self.port, value); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/qemu.rs: -------------------------------------------------------------------------------- 1 | use crate::hw::port::Port; 2 | 3 | /// Exit codes to use with `ExitPort`. We need to differentiate these from the exit codes QEMU itself uses, so we 4 | /// can differentiate between success/failure in QEMU vs the kernel itself. 5 | /// 6 | /// The code passed to the exit port is then turned into a QEMU exit code with `(code << 1) | 1`, so `Success` ends 7 | /// up as `0x21`, and `Failed` ends up as `0x23`. These can be handled specially by whatever invokes QEMU. 8 | #[derive(Clone, Copy, Debug)] 9 | #[repr(u32)] 10 | pub enum ExitCode { 11 | Success = 0x10, 12 | Failed = 0x11, 13 | } 14 | 15 | pub struct ExitPort(Port); 16 | 17 | impl ExitPort { 18 | pub unsafe fn new() -> ExitPort { 19 | ExitPort(unsafe { Port::new(0xf4) }) 20 | } 21 | 22 | pub fn exit(mut self, exit_code: ExitCode) -> ! { 23 | unsafe { 24 | self.0.write(exit_code as u32); 25 | } 26 | unreachable!() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/serial.rs: -------------------------------------------------------------------------------- 1 | use super::port::Port; 2 | use core::{arch::asm, fmt}; 3 | 4 | pub const COM1: u16 = 0x3f8; 5 | 6 | pub struct SerialPort { 7 | data_register: Port, 8 | interrupt_enable_register: Port, 9 | interrupt_identity_register: Port, 10 | line_control_register: Port, 11 | modem_control_register: Port, 12 | line_status_register: Port, 13 | } 14 | 15 | impl fmt::Write for SerialPort { 16 | fn write_str(&mut self, s: &str) -> fmt::Result { 17 | for byte in s.bytes() { 18 | unsafe { 19 | match byte { 20 | /* 21 | * Serial ports expect both a carriage return and a line feed ("\n\r") for 22 | * newlines. 23 | */ 24 | b'\n' => { 25 | self.write(b'\n'); 26 | self.write(b'\r'); 27 | } 28 | 29 | _ => self.write(byte), 30 | } 31 | } 32 | } 33 | Ok(()) 34 | } 35 | } 36 | 37 | impl SerialPort { 38 | pub const unsafe fn new(address: u16) -> SerialPort { 39 | unsafe { 40 | SerialPort { 41 | data_register: Port::new(address), 42 | interrupt_enable_register: Port::new(address + 1), 43 | interrupt_identity_register: Port::new(address + 2), 44 | line_control_register: Port::new(address + 3), 45 | modem_control_register: Port::new(address + 4), 46 | line_status_register: Port::new(address + 5), 47 | } 48 | } 49 | } 50 | 51 | pub unsafe fn initialize(&mut self) { 52 | unsafe { 53 | // Disable IRQs 54 | self.interrupt_enable_register.write(0x00); 55 | 56 | // Set baud rate divisor to 0x0003 (38400 baud rate) 57 | self.line_control_register.write(0x80); 58 | self.data_register.write(0x03); 59 | self.interrupt_enable_register.write(0x00); 60 | 61 | // 8 bits, no parity bits, one stop bit 62 | self.line_control_register.write(0x03); 63 | 64 | // Enable FIFO, clear buffer, 14-byte thresh 65 | self.interrupt_identity_register.write(0xC7); 66 | 67 | // Enable IRQs again, set RTS/DSR 68 | self.modem_control_register.write(0x0B); 69 | } 70 | } 71 | 72 | #[allow(unused)] 73 | pub unsafe fn read(&self) -> u8 { 74 | while (unsafe { self.line_status_register.read() } & 1) == 0 { 75 | unsafe { 76 | asm!("pause"); 77 | } 78 | } 79 | 80 | unsafe { self.data_register.read() } 81 | } 82 | 83 | pub unsafe fn write(&mut self, value: u8) { 84 | while (unsafe { self.line_status_register.read() } & 0x20) == 0 { 85 | unsafe { 86 | asm!("pause"); 87 | } 88 | } 89 | 90 | unsafe { 91 | self.data_register.write(value); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/tlb.rs: -------------------------------------------------------------------------------- 1 | use super::registers::{read_control_reg, write_control_reg}; 2 | use core::arch::asm; 3 | use hal::memory::VAddr; 4 | 5 | #[rustfmt::skip] 6 | pub fn invalidate_page(address: VAddr) { 7 | unsafe { 8 | asm!("invlpg [{}]", in(reg) usize::from(address)); 9 | } 10 | } 11 | 12 | pub fn flush() { 13 | let current_cr3 = read_control_reg!(cr3); 14 | unsafe { 15 | write_control_reg!(cr3, current_cr3); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/hw/tss.rs: -------------------------------------------------------------------------------- 1 | use core::mem; 2 | use hal::memory::VAddr; 3 | 4 | /// Hardware task switching isn't supported on x86_64, so the TSS is just used as a vestigal place 5 | /// to stick stuff. It's used to store kernel-level stacks that should be used if interrupts occur 6 | /// (this is used to prevent triple-faults from occuring if we overflow the kernel stack). 7 | #[derive(Clone, Copy, Debug)] 8 | #[repr(C, packed(4))] 9 | pub struct Tss { 10 | _reserved_1: u32, 11 | pub privilege_stack_table: [VAddr; 3], 12 | _reserved_2: u64, 13 | pub interrupt_stack_table: [VAddr; 7], 14 | _reserved_3: u64, 15 | _reserved_4: u16, 16 | pub iomap_base: u16, 17 | } 18 | 19 | impl Tss { 20 | pub fn new() -> Tss { 21 | Tss { 22 | _reserved_1: 0, 23 | privilege_stack_table: [VAddr::new(0x0); 3], 24 | _reserved_2: 0, 25 | interrupt_stack_table: [VAddr::new(0x0); 7], 26 | _reserved_3: 0, 27 | _reserved_4: 0, 28 | iomap_base: mem::size_of::() as u16, 29 | } 30 | } 31 | 32 | pub fn set_kernel_stack(&mut self, stack_pointer: VAddr) { 33 | self.privilege_stack_table[0] = stack_pointer; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/kernel_map.rs: -------------------------------------------------------------------------------- 1 | //! This module contains constants that define how the kernel address space is laid out on x86_64. The 511th P4 2 | //! entry (virtual addresses `0xffff_ff80_0000_0000` through `0xffff_ffff_ffff_ffff`) is always mapped to the 3 | //! kernel P3. The rest of the virtual address space (virtual addresses `0x0000_0000_0000_0000` through 4 | //! `0xffff_ff7f_ffff_ffff`) are free for userspace to use. 5 | //! 6 | //! This gives us 512 GiB of kernel space. The kernel itself is built with the `kernel` mc-model, and so must lie 7 | //! in the -2GiB of the address space (the top two entries of the kernel P3). The remaining 510 GiB of the kernel 8 | //! P3 is used to map the entirety of physical memory into the kernel address space, and for task kernel stacks. 9 | //! 10 | //! Directly below the base of the kernel, we reserve 128GiB for task kernel stacks, which gives us a maximum of 11 | //! 65536 tasks if each one has the default stack size. 12 | //! 13 | //! This leaves us 382GiB for the physical memory map, which should be sufficient for any system I can imagine us 14 | //! running on (famous last words). 15 | 16 | use hal::memory::{mebibytes, Bytes, PAddr, VAddr}; 17 | 18 | pub const KERNEL_P4_ENTRY: usize = 511; 19 | pub const KERNEL_ADDRESS_SPACE_START: VAddr = VAddr::new(0xffff_ff80_0000_0000); 20 | 21 | pub const PHYSICAL_MAPPING_BASE: VAddr = KERNEL_ADDRESS_SPACE_START; 22 | 23 | /// Access a given physical address through the physical mapping. This cannot be used until the kernel page tables 24 | /// have been switched to. 25 | /// 26 | /// # Safety 27 | /// This itself is safe, because to cause memory unsafety a raw pointer must be created and accessed from the 28 | /// `VAddr`, which is unsafe. 29 | pub fn physical_to_virtual(address: PAddr) -> VAddr { 30 | PHYSICAL_MAPPING_BASE + usize::from(address) 31 | } 32 | 33 | pub const KERNEL_STACKS_BASE: VAddr = VAddr::new(0xffff_ffdf_8000_0000); 34 | /* 35 | * There is an imposed maximum number of tasks because of the simple way we're allocating task kernel stacks. 36 | * This is currently 65536 with a task kernel stack size of 2MiB. 37 | */ 38 | pub const STACK_SLOT_SIZE: Bytes = mebibytes(2); 39 | pub const MAX_TASKS: usize = 65536; 40 | 41 | /// The kernel starts at -2GiB. The kernel image is loaded directly at this address, and the following space until 42 | /// the top of memory is managed dynamically and contains the boot info structures, memory map, and kernel heap. 43 | pub const KERNEL_BASE: VAddr = VAddr::new(0xffff_ffff_8000_0000); 44 | -------------------------------------------------------------------------------- /lib/hal_x86_64/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(decl_macro, naked_functions, type_ascription, if_let_guard)] 3 | #![deny(unsafe_op_in_unsafe_fn)] 4 | 5 | #[cfg(test)] 6 | #[macro_use] 7 | extern crate std; 8 | 9 | pub mod hw; 10 | pub mod kernel_map; 11 | pub mod paging; 12 | 13 | #[inline(always)] 14 | pub fn breakpoint() { 15 | unsafe { 16 | core::arch::asm!("int3"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/mer/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.10.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 10 | 11 | [[package]] 12 | name = "mer" 13 | version = "0.5.1" 14 | dependencies = [ 15 | "bit_field", 16 | "scroll", 17 | ] 18 | 19 | [[package]] 20 | name = "proc-macro2" 21 | version = "1.0.86" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 24 | dependencies = [ 25 | "unicode-ident", 26 | ] 27 | 28 | [[package]] 29 | name = "quote" 30 | version = "1.0.36" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 33 | dependencies = [ 34 | "proc-macro2", 35 | ] 36 | 37 | [[package]] 38 | name = "scroll" 39 | version = "0.12.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" 42 | dependencies = [ 43 | "scroll_derive", 44 | ] 45 | 46 | [[package]] 47 | name = "scroll_derive" 48 | version = "0.12.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932" 51 | dependencies = [ 52 | "proc-macro2", 53 | "quote", 54 | "syn", 55 | ] 56 | 57 | [[package]] 58 | name = "syn" 59 | version = "2.0.70" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" 62 | dependencies = [ 63 | "proc-macro2", 64 | "quote", 65 | "unicode-ident", 66 | ] 67 | 68 | [[package]] 69 | name = "unicode-ident" 70 | version = "1.0.12" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 73 | -------------------------------------------------------------------------------- /lib/mer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mer" 3 | version = "0.5.1" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | repository = "https://github.com/IsaacWoods/poplar/tree/main/lib/mer" 7 | description = "Ergonomic ELF64 parser, designed for use within kernels" 8 | readme = "README.md" 9 | license = "MIT/Apache-2.0" 10 | 11 | [dependencies] 12 | scroll = { version = "0.12", default-features = false, features = ["derive"] } 13 | bit_field = "0.10" 14 | -------------------------------------------------------------------------------- /lib/mer/README.md: -------------------------------------------------------------------------------- 1 | # Mer 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | [![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | 5 | A Rust library for ergonomically working with ELF files. Designed to be usable from within `no_std` contexts, especially kernels. 6 | -------------------------------------------------------------------------------- /lib/mer/src/header.rs: -------------------------------------------------------------------------------- 1 | use crate::ElfError; 2 | use scroll::Pread; 3 | 4 | /// The ELF header 5 | #[derive(Debug, Default, Pread)] 6 | #[repr(C)] 7 | pub struct Header { 8 | pub magic: [u8; 4], 9 | pub class: u8, 10 | pub data: u8, 11 | pub header_version: u8, 12 | pub abi: u8, 13 | pub abi_version: u8, 14 | _padding: [u8; 7], 15 | pub file_type: u16, 16 | pub machine_type: u16, 17 | pub version: u32, 18 | pub entry_point: u64, 19 | pub program_header_offset: u64, 20 | pub section_header_offset: u64, 21 | pub flags: u32, 22 | pub header_size: u16, 23 | pub program_header_entry_size: u16, 24 | pub number_of_program_headers: u16, 25 | pub section_header_entry_size: u16, 26 | pub number_of_section_headers: u16, 27 | 28 | /// This is the section index of the string table that contains the names of the sections. 29 | pub string_table_index: u16, 30 | } 31 | 32 | impl Header { 33 | pub fn validate(&self) -> Result<(), ElfError> { 34 | if self.magic != [0x7f, b'E', b'L', b'F'] { 35 | return Err(ElfError::IncorrectMagic); 36 | } 37 | 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/mer/src/note.rs: -------------------------------------------------------------------------------- 1 | use scroll::Pread; 2 | 3 | #[derive(Debug)] 4 | pub struct NoteEntry<'a> { 5 | pub name: &'a [u8], 6 | pub entry_type: u32, 7 | pub desc: &'a [u8], 8 | } 9 | 10 | pub struct NoteIter<'a> { 11 | data: &'a [u8], 12 | } 13 | 14 | impl<'a> NoteIter<'a> { 15 | pub(crate) fn new(data: &'a [u8]) -> NoteIter<'a> { 16 | NoteIter { data } 17 | } 18 | } 19 | 20 | impl<'a> Iterator for NoteIter<'a> { 21 | type Item = NoteEntry<'a>; 22 | 23 | fn next(&mut self) -> Option { 24 | let name_size = self.data.pread::(0).ok()? as usize; 25 | let desc_size = self.data.pread::(4).ok()? as usize; 26 | let entry_type = self.data.pread::(8).ok()?; 27 | 28 | // Calculate the offsets to the description and next entry 29 | let desc_offset = align_up(12 + name_size, 4); 30 | let next_entry_offset = align_up(desc_offset + desc_size, 4); 31 | 32 | // Make sure the next entry is complete - otherwise we'll panic. We treat incomplete 33 | // entries as missing by returning `None`. 34 | if self.data.len() < next_entry_offset { 35 | return None; 36 | } 37 | 38 | let name = &self.data[12..(12 + name_size)]; 39 | let desc = &self.data[desc_offset..(desc_offset + desc_size)]; 40 | 41 | self.data = &self.data[next_entry_offset..]; 42 | Some(NoteEntry { name, entry_type, desc }) 43 | } 44 | } 45 | 46 | fn align_up(offset: usize, alignment: usize) -> usize { 47 | if offset % alignment == 0 { 48 | offset 49 | } else { 50 | offset + alignment - (offset % alignment) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/mer/src/program.rs: -------------------------------------------------------------------------------- 1 | use crate::{note::NoteIter, Elf, ElfError}; 2 | use bit_field::BitField; 3 | use scroll::Pread; 4 | 5 | #[derive(PartialEq, Eq, Debug)] 6 | pub enum SegmentType { 7 | Null, 8 | Load, 9 | Dynamic, 10 | Interp, 11 | Note, 12 | Shlib, 13 | Phdr, 14 | Tls, 15 | 16 | /// A section with type `0x60000000` through `0x6fffffff` inclusive is defined to be 17 | /// environment-specific. 18 | Os(u32), 19 | 20 | /// A section with type `0x70000000` through `0x7fffffff` inclusive is defined to be 21 | /// processor-specific. 22 | Proc(u32), 23 | } 24 | 25 | #[derive(Debug, Pread)] 26 | #[repr(C)] 27 | pub struct ProgramHeader { 28 | pub segment_type: u32, 29 | pub flags: u32, 30 | pub offset: u64, 31 | pub virtual_address: u64, 32 | pub physical_address: u64, 33 | pub file_size: u64, 34 | pub mem_size: u64, 35 | pub alignment: u64, 36 | } 37 | 38 | impl ProgramHeader { 39 | pub(crate) fn validate(&self) -> Result<(), ElfError> { 40 | match self.segment_type { 41 | 0..=7 | 0x60000000..=0x7fffffff => Ok(()), 42 | _ => Err(ElfError::SegmentInvalidType), 43 | }?; 44 | 45 | Ok(()) 46 | } 47 | 48 | pub fn segment_type(&self) -> SegmentType { 49 | match self.segment_type { 50 | 0 => SegmentType::Null, 51 | 1 => SegmentType::Load, 52 | 2 => SegmentType::Dynamic, 53 | 3 => SegmentType::Interp, 54 | 4 => SegmentType::Note, 55 | 5 => SegmentType::Shlib, 56 | 6 => SegmentType::Phdr, 57 | 7 => SegmentType::Tls, 58 | 0x60000000..=0x6fffffff => SegmentType::Os(self.segment_type), 59 | 0x70000000..=0x7fffffff => SegmentType::Proc(self.segment_type), 60 | 61 | _ => panic!("segment_type called on segment with invalid type. Was validate called?"), 62 | } 63 | } 64 | 65 | pub fn data<'a>(&self, elf: &'a Elf) -> &'a [u8] { 66 | &elf.bytes[(self.offset as usize)..((self.offset + self.file_size) as usize)] 67 | } 68 | 69 | pub fn is_executable(&self) -> bool { 70 | self.flags.get_bit(0) 71 | } 72 | 73 | pub fn is_writable(&self) -> bool { 74 | self.flags.get_bit(1) 75 | } 76 | 77 | pub fn is_readable(&self) -> bool { 78 | self.flags.get_bit(2) 79 | } 80 | 81 | /// If this is a `PT_NOTE` segment, iterate the entries. Returns `None` if this isn't a note 82 | /// segment. 83 | pub fn iterate_note_entries<'a>(&self, elf: &'a Elf) -> Option> { 84 | if self.segment_type() != SegmentType::Note { 85 | return None; 86 | } 87 | 88 | Some(NoteIter::new(self.data(elf))) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/mulch/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bit_field" 13 | version = "0.10.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "0.1.10" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 22 | 23 | [[package]] 24 | name = "mulch" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "bit_field", 28 | "cfg-if", 29 | "num-traits", 30 | ] 31 | 32 | [[package]] 33 | name = "num-traits" 34 | version = "0.2.15" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 37 | dependencies = [ 38 | "autocfg", 39 | ] 40 | -------------------------------------------------------------------------------- /lib/mulch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mulch" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cfg-if = "0.1" 9 | bit_field = "0.10" 10 | num-traits = { version = "0.2", default-features = false } 11 | 12 | [features] 13 | has_alloc = [] 14 | -------------------------------------------------------------------------------- /lib/mulch/README.md: -------------------------------------------------------------------------------- 1 | # `Mulch` 2 | This crate is a collection of data structures and utility methods that are used around the Poplar kernel and 3 | its supporting libraries. It's kind of like that unfortunate crate/module of a largish project that contains 4 | all the stuff that doesn't have a better home, such as generic data structures and maths stuff. 5 | -------------------------------------------------------------------------------- /lib/mulch/src/binary_pretty_print.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | use core::{fmt, mem}; 7 | use num_traits::PrimInt; 8 | 9 | /// Values can be wrapped in this type when they're printed to display them as easy-to-read binary 10 | /// numbers. `Display` is implemented to print the value in the form `00000000-00000000`, while 11 | /// `Debug` will print it in the form `00000000(8)-00000000(0)` (with offsets of each byte). 12 | pub struct BinaryPrettyPrint(pub T); 13 | 14 | impl fmt::Display for BinaryPrettyPrint { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | let byte_mask: T = T::from(0xff).unwrap(); 17 | let max_byte = mem::size_of::() - 1; 18 | 19 | for i in 0..max_byte { 20 | let byte = max_byte - i; 21 | write!(f, "{:>08b}-", (self.0 >> (byte * 8)) & byte_mask).unwrap(); 22 | } 23 | write!(f, "{:>08b}", self.0 & byte_mask).unwrap(); 24 | 25 | Ok(()) 26 | } 27 | } 28 | 29 | impl fmt::Debug for BinaryPrettyPrint { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | let byte_mask: T = T::from(0xff).unwrap(); 32 | let max_byte = mem::size_of::() - 1; 33 | 34 | for i in 0..max_byte { 35 | let byte = max_byte - i; 36 | write!(f, "{:>08b}({})-", (self.0 >> (byte * 8)) & byte_mask, byte * 8).unwrap(); 37 | } 38 | write!(f, "{:>08b}(0)", self.0 & byte_mask).unwrap(); 39 | 40 | Ok(()) 41 | } 42 | } 43 | 44 | #[test] 45 | fn test() { 46 | assert_eq!(format!("{}", BinaryPrettyPrint(0 as u8)), "00000000"); 47 | assert_eq!(format!("{}", BinaryPrettyPrint(0 as u16)), "00000000-00000000"); 48 | assert_eq!(format!("{}", BinaryPrettyPrint(0 as u32)), "00000000-00000000-00000000-00000000"); 49 | } 50 | -------------------------------------------------------------------------------- /lib/mulch/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | #![no_std] 7 | #![feature(decl_macro, type_ascription, extern_types)] 8 | 9 | #[cfg(test)] 10 | #[macro_use] 11 | extern crate std; 12 | 13 | cfg_if::cfg_if! { 14 | if #[cfg(feature = "has_alloc")] { 15 | extern crate alloc; 16 | 17 | #[doc(hidden)] 18 | pub mod alloc_reexport { 19 | #[doc(hidden)] 20 | pub use ::alloc::*; 21 | } 22 | 23 | #[macro_use] 24 | pub mod downcast; 25 | } 26 | } 27 | 28 | mod binary_pretty_print; 29 | pub mod bitmap; 30 | mod init_guard; 31 | pub mod math; 32 | #[macro_use] 33 | pub mod pin; 34 | pub mod bipqueue; 35 | pub mod linker; 36 | pub mod ranges; 37 | 38 | pub use self::{binary_pretty_print::BinaryPrettyPrint, init_guard::InitGuard}; 39 | 40 | /// This macro should be called at the beginning of functions that create logic errors if they are 41 | /// called more than once. Most commonly this is used for initialization functions. 42 | pub macro assert_first_call 43 | { 44 | () => 45 | { 46 | assert_first_call!("ASSERTION FAILED: function has already been called"); 47 | }, 48 | 49 | ($($arg:tt)+) => 50 | {{ 51 | fn assert_first_call() 52 | { 53 | use $crate::core_reexport::sync::atomic::{AtomicBool, 54 | ATOMIC_BOOL_INIT, 55 | Ordering}; 56 | 57 | static CALLED : AtomicBool = ATOMIC_BOOL_INIT; 58 | let called = CALLED.swap(true, Ordering::Relaxed); 59 | assert!(!called, $($arg)+); 60 | } 61 | assert_first_call(); 62 | }} 63 | } 64 | 65 | /* 66 | * These are used in macros to prevent weird issues if the user crate does something weird like re-exports 67 | * another crate as `core` or `alloc`. 68 | */ 69 | #[doc(hidden)] 70 | pub mod core_reexport { 71 | #[doc(hidden)] 72 | pub use core::*; 73 | } 74 | -------------------------------------------------------------------------------- /lib/mulch/src/linker.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | extern "C" { 7 | /// `LinkerSymbol` is an extern type that represents a symbol defined by the linker. It is entirely opaque to 8 | /// the Rust type system, and cannot be instantiated, which aims to avoid mistakes where it is taken by value 9 | /// instead of by reference. 10 | /// 11 | /// Symbols can be defined with something like, and accessed only via the `LinkerSymbol::ptr` method: 12 | /// ```ignore 13 | /// extern "C" { 14 | /// static _stack_top: LinkerSymbol; 15 | /// } 16 | /// let stack_top: *const u8 = _stack_top.ptr(); 17 | /// ``` 18 | pub type LinkerSymbol; 19 | } 20 | 21 | impl LinkerSymbol { 22 | pub fn ptr(&'static self) -> *const u8 { 23 | self as *const Self as *const u8 24 | } 25 | } 26 | 27 | unsafe impl Send for LinkerSymbol {} 28 | unsafe impl Sync for LinkerSymbol {} 29 | -------------------------------------------------------------------------------- /lib/mulch/src/math.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | use core::ops; 7 | use num_traits::PrimInt; 8 | 9 | // TODO: feels like something like this should exist in `num_traits` or something, but if it does I couldn't find 10 | // it 11 | pub trait PowerOfTwoable { 12 | fn is_power_of_two(self) -> bool; 13 | fn next_power_of_two(self) -> Self; 14 | } 15 | 16 | macro impl_power_of_twoable($type:ty) { 17 | impl PowerOfTwoable for $type { 18 | fn is_power_of_two(self) -> bool { 19 | self.is_power_of_two() 20 | } 21 | 22 | fn next_power_of_two(self) -> Self { 23 | self.next_power_of_two() 24 | } 25 | } 26 | } 27 | 28 | impl_power_of_twoable!(u8); 29 | impl_power_of_twoable!(u16); 30 | impl_power_of_twoable!(u32); 31 | impl_power_of_twoable!(u64); 32 | impl_power_of_twoable!(usize); 33 | 34 | pub fn align_down(value: T, align: T) -> T { 35 | assert!(align == T::zero() || align.is_power_of_two()); 36 | 37 | if align == T::zero() { 38 | value 39 | } else { 40 | /* 41 | * Alignment must be a power of two. 42 | * 43 | * E.g. 44 | * align = 0b00001000 45 | * align-1 = 0b00000111 46 | * !(align-1) = 0b11111000 47 | * ^^^ Masks the value to the one below it with the correct align 48 | */ 49 | value & !(align - T::one()) 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_align_down() { 55 | assert_eq!(align_down(17u64, 0), 17); 56 | assert_eq!(align_down(17u64, 1), 17); 57 | assert_eq!(align_down(9u64, 8), 8); 58 | assert_eq!(align_down(19u64, 8), 16); 59 | assert_eq!(align_down(1025u64, 16), 1024); 60 | } 61 | 62 | pub fn align_up(value: T, align: T) -> T { 63 | if align == T::zero() { 64 | value 65 | } else { 66 | align_down(value + align - T::one(), align) 67 | } 68 | } 69 | 70 | #[test] 71 | fn test_align_up() { 72 | assert_eq!(align_up(17u64, 0), 17); 73 | assert_eq!(align_up(43u64, 1), 43); 74 | assert_eq!(align_up(9u64, 8), 16); 75 | assert_eq!(align_up(1023u64, 16), 1024); 76 | } 77 | 78 | /// Divide `x` by `divide_by`, taking the ceiling if it does not divide evenly. 79 | pub fn ceiling_integer_divide(x: usize, divide_by: usize) -> usize { 80 | x / divide_by + if x % divide_by != 0 { 1 } else { 0 } 81 | } 82 | 83 | #[test] 84 | fn test_ceiling_integer_divide() { 85 | assert_eq!(ceiling_integer_divide(1, 1), 1); 86 | assert_eq!(ceiling_integer_divide(10, 5), 2); 87 | assert_eq!(ceiling_integer_divide(11, 5), 3); 88 | assert_eq!(ceiling_integer_divide(0, 5), 0); 89 | } 90 | 91 | pub fn abs_difference(a: T, b: T) -> T 92 | where 93 | T: Ord + ops::Sub, 94 | { 95 | if a > b { 96 | a - b 97 | } else { 98 | b - a 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/mulch/src/pin.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018, The pin-utils authors 3 | * Copyright 2022, Isaac Woods 4 | * SPDX-License-Identifier: MIT OR Apache-2.0 5 | */ 6 | 7 | //! This module includes some macros for more easily working with pinned types. It takes inspiration from the 8 | //! `pin-utils` crate, but extends it to provide custom visibility on the created methods. 9 | 10 | /// A pinned projection of a struct field. 11 | /// 12 | /// To make using this macro safe, three things need to be ensured: 13 | /// - If the struct implements [`Drop`], the [`drop`] method is not allowed to move the value of the field. 14 | /// - If the struct wants to implement [`Unpin`], it has to do so conditionally: The struct can only implement 15 | /// [`Unpin`] if the field's type is [`Unpin`]. 16 | /// - The struct must not be `#[repr(packed)]`. 17 | /// 18 | /// ``` 19 | /// use mulch::unsafe_pinned; 20 | /// use core::marker::Unpin; 21 | /// use core::pin::Pin; 22 | /// 23 | /// struct Foo { 24 | /// field: T, 25 | /// } 26 | /// 27 | /// impl Foo { 28 | /// unsafe_pinned!(field: T); 29 | /// 30 | /// fn baz(mut self: Pin<&mut Self>) { 31 | /// let _: Pin<&mut T> = self.field(); 32 | /// } 33 | /// } 34 | /// 35 | /// impl Unpin for Foo {} 36 | /// ``` 37 | /// 38 | /// Note that borrowing the field multiple times requires using `.as_mut()` to 39 | /// avoid consuming the `Pin`. 40 | /// 41 | /// [`Unpin`]: core::marker::Unpin 42 | /// [`drop`]: Drop::drop 43 | #[macro_export] 44 | macro_rules! unsafe_pinned { 45 | ($v:vis $f:ident: $t:ty) => { 46 | #[allow(unsafe_code)] 47 | $v fn $f<'__a>( 48 | self: $crate::core_reexport::pin::Pin<&'__a mut Self>, 49 | ) -> $crate::core_reexport::pin::Pin<&'__a mut $t> { 50 | unsafe { $crate::core_reexport::pin::Pin::map_unchecked_mut(self, |x| &mut x.$f) } 51 | } 52 | }; 53 | } 54 | 55 | /// An unpinned projection of a struct field. 56 | /// 57 | /// This macro is unsafe because it creates a method that returns a normal 58 | /// non-pin reference to the struct field. It is up to the programmer to ensure 59 | /// that the contained value can be considered not pinned in the current 60 | /// context. 61 | /// 62 | /// Note that borrowing the field multiple times requires using `.as_mut()` to 63 | /// avoid consuming the `Pin`. 64 | /// 65 | /// ``` 66 | /// use mulch::unsafe_unpinned; 67 | /// use core::pin::Pin; 68 | /// 69 | /// struct Bar; 70 | /// struct Foo { 71 | /// field: Bar, 72 | /// } 73 | /// 74 | /// impl Foo { 75 | /// unsafe_unpinned!(field: Bar); 76 | /// 77 | /// fn baz(mut self: Pin<&mut Self>) { 78 | /// let _: &mut Bar = self.field(); 79 | /// } 80 | /// } 81 | /// ``` 82 | #[macro_export] 83 | macro_rules! unsafe_unpinned { 84 | ($v:vis $f:ident: $t:ty) => { 85 | #[allow(unsafe_code)] 86 | $v fn $f<'__a>(self: $crate::core_reexport::pin::Pin<&'__a mut Self>) -> &'__a mut $t { 87 | unsafe { &mut $crate::core_reexport::pin::Pin::get_unchecked_mut(self).$f } 88 | } 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /lib/picotoml/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "picotoml" 3 | version = "0.1.0" 4 | description = "A `no_std` TOML deserializer build for embedded systems. Can be used without an allocator" 5 | authors = ["Isaac Woods"] 6 | license = "MIT/Apache-2.0" 7 | edition = "2021" 8 | 9 | [dependencies] 10 | logos = { version = "0.13.0", default-features = false, features = ["export_derive"] } 11 | serde = { version = "1.0.188", default-features = false } 12 | -------------------------------------------------------------------------------- /lib/picotoml/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (C) 2019 foresterre 4 | Copyright (C) 2021 Troy Neubauer 5 | Copyright (C) 2023 Isaac Woods 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /lib/picotoml/README.md: -------------------------------------------------------------------------------- 1 | # `picotoml` 2 | A `no_std` TOML deserializer for `serde`. Forked from [`TroyNeubauer/minimal-toml`](https://github.com/TroyNeubauer/minimal-toml), 3 | which has not been published on crates.io, plus elements of its fork of its `peekmore` dependency. 4 | 5 | ## Example Usage 6 | 7 | Cargo.toml: 8 | ```toml 9 | [dependencies] 10 | picotoml = "0.1.0" 11 | serde = { version = "1.0", features = ["derive"] } 12 | ``` 13 | 14 | main.rs: 15 | ```rust 16 | use serde::Deserialize; 17 | 18 | #[derive(Debug, Deserialize)] 19 | pub struct MyConfig { 20 | pub app_name: String, 21 | pub version: u32, 22 | pub enable: bool, 23 | pub options: Option, 24 | 25 | pub users: Vec, 26 | pub scores: Vec, 27 | 28 | pub server: Server, 29 | } 30 | 31 | #[derive(Debug, Deserialize)] 32 | pub struct Server { 33 | pub ip: String, 34 | pub port: u16, 35 | } 36 | 37 | const MY_CONFIG_TOML: &str = r#" 38 | app_name = "MyApp" 39 | version = 1 40 | enable = true 41 | options = "SomeOption" 42 | 43 | users = ["Alice", "Bob", "Charlie"] 44 | scores = [100, 200, 300] 45 | 46 | [server] 47 | ip = "127.0.0.1" 48 | port = 8080 49 | "#; 50 | 51 | fn main() { 52 | let config = picotoml::from_str::(MY_CONFIG_TOML).unwrap(); 53 | dbg!(config); 54 | } 55 | ``` 56 | 57 | This will output: 58 | ``` 59 | [src/main.rs:38:5] config = MyConfig { 60 | app_name: "MyApp", 61 | version: 1, 62 | enable: true, 63 | options: Some( 64 | "SomeOption", 65 | ), 66 | users: [ 67 | "Alice", 68 | "Bob", 69 | "Charlie", 70 | ], 71 | scores: [ 72 | 100, 73 | 200, 74 | 300, 75 | ], 76 | server: Server { 77 | ip: "127.0.0.1", 78 | port: 8080, 79 | }, 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /lib/picotoml/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A `no_std` TOML deserializer build for embedded systems. Can be used without an allocator. 2 | 3 | #![no_std] 4 | 5 | extern crate alloc; 6 | 7 | #[cfg(test)] 8 | extern crate std; 9 | 10 | mod de; 11 | mod error; 12 | mod lexer; 13 | mod peeking; 14 | 15 | pub use de::{from_str, Deserializer}; 16 | pub use error::{Error, ErrorKind, Expected}; 17 | -------------------------------------------------------------------------------- /lib/poplar/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poplar" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | license = "MIT OR Apache-2.0" 7 | description = "Rust interace to the Poplar kernel from userspace" 8 | 9 | [dependencies] 10 | bit_field = "0.10.2" 11 | bitflags = "2.4.0" 12 | cfg-if = "0.1" 13 | log = { version = "0.4", optional = true } 14 | pci_types = { path = "../pci_types", optional = true } 15 | ptah = { path = "../ptah", optional = true, default-features = false } 16 | linked_list_allocator = { version = "0.10.5", optional = true, features = ["alloc_ref"] } 17 | mulch = { path = "../mulch" } 18 | maitake = { git = "https://github.com/hawkw/mycelium", optional = true, features = ["alloc", "tracing-02"] } 19 | spinning_top = "0.3.0" 20 | 21 | [features] 22 | default = ["can_alloc", "async"] 23 | can_alloc = ["log", "ptah", "ptah/alloc", "ptah/derive"] 24 | ddk = ["pci_types", "linked_list_allocator"] 25 | async = ["can_alloc", "maitake"] 26 | -------------------------------------------------------------------------------- /lib/poplar/src/ddk/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dma; 2 | pub mod pci; 3 | -------------------------------------------------------------------------------- /lib/poplar/src/ddk/pci.rs: -------------------------------------------------------------------------------- 1 | use crate::{syscall::pci::PciGetInfoError, Handle}; 2 | use pci_types::{BaseClass, DeviceId, DeviceRevision, Interface, PciAddress, SubClass, VendorId}; 3 | 4 | #[derive(Debug, Default)] 5 | #[repr(C)] 6 | pub struct PciDeviceInfo { 7 | pub address: PciAddress, 8 | /// The ID of the manufacturer of the device. These are allocated by PCI SIG. 9 | pub vendor_id: VendorId, 10 | /// The ID of the particular device. These are allocated by the vendor. 11 | pub device_id: DeviceId, 12 | /// A device-specific revision identifier. These are chosen by the vendor, and should be thought of as a 13 | /// vendor-defined extension of the device ID. 14 | pub revision: DeviceRevision, 15 | /// The upper byte of the class-code. This identifies the Base Class of the device. 16 | pub class: BaseClass, 17 | /// The middle byte of the class-code. This identifies the Sub Class of the device. 18 | pub sub_class: SubClass, 19 | /// The lower byte of the class-code. This may indicate a specific register-level programming interface of the 20 | /// device. 21 | pub interface: Interface, 22 | pub bars: [Option; 6], 23 | /// A handle to an `Interrupt` that is signalled when this PCI device issues an interrupt. 24 | pub interrupt: Option, 25 | } 26 | 27 | #[derive(Debug)] 28 | #[repr(C)] 29 | pub enum Bar { 30 | Memory32 { memory_object: Handle, size: u32 }, 31 | Memory64 { memory_object: Handle, size: u64 }, 32 | } 33 | 34 | pub fn pci_get_info_slice(buffer: &mut [PciDeviceInfo]) -> Result<&mut [PciDeviceInfo], PciGetInfoError> { 35 | match crate::syscall::pci_get_info( 36 | if buffer.len() == 0 { 0x0 as *mut u8 } else { buffer.as_mut_ptr() as *mut u8 }, 37 | buffer.len(), 38 | ) { 39 | Ok(valid_entries) => Ok(&mut buffer[0..valid_entries]), 40 | Err(err) => Err(err), 41 | } 42 | } 43 | 44 | #[cfg(feature = "can_alloc")] 45 | pub fn pci_get_info_vec() -> Result, PciGetInfoError> { 46 | use alloc::vec::Vec; 47 | 48 | // Make an initial call to find out how many descriptors there are 49 | let num_descriptors = match crate::syscall::pci_get_info(0x0 as *mut u8, 0) { 50 | Ok(_) => panic!("pci_get_info with null buffer succeeded."), 51 | Err(PciGetInfoError::BufferNotLargeEnough(num_descriptors)) => num_descriptors as usize, 52 | Err(err) => return Err(err), 53 | }; 54 | 55 | // Then actually fetch the data 56 | let mut descriptors: Vec = Vec::with_capacity(num_descriptors); 57 | assert_eq!( 58 | crate::syscall::pci_get_info(descriptors.as_mut_ptr() as *mut u8, num_descriptors)?, 59 | num_descriptors 60 | ); 61 | unsafe { 62 | descriptors.set_len(num_descriptors); 63 | } 64 | 65 | Ok(descriptors) 66 | } 67 | -------------------------------------------------------------------------------- /lib/poplar/src/early_logger.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use core::fmt::Write; 3 | use log::{Log, Metadata, Record}; 4 | 5 | pub struct EarlyLogger; 6 | 7 | impl Log for EarlyLogger { 8 | fn enabled(&self, _metadata: &Metadata) -> bool { 9 | true 10 | } 11 | 12 | fn log(&self, record: &Record) { 13 | if self.enabled(record.metadata()) { 14 | let mut s = String::new(); 15 | write!(s, "{}", record.args()).unwrap(); 16 | crate::syscall::early_log(&s).unwrap(); 17 | } 18 | } 19 | 20 | fn flush(&self) {} 21 | } 22 | -------------------------------------------------------------------------------- /lib/poplar/src/event.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | syscall::{self, WaitForEventError}, 3 | Handle, 4 | }; 5 | use core::{future::Future, task::Poll}; 6 | 7 | pub struct Event(Handle); 8 | 9 | impl Event { 10 | pub fn new_from_handle(handle: Handle) -> Event { 11 | Event(handle) 12 | } 13 | 14 | pub fn wait_for_event(&self) -> impl Future + '_ { 15 | core::future::poll_fn(|context| { 16 | /* 17 | * We call `wait_for_event`, but don't allow it to block. This effectively just clears 18 | * the event if there is one pending to be handled - the async side handles waiting for 19 | * events through `poll_interest` via the reactor. 20 | */ 21 | match syscall::wait_for_event(self.0, false) { 22 | Ok(()) => Poll::Ready(()), 23 | Err(WaitForEventError::NoEvent) => { 24 | crate::rt::RUNTIME.get().reactor.lock().register(self.0, context.waker().clone()); 25 | Poll::Pending 26 | } 27 | Err(other) => panic!("Error waiting for event: {:?}", other), 28 | } 29 | }) 30 | } 31 | 32 | pub fn wait_for_event_blocking(&self) { 33 | syscall::wait_for_event(self.0, true).unwrap(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/poplar/src/interrupt.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | syscall::{self, WaitForInterruptError}, 3 | Handle, 4 | }; 5 | use core::{future::Future, task::Poll}; 6 | 7 | pub struct Interrupt(Handle); 8 | 9 | impl Interrupt { 10 | pub fn new_from_handle(handle: Handle) -> Interrupt { 11 | Interrupt(handle) 12 | } 13 | 14 | pub fn wait_for_interrupt(&self) -> impl Future + '_ { 15 | core::future::poll_fn(|context| { 16 | /* 17 | * We call `wait_for_interrupt`, but don't allow it to block. This effectively just clears 18 | * the interrupt if there is one pending to be handled - the async side handles waiting for 19 | * events through `poll_interest` via the reactor. 20 | */ 21 | match syscall::wait_for_interrupt(self.0, false) { 22 | Ok(()) => Poll::Ready(()), 23 | Err(WaitForInterruptError::NoInterrupt) => { 24 | crate::rt::RUNTIME.get().reactor.lock().register(self.0, context.waker().clone()); 25 | Poll::Pending 26 | } 27 | Err(other) => panic!("Error waiting for interrupt: {:?}", other), 28 | } 29 | }) 30 | } 31 | 32 | pub fn wait_for_interrupt_blocking(&self) { 33 | syscall::wait_for_interrupt(self.0, true).unwrap(); 34 | } 35 | 36 | pub fn ack(&self) { 37 | if let Err(err) = syscall::ack_interrupt(self.0) { 38 | panic!("Issue acknowledging interrupt: {:?}", err); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/poplar/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(decl_macro, never_type, allocator_api, ptr_as_uninit)] 3 | #![deny(unsafe_op_in_unsafe_fn)] 4 | 5 | #[cfg(feature = "can_alloc")] 6 | extern crate alloc; 7 | 8 | #[cfg(feature = "can_alloc")] 9 | pub mod channel; 10 | #[cfg(feature = "ddk")] 11 | pub mod ddk; 12 | #[cfg(feature = "can_alloc")] 13 | pub mod early_logger; 14 | pub mod event; 15 | pub mod interrupt; 16 | pub mod manifest; 17 | pub mod memory_object; 18 | #[cfg(feature = "async")] 19 | pub mod rt; 20 | pub mod syscall; 21 | 22 | use core::num::TryFromIntError; 23 | 24 | /// A `Handle` is used to represent a task's access to a kernel object. It is allocated by the kernel and is unique 25 | /// to the task to which it is issued - a kernel object can have handles in multiple tasks (and the numbers will 26 | /// not be shared between those tasks). 27 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] 28 | pub struct Handle(pub u32); 29 | 30 | impl Handle { 31 | pub const ZERO: Handle = Handle(0); 32 | } 33 | 34 | /* 35 | * Often, handles are passed in single syscall parameters, and need to be turned into `Handle`s fallibly. 36 | * XXX: this cannot be used to convert `Result` types that contain handles - it simply does the bounds check! 37 | */ 38 | impl TryFrom for Handle { 39 | type Error = TryFromIntError; 40 | 41 | fn try_from(value: usize) -> Result { 42 | Ok(Handle(u32::try_from(value)?)) 43 | } 44 | } 45 | 46 | #[cfg(feature = "ptah")] 47 | impl ptah::Serialize for Handle { 48 | fn serialize(&self, serializer: &mut ptah::Serializer) -> ptah::ser::Result<()> 49 | where 50 | W: ptah::Writer, 51 | { 52 | serializer.serialize_handle(self.0 as ptah::Handle) 53 | } 54 | } 55 | 56 | #[cfg(feature = "ptah")] 57 | impl<'de> ptah::Deserialize<'de> for Handle { 58 | fn deserialize(deserializer: &mut ptah::Deserializer<'de>) -> ptah::de::Result { 59 | Ok(Handle(deserializer.deserialize_handle()?)) 60 | } 61 | } 62 | 63 | // TODO: I don't think rights are implemented at all are they? Work out if we want them / remove 64 | // this. 65 | bitflags::bitflags! { 66 | struct HandleRights: u32 { 67 | /// Whether the handle's owner can use it to modify the kernel object it points to. What is means to 68 | /// "modify" a kernel object differs depending on the type of the kernel object. 69 | const MODIFY = 0b1; 70 | /// Whether the handle can be duplicated. 71 | const DUPLICATE = 0b10; 72 | /// Whether the handle can be transferred over a `Channel`. 73 | const TRANSFER = 0b100; 74 | /// For `MemoryObject`s, whether the memory can be mapped into the handle owner's `AddressSpace`. 75 | const MAP = 0x1000; 76 | /// For `Channel` ends, whether the `send_message` system call can be used on this `Channel` end. 77 | const SEND = 0x1_0000; 78 | /// For `Channel` ends, whether the `receive_message` & co. system calls can be used on this `Channel` end. 79 | const RECEIVE = 0x10_0000; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/poplar/src/manifest.rs: -------------------------------------------------------------------------------- 1 | //! Each task is passed a 'manifest' when it is started that details the handles the task has been 2 | //! created with, boot arguments, etc. This is encoded using Ptah. 3 | 4 | use alloc::{string::String, vec::Vec}; 5 | use ptah::{Deserialize, Serialize}; 6 | 7 | #[derive(Clone, Debug, Serialize, Deserialize)] 8 | pub struct BootstrapManifest { 9 | pub task_name: String, 10 | pub boot_tasks: Vec, 11 | } 12 | 13 | #[derive(Clone, Debug, Serialize, Deserialize)] 14 | pub struct BootTask { 15 | pub name: String, 16 | pub entry_point: usize, 17 | /// The segments that should be loaded into the task's address space. In the format `(virtual 18 | /// address, handle to MemoryObject)`. 19 | pub segments: Vec<(usize, u32)>, 20 | } 21 | -------------------------------------------------------------------------------- /lib/poplar/src/rt/mod.rs: -------------------------------------------------------------------------------- 1 | //! Poplar's `async` runtime. This provides an executor based on 2 | //! [`maitake`](https://github.com/hawkw/mycelium/tree/main/maitake) and a reactor compatible with 3 | //! Poplar's system call layer. 4 | 5 | mod reactor; 6 | 7 | pub use maitake; 8 | 9 | use self::reactor::Reactor; 10 | use core::future::Future; 11 | use maitake::{scheduler::Scheduler, task::JoinHandle}; 12 | use mulch::InitGuard; 13 | use spinning_top::Spinlock; 14 | 15 | // TODO: if we want support for multiple tasks in an address space, this needs to be thread-local 16 | pub(crate) static RUNTIME: InitGuard = InitGuard::uninit(); 17 | 18 | pub struct Runtime { 19 | scheduler: Scheduler, 20 | // TODO: maintain a timer wheel so time-based futures work in userspace 21 | pub reactor: Spinlock, 22 | } 23 | 24 | pub fn init_runtime() { 25 | RUNTIME.initialize(Runtime { scheduler: Scheduler::new(), reactor: Spinlock::new(Reactor::new()) }); 26 | } 27 | 28 | pub fn enter_loop() { 29 | loop { 30 | crate::syscall::yield_to_kernel(); 31 | 32 | let runtime = RUNTIME.get(); 33 | runtime.reactor.lock().poll(); 34 | runtime.scheduler.tick(); 35 | } 36 | } 37 | 38 | pub fn spawn(future: F) -> JoinHandle 39 | where 40 | F: Future + Send + 'static, 41 | F::Output: Send + 'static, 42 | { 43 | RUNTIME.get().scheduler.spawn(future) 44 | } 45 | -------------------------------------------------------------------------------- /lib/poplar/src/rt/reactor.rs: -------------------------------------------------------------------------------- 1 | use crate::Handle; 2 | use alloc::{collections::BTreeMap, vec::Vec}; 3 | use core::task::Waker; 4 | 5 | /// The `Reactor` is a component of the Poplar userspace async runtime that processes events from 6 | /// kernel objects in order to wake futures when they have work to do. 7 | pub struct Reactor { 8 | interests: BTreeMap, 9 | } 10 | 11 | impl Reactor { 12 | pub fn new() -> Reactor { 13 | Reactor { interests: BTreeMap::new() } 14 | } 15 | 16 | pub fn register(&mut self, handle: Handle, waker: Waker) { 17 | self.interests.insert(handle, waker); 18 | } 19 | 20 | pub fn poll(&mut self) { 21 | /* 22 | * Make a copy of the current list of handles we're interested in. We do this so we can 23 | * later remove events that have been awoken. 24 | */ 25 | let handles: Vec = self.interests.keys().copied().collect(); 26 | 27 | for handle in handles { 28 | if crate::syscall::poll_interest(handle).unwrap() { 29 | let waker = self.interests.remove(&handle).unwrap(); 30 | waker.wake(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/poplar/src/syscall/get_framebuffer.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | raw, 3 | result::{define_error_type, handle_from_syscall_repr}, 4 | SYSCALL_GET_FRAMEBUFFER, 5 | }; 6 | use crate::Handle; 7 | 8 | define_error_type!(GetFramebufferError { 9 | /// The calling task does not have the correct capability to access the framebuffer. 10 | AccessDenied => 1, 11 | 12 | /// The address passed in `a` to write the info struct into was invalid. 13 | InfoAddressIsInvalid => 2, 14 | 15 | /// The kernel did not create a framebuffer. 16 | NoFramebufferCreated => 3, 17 | }); 18 | 19 | /// Describes how the supplied framebuffer represents pixels. 20 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 21 | #[repr(u8)] 22 | pub enum PixelFormat { 23 | Rgb32 = 0, 24 | Bgr32 = 1, 25 | } 26 | 27 | #[derive(Clone, Copy, Debug)] 28 | #[repr(C)] 29 | pub struct FramebufferInfo { 30 | pub width: u16, 31 | pub height: u16, 32 | pub stride: u16, 33 | pub pixel_format: PixelFormat, 34 | } 35 | 36 | pub fn get_framebuffer(info: *mut FramebufferInfo) -> Result { 37 | handle_from_syscall_repr(unsafe { raw::syscall1(SYSCALL_GET_FRAMEBUFFER, info as usize) }) 38 | } 39 | -------------------------------------------------------------------------------- /lib/poplar/src/syscall/pci.rs: -------------------------------------------------------------------------------- 1 | use super::{raw, SYSCALL_PCI_GET_INFO}; 2 | use bit_field::BitField; 3 | 4 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 5 | pub enum PciGetInfoError { 6 | TaskDoesNotHaveCorrectCapability, 7 | BufferPointerInvalid, 8 | BufferNotLargeEnough(u32), 9 | PlatformDoesNotSupportPci, 10 | } 11 | 12 | // TODO: it would be cool if we could do this with the define_error_type macro 13 | impl TryFrom for PciGetInfoError { 14 | type Error = (); 15 | 16 | fn try_from(status: usize) -> Result { 17 | match status.get_bits(0..16) { 18 | 1 => Ok(Self::TaskDoesNotHaveCorrectCapability), 19 | 2 => Ok(Self::BufferPointerInvalid), 20 | 3 => Ok(Self::BufferNotLargeEnough(status.get_bits(16..48) as u32)), 21 | 4 => Ok(Self::PlatformDoesNotSupportPci), 22 | _ => Err(()), 23 | } 24 | } 25 | } 26 | 27 | impl Into for PciGetInfoError { 28 | fn into(self) -> usize { 29 | match self { 30 | Self::TaskDoesNotHaveCorrectCapability => 1, 31 | Self::BufferPointerInvalid => 2, 32 | Self::BufferNotLargeEnough(num_needed) => { 33 | let mut result = 3; 34 | result.set_bits(16..48, num_needed as usize); 35 | result 36 | } 37 | Self::PlatformDoesNotSupportPci => 4, 38 | } 39 | } 40 | } 41 | 42 | /// Makes a raw `pci_get_info` system call, given a pointer to a buffer and the size of the buffer. On success, 43 | /// returns the number of entries written into the buffer. For a nicer interface to this system call, see 44 | /// [`crate::ddk::pci::pci_get_info_slice`] or [`crate::ddk::pci::pci_get_info_vec`] - these are 45 | /// part of the DDK to avoid pulling the `pci_types` crate into everything that uses this crate. 46 | pub fn pci_get_info(buffer_ptr: *mut u8, buffer_size: usize) -> Result { 47 | let result = unsafe { raw::syscall2(SYSCALL_PCI_GET_INFO, buffer_ptr as usize, buffer_size) }; 48 | 49 | if result.get_bits(0..16) == 0 { 50 | Ok(result.get_bits(16..48)) 51 | } else { 52 | Err(PciGetInfoError::try_from(result).unwrap()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/poplar/src/syscall/raw_riscv.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | pub unsafe fn syscall0(number: usize) -> usize { 4 | let result: usize; 5 | unsafe { 6 | asm!("ecall", inlateout("a0") number => result); 7 | } 8 | result 9 | } 10 | 11 | pub unsafe fn syscall1(number: usize, a: usize) -> usize { 12 | let result: usize; 13 | unsafe { 14 | asm!("ecall", inlateout("a0") number => result, in("a1") a); 15 | } 16 | result 17 | } 18 | 19 | pub unsafe fn syscall2(number: usize, a: usize, b: usize) -> usize { 20 | let result: usize; 21 | unsafe { 22 | asm!("ecall", inlateout("a0") number => result, in("a1") a, in("a2") b); 23 | } 24 | result 25 | } 26 | 27 | pub unsafe fn syscall3(number: usize, a: usize, b: usize, c: usize) -> usize { 28 | let result: usize; 29 | unsafe { 30 | asm!("ecall", inlateout("a0") number => result, in("a1") a, in("a2") b, in("a3") c); 31 | } 32 | result 33 | } 34 | 35 | pub unsafe fn syscall4(number: usize, a: usize, b: usize, c: usize, d: usize) -> usize { 36 | let result: usize; 37 | unsafe { 38 | asm!("ecall", inlateout("a0") number => result, in("a1") a, in("a2") b, in("a3") c, in("a4") d); 39 | } 40 | result 41 | } 42 | 43 | pub unsafe fn syscall5(number: usize, a: usize, b: usize, c: usize, d: usize, e: usize) -> usize { 44 | let result: usize; 45 | unsafe { 46 | asm!("ecall", inlateout("a0") number => result, in("a1") a, in("a2") b, in("a3") c, in("a4") d, in("a5") e); 47 | } 48 | result 49 | } 50 | -------------------------------------------------------------------------------- /lib/poplar/src/syscall/result.rs: -------------------------------------------------------------------------------- 1 | use crate::Handle; 2 | use bit_field::BitField; 3 | 4 | pub(super) macro define_error_type($error_name:ident { 5 | $($(#[$attrib:meta])*$name:ident => $repr_num:expr),*$(,)? 6 | }) { 7 | #[derive(Clone, Copy, Debug)] 8 | pub enum $error_name { 9 | $( 10 | $(#[$attrib])* 11 | $name, 12 | )* 13 | } 14 | 15 | impl TryFrom for $error_name { 16 | type Error = (); 17 | 18 | fn try_from(status: usize) -> Result { 19 | match status { 20 | $( 21 | $repr_num => Ok(Self::$name), 22 | )* 23 | _ => Err(()), 24 | } 25 | } 26 | } 27 | 28 | impl Into for $error_name { 29 | fn into(self) -> usize { 30 | match self { 31 | $( 32 | Self::$name => $repr_num, 33 | )* 34 | } 35 | } 36 | } 37 | } 38 | 39 | pub fn status_from_syscall_repr(status: usize) -> Result<(), E> 40 | where 41 | E: TryFrom, 42 | { 43 | if status == 0 { 44 | Ok(()) 45 | } else { 46 | Err(E::try_from(status).expect("System call returned invalid status")) 47 | } 48 | } 49 | 50 | pub fn status_to_syscall_repr(result: Result<(), E>) -> usize 51 | where 52 | E: Into, 53 | { 54 | match result { 55 | Ok(()) => 0, 56 | Err(err) => err.into(), 57 | } 58 | } 59 | 60 | /// Convert a `Result` that carries a custom status on success. It is the producer's responsibility that the 61 | /// success status can be differentiated from an error, if needed. 62 | pub fn status_with_payload_to_syscall_repr(result: Result) -> usize 63 | where 64 | E: Into, 65 | { 66 | match result { 67 | Ok(status) => status, 68 | Err(err) => err.into(), 69 | } 70 | } 71 | 72 | pub fn handle_from_syscall_repr(result: usize) -> Result 73 | where 74 | E: TryFrom, 75 | { 76 | let status = result.get_bits(0..32); 77 | if status == 0 { 78 | Ok(Handle(result.get_bits(32..64) as u32)) 79 | } else { 80 | Err(E::try_from(status).expect("System call returned invalid result status")) 81 | } 82 | } 83 | 84 | pub fn handle_to_syscall_repr(result: Result) -> usize 85 | where 86 | E: Into, 87 | { 88 | match result { 89 | Ok(handle) => { 90 | let mut value = 0usize; 91 | value.set_bits(32..64, handle.0 as usize); 92 | value 93 | } 94 | Err(err) => { 95 | let mut value = 0usize; 96 | value.set_bits(0..32, err.into()); 97 | value 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/ptah/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["ptah_derive"] 3 | 4 | [package] 5 | name = "ptah" 6 | version = "0.2.1" 7 | authors = ["Isaac Woods"] 8 | edition = "2021" 9 | description = "Rust implementation of Ptah, Poplar's message-passing format" 10 | license = "MIT OR Apache-2.0" 11 | 12 | [dependencies] 13 | # ptah_derive = { version = "0.2.1", optional = true } 14 | ptah_derive = { path = "ptah_derive", optional = true } 15 | heapless = { version = "0.7.16", optional = true } 16 | 17 | [features] 18 | default = ["alloc", "derive"] 19 | derive = ["ptah_derive"] 20 | alloc = [] 21 | -------------------------------------------------------------------------------- /lib/ptah/ptah_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ptah_derive" 3 | version = "0.2.1" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | description = "Derive macro for Rust's implementation of Ptah, Poplar's message-passing format" 7 | license = "MIT OR Apache-2.0" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | proc-macro2 = "1" 14 | quote = "1" 15 | syn = "1" 16 | -------------------------------------------------------------------------------- /lib/ptah/ptah_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod de; 2 | mod ser; 3 | 4 | use proc_macro::TokenStream; 5 | use syn::{parse_macro_input, DeriveInput}; 6 | 7 | #[proc_macro_derive(Serialize)] 8 | pub fn derive_serialize(input: TokenStream) -> TokenStream { 9 | let input = parse_macro_input!(input as DeriveInput); 10 | ser::impl_serialize(input) 11 | } 12 | 13 | #[proc_macro_derive(Deserialize)] 14 | pub fn derive_deserialize(input: TokenStream) -> TokenStream { 15 | let input = parse_macro_input!(input as DeriveInput); 16 | de::impl_deserialize(input) 17 | } 18 | -------------------------------------------------------------------------------- /lib/ptah/src/de/heapless.rs: -------------------------------------------------------------------------------- 1 | use crate::de::{Deserialize, Deserializer, Result}; 2 | 3 | impl<'de, T, const N: usize> Deserialize<'de> for heapless::Vec 4 | where 5 | T: Deserialize<'de>, 6 | { 7 | fn deserialize(deserializer: &mut Deserializer<'de>) -> Result> { 8 | let length = deserializer.deserialize_seq_length()?; 9 | assert!(length as usize <= N); 10 | let mut vec = heapless::Vec::new(); 11 | 12 | for _ in 0..length { 13 | // TODO: either map error or use `push_unchecked` bc we check it explicitely 14 | vec.push(T::deserialize(deserializer)?); 15 | } 16 | 17 | Ok(vec) 18 | } 19 | } 20 | 21 | impl<'de, const N: usize> Deserialize<'de> for heapless::String { 22 | fn deserialize(deserializer: &mut Deserializer<'de>) -> Result> { 23 | Ok(heapless::String::from(deserializer.deserialize_str()?)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/ptah/src/ser/heapless.rs: -------------------------------------------------------------------------------- 1 | use crate::{ser::Result, Serialize, Serializer, Writer}; 2 | 3 | impl Serialize for heapless::Vec 4 | where 5 | T: Serialize, 6 | { 7 | fn serialize(&self, serializer: &mut Serializer) -> Result<()> 8 | where 9 | W: Writer, 10 | { 11 | let mut seq = serializer.serialize_seq(self.len() as u32)?; 12 | for element in self { 13 | seq.serialize_element(element)?; 14 | } 15 | Ok(()) 16 | } 17 | } 18 | 19 | impl Serialize for heapless::String { 20 | fn serialize(&self, serializer: &mut Serializer) -> Result<()> 21 | where 22 | W: Writer, 23 | { 24 | serializer.serialize_str(self) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "std" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | poplar = { path = "../poplar" } 9 | linked_list_allocator = "0.10.5" 10 | spinning_top = "0.3.0" 11 | 12 | [features] 13 | ddk = ["poplar/ddk"] 14 | async = ["poplar/async"] 15 | -------------------------------------------------------------------------------- /lib/std/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs::File, io::Write, path::PathBuf}; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | 6 | // Put the linker script somewhere the linker can find it. We can then specify the linker script with just 7 | // `link-arg=-Tlink.ld`. 8 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); 9 | match env::var_os("TARGET").unwrap().to_str() { 10 | Some("x86_64-poplar") => { 11 | println!("cargo:rerun-if-changed=x64.x"); 12 | File::create(out.join("link.ld")).unwrap().write_all(include_bytes!("x64.ld")).unwrap(); 13 | } 14 | Some("riscv64gc-unknown-none-elf") => { 15 | println!("cargo:rerun-if-changed=rv64.x"); 16 | File::create(out.join("link.ld")).unwrap().write_all(include_bytes!("rv64.ld")).unwrap(); 17 | } 18 | _ => panic!("Building Poplar `std` for unsupported target!"), 19 | } 20 | println!("cargo:rustc-link-search={}", out.display()); 21 | } 22 | -------------------------------------------------------------------------------- /lib/std/rv64.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_ARCH("riscv") 3 | OUTPUT_FORMAT("elf64-littleriscv") 4 | 5 | IMAGE_START = 0x10000; 6 | 7 | PHDRS { 8 | text PT_LOAD; 9 | rodata PT_LOAD FLAGS(4); 10 | data PT_LOAD; 11 | tls PT_TLS; 12 | } 13 | 14 | SECTIONS { 15 | . = IMAGE_START; 16 | 17 | .text : { 18 | *(.text .text.*) 19 | . = ALIGN(4K); 20 | } :text 21 | 22 | .rodata : { 23 | *(.rodata .rodata.*) 24 | /* No need to align, because .got is aligned below */ 25 | } :rodata 26 | 27 | .got : { 28 | *(.got) 29 | . = ALIGN(4K); 30 | } :rodata 31 | 32 | .data : { 33 | *(.data .data.*) 34 | } :data 35 | 36 | PROVIDE(__global_pointer$ = .); 37 | 38 | .sdata : { 39 | *(.sdata .sdata.*) 40 | } 41 | 42 | .sbss : { 43 | *(.sbss .sbss.*) 44 | } :data 45 | 46 | .bss : { 47 | *(.bss .bss.*) 48 | . = ALIGN(4K); 49 | } :data 50 | 51 | .tdata : { 52 | *(.tdata .tdata.*) 53 | } :tls 54 | 55 | .tbss : { 56 | *(.tbss .tbss.*) 57 | } :tls 58 | 59 | /DISCARD/ : { 60 | *(.eh_frame*) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/std/src/alloc.rs: -------------------------------------------------------------------------------- 1 | pub use alloc_crate::alloc::*; 2 | 3 | use core::ptr::NonNull; 4 | use linked_list_allocator::LockedHeap; 5 | use poplar::{ 6 | memory_object::{MappedMemoryObject, MemoryObject}, 7 | syscall::MemoryObjectFlags, 8 | }; 9 | use spinning_top::Spinlock; 10 | 11 | /// Virtual address to put the heap at. This could be dynamically chosen in the future. 12 | const HEAP_START: usize = 0x600000000; 13 | /// The size of the heap on the first allocation made 14 | const INITIAL_HEAP_SIZE: usize = 0x4000; 15 | 16 | #[global_allocator] 17 | static ALLOCATOR: ManagedHeap = ManagedHeap::empty(); 18 | 19 | struct ManagedHeap { 20 | inner: LockedHeap, 21 | mapped_heap: Spinlock>, 22 | } 23 | 24 | impl ManagedHeap { 25 | const fn empty() -> ManagedHeap { 26 | ManagedHeap { inner: LockedHeap::empty(), mapped_heap: Spinlock::new(None) } 27 | } 28 | } 29 | 30 | unsafe impl GlobalAlloc for ManagedHeap { 31 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 32 | let attempted_alloc = self.inner.lock().allocate_first_fit(layout); 33 | if let Ok(ptr) = attempted_alloc { 34 | ptr.as_ptr() 35 | } else { 36 | /* 37 | * The allocation failed. Initialize the heap if it hasn't been already, or try to 38 | * extend it if it has. 39 | */ 40 | if self.mapped_heap.lock().is_none() { 41 | let initial_size = usize::max(INITIAL_HEAP_SIZE, layout.size()); 42 | let memory = MemoryObject::create(initial_size, MemoryObjectFlags::WRITABLE).unwrap(); 43 | *self.mapped_heap.lock() = Some(memory.map_at(HEAP_START).unwrap()); 44 | self.inner.lock().init(HEAP_START as *mut u8, initial_size); 45 | 46 | // Recurse to make the allocation so we can extend the heap if needed 47 | self.alloc(layout) 48 | } else { 49 | { 50 | let mut memory_object = self.mapped_heap.lock(); 51 | let current_size = memory_object.as_ref().unwrap().inner.size; 52 | let new_size = usize::max(current_size * 2, current_size + layout.size() + 256); 53 | memory_object.as_mut().unwrap().resize(new_size).unwrap(); 54 | self.inner.lock().extend(new_size - current_size); 55 | } 56 | 57 | // Recurse to make the allocation / extend the heap more 58 | self.alloc(layout) 59 | } 60 | } 61 | } 62 | 63 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 64 | self.inner.lock().deallocate(NonNull::new_unchecked(ptr), layout); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/std/x64.ld: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | OUTPUT_FORMAT(elf64-x86-64) 3 | 4 | IMAGE_START = 0x10000; 5 | 6 | PHDRS { 7 | text PT_LOAD; 8 | rodata PT_LOAD FLAGS(4); 9 | data PT_LOAD; 10 | tls PT_TLS; 11 | } 12 | 13 | SECTIONS { 14 | . = IMAGE_START; 15 | 16 | .text : { 17 | *(.text .text.*) 18 | . = ALIGN(4K); 19 | } :text 20 | 21 | .rodata : { 22 | *(.rodata .rodata.*) 23 | /* No need to align, because .got is aligned below */ 24 | } :rodata 25 | 26 | .got : { 27 | *(.got) 28 | . = ALIGN(4K); 29 | } :rodata 30 | 31 | .data : { 32 | *(.data .data.*) 33 | /* No need to align, because .bss is aligned below */ 34 | } :data 35 | 36 | .bss : { 37 | *(.bss .bss.*) 38 | . = ALIGN(4K); 39 | } :data 40 | 41 | .tdata : { 42 | *(.tdata .tdata.*) 43 | } :tls 44 | .tbss : { 45 | *(.tbss .tbss.*) 46 | } :tls 47 | 48 | /DISCARD/ : { 49 | *(.eh_frame*) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/usb/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.10.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 10 | 11 | [[package]] 12 | name = "log" 13 | version = "0.4.21" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 16 | 17 | [[package]] 18 | name = "mycelium-bitfield" 19 | version = "0.1.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 22 | 23 | [[package]] 24 | name = "proc-macro2" 25 | version = "1.0.79" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 28 | dependencies = [ 29 | "unicode-ident", 30 | ] 31 | 32 | [[package]] 33 | name = "ptah" 34 | version = "0.2.1" 35 | dependencies = [ 36 | "ptah_derive", 37 | ] 38 | 39 | [[package]] 40 | name = "ptah_derive" 41 | version = "0.2.1" 42 | dependencies = [ 43 | "proc-macro2", 44 | "quote", 45 | "syn", 46 | ] 47 | 48 | [[package]] 49 | name = "quote" 50 | version = "1.0.35" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 53 | dependencies = [ 54 | "proc-macro2", 55 | ] 56 | 57 | [[package]] 58 | name = "syn" 59 | version = "1.0.109" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 62 | dependencies = [ 63 | "proc-macro2", 64 | "quote", 65 | "unicode-ident", 66 | ] 67 | 68 | [[package]] 69 | name = "unicode-ident" 70 | version = "1.0.12" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 73 | 74 | [[package]] 75 | name = "usb" 76 | version = "0.1.0" 77 | dependencies = [ 78 | "bit_field", 79 | "log", 80 | "mycelium-bitfield", 81 | "ptah", 82 | ] 83 | -------------------------------------------------------------------------------- /lib/usb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | bit_field = "0.10.2" 9 | mycelium-bitfield = "0.1.5" 10 | ptah = { path = "../ptah/", features = ["derive"] } 11 | log = "0.4" 12 | -------------------------------------------------------------------------------- /lib/usb/src/hid/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod report; 2 | 3 | #[derive(Clone, Copy, Debug)] 4 | #[repr(C, packed)] 5 | pub struct HidDescriptor { 6 | pub length: u8, 7 | pub typ: u8, 8 | pub bcd_hid: u16, 9 | pub country_code: CountryCode, 10 | /// The number of included class descriptors. Will be `>=1` as a `Report` descriptor will 11 | /// always be present. 12 | pub num_descriptors: u8, 13 | /* 14 | * We include the first (guaranteed to be present) descriptor header here so it's included in 15 | * the first request. These are not naturally aligned, so we pack the field. 16 | */ 17 | pub descriptor_typ: u8, 18 | pub descriptor_length: u16, 19 | } 20 | 21 | #[derive(Clone, Copy, Debug)] 22 | #[repr(u8)] 23 | pub enum CountryCode { 24 | NotSupported = 0, 25 | Arabic = 1, 26 | Belgian = 2, 27 | CanadianBilingual = 3, 28 | CanadianFrench = 4, 29 | Czech = 5, 30 | Danish = 6, 31 | Finnish = 7, 32 | French = 8, 33 | German = 9, 34 | Greek = 10, 35 | Hebrew = 11, 36 | Hungary = 12, 37 | International = 13, 38 | Italian = 14, 39 | Japan = 15, 40 | Korean = 16, 41 | LatinAmerican = 17, 42 | Dutch = 18, 43 | Norwegian = 19, 44 | Farsi = 20, 45 | Poland = 21, 46 | Portuguese = 22, 47 | Russia = 23, 48 | Slovakia = 24, 49 | Spanish = 25, 50 | Swedish = 26, 51 | SwissFrench = 27, 52 | SwissGerman = 28, 53 | Switzerland = 29, 54 | Taiwan = 30, 55 | Turkish = 31, 56 | // 36-255 are reserved 57 | } 58 | -------------------------------------------------------------------------------- /lib/usb/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | 5 | pub mod descriptor; 6 | pub mod hid; 7 | pub mod setup; 8 | 9 | use alloc::vec::Vec; 10 | use descriptor::DescriptorType; 11 | use ptah::{Deserialize, Serialize}; 12 | 13 | #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)] 14 | pub enum EndpointDirection { 15 | In, 16 | Out, 17 | } 18 | 19 | #[derive(Clone, Debug, Serialize, Deserialize)] 20 | pub enum DeviceControlMessage { 21 | UseConfiguration(u8), 22 | UseInterface(u8, u8), 23 | OpenEndpoint { number: u8, direction: EndpointDirection, max_packet_size: u16 }, 24 | GetInterfaceDescriptor { typ: DescriptorType, index: u8, length: u16 }, 25 | InterruptTransferIn { endpoint: u8, packet_size: u16 }, 26 | } 27 | 28 | #[derive(Clone, Debug, Serialize, Deserialize)] 29 | pub enum DeviceResponse { 30 | Data(Vec), 31 | NoData, 32 | Descriptor { typ: DescriptorType, index: u8, bytes: Vec }, 33 | } 34 | -------------------------------------------------------------------------------- /lib/usb/src/setup.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug)] 2 | #[repr(C, align(8))] 3 | pub struct SetupPacket { 4 | pub typ: RequestType, 5 | pub request: Request, 6 | pub value: u16, 7 | pub index: u16, 8 | pub length: u16, 9 | } 10 | 11 | mycelium_bitfield::bitfield! { 12 | pub struct RequestType { 13 | pub const RECIPIENT: Recipient; 14 | pub const TYP: RequestTypeType; 15 | pub const DIRECTION: Direction; 16 | } 17 | } 18 | 19 | mycelium_bitfield::enum_from_bits! { 20 | #[derive(PartialEq, Eq, Debug)] 21 | pub enum Recipient { 22 | Device = 0b00000, 23 | Interface = 0b00001, 24 | Endpoint = 0b00010, 25 | Other = 0b00100, 26 | // XXX: required to make it take up the required number of bits. 27 | // TODO: Maybe this could be done better via a proc macro that parses the leading zeros 28 | // too? 29 | _Dummy = 0b10000, 30 | } 31 | } 32 | 33 | mycelium_bitfield::enum_from_bits! { 34 | #[derive(PartialEq, Eq, Debug)] 35 | pub enum RequestTypeType { 36 | Standard = 0b00, 37 | Class = 0b01, 38 | Vendor = 0b10, 39 | } 40 | } 41 | 42 | mycelium_bitfield::enum_from_bits! { 43 | #[derive(PartialEq, Eq, Debug)] 44 | pub enum Direction { 45 | HostToDevice = 0b0, 46 | DeviceToHost = 0b1, 47 | } 48 | } 49 | 50 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 51 | #[repr(u8)] 52 | pub enum Request { 53 | GetStatus = 0, 54 | ClearFeature = 1, 55 | SetFeature = 3, 56 | SetAddress = 5, 57 | GetDescriptor = 6, 58 | SetDescriptor = 7, 59 | GetConfiguration = 8, 60 | SetConfiguration = 9, 61 | GetInterface = 10, 62 | SetInterface = 11, 63 | SynchFrame = 12, 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | pub fn test_request_type() { 72 | assert_eq!( 73 | RequestType::new() 74 | .with(RequestType::RECIPIENT, Recipient::Endpoint) 75 | .with(RequestType::TYP, RequestTypeType::Vendor) 76 | .with(RequestType::DIRECTION, Direction::DeviceToHost) 77 | .bits(), 78 | 0b1_10_00010 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/virtio/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.10.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.5.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 16 | 17 | [[package]] 18 | name = "mycelium-bitfield" 19 | version = "0.1.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc" 22 | 23 | [[package]] 24 | name = "virtio" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "bit_field", 28 | "bitflags", 29 | "mycelium-bitfield", 30 | "volatile", 31 | ] 32 | 33 | [[package]] 34 | name = "volatile" 35 | version = "0.1.0" 36 | -------------------------------------------------------------------------------- /lib/virtio/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtio" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | volatile = { path = "../volatile" } 9 | bitflags = "2.4.0" 10 | bit_field = "0.10.2" 11 | mycelium-bitfield = "0.1.5" 12 | -------------------------------------------------------------------------------- /lib/virtio/src/block.rs: -------------------------------------------------------------------------------- 1 | use crate::mmio::VirtioMmioHeader; 2 | use volatile::{Read, Volatile}; 3 | 4 | #[repr(C)] 5 | pub struct BlockDeviceConfig { 6 | // TODO: how to abstract over both MMIO and PCI Virtio devices? 7 | pub header: VirtioMmioHeader, 8 | pub capacity: Volatile<[u32; 2], Read>, 9 | pub size_max: Volatile, 10 | pub seg_max: Volatile, 11 | pub geometry: Volatile, 12 | pub block_size: Volatile, 13 | pub topology: Volatile, 14 | pub writeback: Volatile, 15 | _reserved0: u8, 16 | pub num_queues: Volatile, 17 | pub max_discard_sectors: Volatile, 18 | pub max_discard_seg: Volatile, 19 | pub discard_sector_alignment: Volatile, 20 | pub max_write_zeroes_sectors: Volatile, 21 | pub max_write_zeroes_seg: Volatile, 22 | pub write_zeroes_may_unmap: Volatile, 23 | _reserved1: [u8; 3], 24 | pub max_secure_erase_sectors: Volatile, 25 | pub max_secure_erase_seg: Volatile, 26 | pub secure_erase_sector_alignment: Volatile, 27 | } 28 | 29 | impl BlockDeviceConfig { 30 | pub fn capacity(&self) -> u64 { 31 | let [lo, hi] = self.capacity.read(); 32 | (u64::from(hi) << 32) + u64::from(lo) 33 | } 34 | } 35 | 36 | #[derive(Clone, Copy, Debug)] 37 | #[repr(C)] 38 | pub struct Geometry { 39 | pub cylinders: u16, 40 | pub heads: u8, 41 | pub sectors: u8, 42 | } 43 | 44 | #[derive(Clone, Copy, Debug)] 45 | #[repr(C)] 46 | pub struct Topology { 47 | /// The number of logical blocks per physical block (log2) 48 | pub physical_block_log2: u8, 49 | /// The offset of the first aligned logical block 50 | pub alignment_offset: u8, 51 | /// The minimum I/O size (in blocks) 52 | pub min_io_size: u16, 53 | /// The optimal (and suggested maximum) I/O size (in blocks) 54 | pub optimal_io_size: u32, 55 | } 56 | 57 | #[derive(Clone, Copy, Debug)] 58 | #[repr(C)] 59 | pub struct Request { 60 | pub typ: RequestType, 61 | _reserved0: u32, 62 | pub sector: u64, 63 | // XXX: various request types have a variable-size data field here. Not sure how to model that tbh. 64 | pub status: RequestStatus, 65 | } 66 | 67 | impl Request { 68 | pub fn read(sector: u64) -> Request { 69 | Request { typ: RequestType::Read, _reserved0: 0, sector, status: RequestStatus::Ok } 70 | } 71 | } 72 | 73 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 74 | #[repr(u32)] 75 | pub enum RequestType { 76 | Read = 0, 77 | Write = 1, 78 | Flush = 4, 79 | GetId = 8, 80 | GetLifetime = 10, 81 | Discard = 11, 82 | WriteZeroes = 13, 83 | SecureErase = 14, 84 | } 85 | 86 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 87 | #[repr(u8)] 88 | pub enum RequestStatus { 89 | Ok = 0, 90 | Error = 1, 91 | Unsupported = 2, 92 | } 93 | -------------------------------------------------------------------------------- /lib/volatile/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "volatile" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /lib/volatile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "volatile" 3 | version = "0.1.0" 4 | authors = ["The Vanadinite Developers", "Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | -------------------------------------------------------------------------------- /lib/volatile/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021, The Vanadinite Developers 3 | * Copyright 2022, Isaac Woods 4 | * SPDX-License-Identifier: MPL-2.0 5 | */ 6 | 7 | #![no_std] 8 | 9 | use core::{cell::UnsafeCell, marker::PhantomData, ops::Index}; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Read; 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct Write; 15 | #[derive(Clone, Copy, Debug)] 16 | pub struct ReadWrite; 17 | 18 | #[derive(Debug)] 19 | #[repr(transparent)] 20 | pub struct Volatile(UnsafeCell, PhantomData); 21 | 22 | unsafe impl Send for Volatile {} 23 | unsafe impl Sync for Volatile {} 24 | 25 | impl Volatile { 26 | pub fn new(value: T) -> Volatile { 27 | Volatile(UnsafeCell::new(value), PhantomData) 28 | } 29 | } 30 | 31 | impl Volatile 32 | where 33 | T: Copy + 'static, 34 | { 35 | pub fn read(&self) -> T { 36 | unsafe { self.0.get().read_volatile() } 37 | } 38 | } 39 | 40 | impl Volatile 41 | where 42 | T: Copy + 'static, 43 | { 44 | pub fn write(&self, val: T) { 45 | unsafe { self.0.get().write_volatile(val) } 46 | } 47 | } 48 | 49 | impl Volatile 50 | where 51 | T: Copy + 'static, 52 | { 53 | pub fn read(&self) -> T { 54 | unsafe { self.0.get().read_volatile() } 55 | } 56 | 57 | pub fn write(&self, val: T) { 58 | unsafe { self.0.get().write_volatile(val) } 59 | } 60 | } 61 | 62 | impl Index for Volatile<[T; N], Read> 63 | where 64 | T: Copy, 65 | { 66 | type Output = Volatile; 67 | 68 | #[allow(clippy::transmute_ptr_to_ptr)] 69 | fn index(&self, index: usize) -> &Self::Output { 70 | unsafe { &core::mem::transmute::<_, &[Volatile; N]>(self)[index] } 71 | } 72 | } 73 | 74 | impl Index for Volatile<[T; N], ReadWrite> 75 | where 76 | T: Copy, 77 | { 78 | type Output = Volatile; 79 | 80 | #[allow(clippy::transmute_ptr_to_ptr)] 81 | fn index(&self, index: usize) -> &Self::Output { 82 | unsafe { &core::mem::transmute::<_, &[Volatile; N]>(self)[index] } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | edition = "2021" 3 | 4 | imports_granularity = "Crate" 5 | imports_layout = "HorizontalVertical" 6 | use_field_init_shorthand = true 7 | use_try_shorthand = true 8 | format_code_in_doc_comments = true 9 | max_width = 115 10 | use_small_heuristics = "max" 11 | -------------------------------------------------------------------------------- /seed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seed" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | hal = { path = "../lib/hal" } 9 | heapless = "0.8.0" 10 | serde = { version = "1", default-features = false, features = ["derive", "alloc"] } 11 | 12 | [workspace] 13 | members = ["seed_uefi", "seed_riscv", "d1_boot0"] 14 | -------------------------------------------------------------------------------- /seed/d1_boot0/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "d1_boot0" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | volatile = { path = "../../lib/volatile" } 9 | -------------------------------------------------------------------------------- /seed/d1_boot0/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=link.ld"); 3 | } 4 | -------------------------------------------------------------------------------- /seed/d1_boot0/link.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH("riscv") 2 | OUTPUT_FORMAT("elf64-littleriscv") 3 | ENTRY(_start) 4 | 5 | SECTIONS { 6 | . = 0x40000000; 7 | 8 | .text : ALIGN(16) { 9 | *(.text.start) 10 | *(.text .text.*) 11 | } 12 | 13 | .srodata : ALIGN(16) { 14 | *(.srodata .srodata.*) 15 | } 16 | 17 | .sdata : ALIGN(16) { 18 | *(.sdata .sdata.*) 19 | } 20 | 21 | PROVIDE(__global_pointer$ = .); 22 | PROVIDE(_bss_start = .); 23 | 24 | .sbss : ALIGN(16) { 25 | *(.sbss .sbss.*) 26 | } 27 | 28 | .bss : ALIGN(16) { 29 | *(.bss .bss.*) 30 | 31 | PROVIDE(_stack_bottom = .); 32 | . += 64K; 33 | PROVIDE(_stack_top = .); 34 | } 35 | 36 | PROVIDE(_bss_end = .); 37 | 38 | .data : ALIGN(16) { 39 | *(.data .data.*) 40 | } 41 | 42 | .rodata : ALIGN(16) { 43 | *(.rodata .rodata.*) 44 | } 45 | 46 | .eh_frame : ALIGN(16) { 47 | *(.eh_frame) 48 | } 49 | 50 | /DISCARD/ : { *(.eh_frame_hdr .eh_frame) } 51 | } 52 | -------------------------------------------------------------------------------- /seed/d1_boot0/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::fmt::Write; 5 | use volatile::Volatile; 6 | 7 | core::arch::global_asm!( 8 | " 9 | .section .text.start 10 | .global _start 11 | _start: 12 | // Zero the BSS 13 | la t0, _bss_start 14 | la t1, _bss_end 15 | bgeu t0, t1, .bss_zero_loop_end 16 | .bss_zero_loop: 17 | sd zero, (t0) 18 | addi t0, t0, 8 19 | bltu t0, t1, .bss_zero_loop 20 | .bss_zero_loop_end: 21 | 22 | la sp, _stack_top 23 | 24 | jal main 25 | unimp 26 | " 27 | ); 28 | 29 | /* 30 | * TODO: 31 | * We originally thought we'd need this boot shim to load OpenSBI even when booting over FEL. 32 | * However, we've managed to do that without one - this is left over and committed because we'll 33 | * need it at some point to load from persistent media on the D1. It'll need plenty more work, 34 | * including code to initialize DRAM, special headers to be loaded by the BROM, and a small SDHC 35 | * driver to load OpenSBI and Seed from the SD card. 36 | * 37 | * For now, it should basically be ignored, and is just in-tree to prevent me from having to do the 38 | * work of setting it up again when we work on the next bit. 39 | */ 40 | 41 | #[no_mangle] 42 | pub fn main() -> ! { 43 | let serial = unsafe { &mut *(0x0250_0000 as *mut Uart) }; 44 | writeln!(serial, "Poplar's boot0 is running!").unwrap(); 45 | 46 | let hart_id = unsafe { 47 | let value: usize; 48 | core::arch::asm!("csrr {}, mhartid", out(reg) value); 49 | value 50 | }; 51 | writeln!(serial, "HART id: {}", hart_id).unwrap(); 52 | 53 | loop {} 54 | } 55 | 56 | #[repr(C)] 57 | pub struct Uart { 58 | data: Volatile, 59 | interrupt_enable: Volatile, 60 | interrupt_identity: Volatile, 61 | line_control: Volatile, 62 | modem_control: Volatile, 63 | line_status: Volatile, 64 | modem_status: Volatile, 65 | scratch: Volatile, 66 | } 67 | 68 | impl Uart { 69 | fn line_status(&self) -> u32 { 70 | self.line_status.read() 71 | } 72 | 73 | pub fn write(&self, data: u8) { 74 | while (self.line_status() & 0x20) == 0 {} 75 | self.data.write(data as u32); 76 | } 77 | } 78 | 79 | impl core::fmt::Write for Uart { 80 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 81 | for byte in s.bytes() { 82 | self.write(byte); 83 | } 84 | Ok(()) 85 | } 86 | } 87 | 88 | #[panic_handler] 89 | pub fn panic(_: &core::panic::PanicInfo) -> ! { 90 | let serial = unsafe { &mut *(0x0250_0000 as *mut Uart) }; 91 | let _ = write!(serial, "boot0: PANIC!"); 92 | loop {} 93 | } 94 | -------------------------------------------------------------------------------- /seed/seed_riscv/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seed_riscv" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | seed = { path = ".." } 9 | hal = { path = "../../lib/hal/" } 10 | hal_riscv = { path = "../../lib/hal_riscv/" } 11 | mulch = { path = "../../lib/mulch/" } 12 | volatile = { path = "../../lib/volatile" } 13 | spinning_top = { version = "0.2.4", features = ["nightly"] } 14 | bit_field = "0.10.1" 15 | fdt = { path = "../../lib/fdt/", features = ["pretty-printing"] } 16 | tracing = { git = "https://github.com/tokio-rs/tracing", default-features = false } 17 | tracing-core = { git = "https://github.com/tokio-rs/tracing", default-features = false } 18 | linked_list_allocator = "0.10.5" 19 | arrayvec = { version = "0.7.2", default-features = false } 20 | mer = { path = "../../lib/mer/" } 21 | pci_types = { path = "../../lib/pci_types" } 22 | virtio = { path = "../../lib/virtio" } 23 | gpt = { path = "../../lib/gpt" } 24 | heapless = "0.8.0" 25 | serde = { version = "1", default-features = false, features = ["derive", "alloc"] } 26 | picotoml = { path = "../../lib/picotoml" } 27 | 28 | [features] 29 | platform_rv64_virt = ["hal_riscv/platform_rv64_virt"] 30 | platform_mq_pro = ["hal_riscv/platform_mq_pro"] 31 | -------------------------------------------------------------------------------- /seed/seed_riscv/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // TODO: wonder if we can do this based on a platform feature? 3 | println!("cargo:rerun-if-changed=rv64_virt.ld"); 4 | println!("cargo:rerun-if-changed=mq_pro.ld"); 5 | } 6 | -------------------------------------------------------------------------------- /seed/seed_riscv/mq_pro.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | OUTPUT_ARCH("riscv") 7 | OUTPUT_FORMAT("elf64-littleriscv") 8 | ENTRY(_start) 9 | 10 | SECTIONS { 11 | . = 0x40080000; 12 | PROVIDE(_seed_start = .); 13 | 14 | .text : ALIGN(16) { 15 | *(.text.start) 16 | *(.text .text.*) 17 | } 18 | 19 | .srodata : ALIGN(16) { 20 | *(.srodata .srodata.*) 21 | } 22 | 23 | .sdata : ALIGN(16) { 24 | *(.sdata .sdata.*) 25 | } 26 | 27 | PROVIDE(__global_pointer$ = .); 28 | PROVIDE(_bss_start = .); 29 | 30 | .sbss : ALIGN(16) { 31 | *(.sbss .sbss.*) 32 | } 33 | 34 | .bss : ALIGN(16) { 35 | *(.bss .bss.*) 36 | 37 | PROVIDE(_stack_bottom = .); 38 | . += 256K; 39 | PROVIDE(_stack_top = .); 40 | } 41 | 42 | PROVIDE(_bss_end = .); 43 | 44 | .data : ALIGN(16) { 45 | *(.data .data.*) 46 | } 47 | 48 | .rodata : ALIGN(16) { 49 | *(.rodata .rodata.*) 50 | } 51 | 52 | .eh_frame : ALIGN(16) { 53 | *(.eh_frame) 54 | } 55 | PROVIDE(_seed_end = .); 56 | 57 | /DISCARD/ : { *(.eh_frame_hdr .eh_frame) } 58 | } 59 | -------------------------------------------------------------------------------- /seed/seed_riscv/rv64_virt.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022, Isaac Woods 3 | * SPDX-License-Identifier: MPL-2.0 4 | */ 5 | 6 | OUTPUT_ARCH("riscv") 7 | OUTPUT_FORMAT("elf64-littleriscv") 8 | ENTRY(_start) 9 | 10 | SECTIONS { 11 | . = 0x80200000; 12 | PROVIDE(_seed_start = .); 13 | 14 | .text : ALIGN(16) { 15 | *(.text.start) 16 | *(.text .text.*) 17 | } 18 | 19 | .srodata : ALIGN(16) { 20 | *(.srodata .srodata.*) 21 | } 22 | 23 | .sdata : ALIGN(16) { 24 | *(.sdata .sdata.*) 25 | } 26 | 27 | PROVIDE(__global_pointer$ = .); 28 | PROVIDE(_bss_start = .); 29 | 30 | .sbss : ALIGN(16) { 31 | *(.sbss .sbss.*) 32 | } 33 | 34 | .bss : ALIGN(16) { 35 | *(.bss .bss.*) 36 | 37 | PROVIDE(_stack_bottom = .); 38 | . += 256K; 39 | PROVIDE(_stack_top = .); 40 | } 41 | 42 | PROVIDE(_bss_end = .); 43 | 44 | .data : ALIGN(16) { 45 | *(.data .data.*) 46 | } 47 | 48 | .rodata : ALIGN(16) { 49 | *(.rodata .rodata.*) 50 | } 51 | 52 | .eh_frame : ALIGN(16) { 53 | *(.eh_frame) 54 | } 55 | PROVIDE(_seed_end = .); 56 | 57 | /DISCARD/ : { *(.eh_frame_hdr .eh_frame) } 58 | } 59 | -------------------------------------------------------------------------------- /seed/seed_riscv/src/block/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod virtio; 2 | 3 | use core::ptr::NonNull; 4 | 5 | pub trait BlockDevice { 6 | type ReadTokenMetadata; 7 | 8 | fn read(&mut self, block: u64) -> ReadToken; 9 | fn free_read_block(&mut self, token: ReadToken); 10 | } 11 | 12 | /// Represents a block that has been read from a `BlockDevice`. Must be freed using 13 | /// `BlockDevice::free_read_block`. 14 | pub struct ReadToken { 15 | pub data: NonNull<[u8; 512]>, 16 | meta: M, 17 | } 18 | -------------------------------------------------------------------------------- /seed/seed_riscv/src/fs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ramdisk; 2 | 3 | use alloc::string::String; 4 | 5 | /// A `Filesystem` represents something that can meaningfully contain 'files' - discrete chunks of 6 | /// data addressed using paths. For Seed, this is generally going to be a real filesystem that 7 | /// occupies a partition on a block device, either real or virtual, or the very simple 'filesystem' 8 | /// provided by the ramdisk used on some platforms. 9 | /// 10 | /// This interface (at the moment, at least) is much simpler than a 'real' one. You can simply load 11 | /// a file in its entirity into memory, and then close it once you're done with it. In the future, 12 | /// this could be made smarter, but is probably sufficient for a bootloader as is. 13 | pub trait Filesystem { 14 | fn load(&mut self, path: &str) -> Result; 15 | fn close(&mut self, file: File); 16 | } 17 | 18 | // TODO: some filesystems will need to allocate memory for this (real ones) and others wont (the 19 | // ramdisk). We probably need a way to represent both scenarios. (this could be another trait with 20 | // a `data` method? And then the concrete type an associated type of the FS?) 21 | pub struct File<'a> { 22 | pub path: String, 23 | pub data: &'a [u8], 24 | } 25 | -------------------------------------------------------------------------------- /seed/seed_riscv/src/fs/ramdisk.rs: -------------------------------------------------------------------------------- 1 | use super::{File, Filesystem}; 2 | use alloc::{slice, string::ToString}; 3 | use core::mem; 4 | use hal::memory::PAddr; 5 | use seed::ramdisk::{RamdiskEntry, RamdiskHeader}; 6 | 7 | pub struct Ramdisk { 8 | base: *const RamdiskHeader, 9 | offset_to_data: usize, 10 | } 11 | 12 | impl Ramdisk { 13 | pub unsafe fn new(address: usize) -> Option { 14 | let magic: [u8; 8] = unsafe { core::ptr::read_volatile(address as *const [u8; 8]) }; 15 | if magic != RamdiskHeader::MAGIC { 16 | return None; 17 | } 18 | 19 | let header = unsafe { &*(address as *const RamdiskHeader) }; 20 | let offset_to_data = 21 | mem::size_of::() + header.num_entries as usize * mem::size_of::(); 22 | Some(Ramdisk { base: address as *const RamdiskHeader, offset_to_data }) 23 | } 24 | 25 | pub fn entry(&self, name: &str) -> Option<&RamdiskEntry> { 26 | self.entries().into_iter().find(|entry| entry.name().unwrap() == name) 27 | } 28 | 29 | pub fn entry_data(&self, name: &str) -> Option<&[u8]> { 30 | let entry = self.entry(name)?; 31 | 32 | unsafe { 33 | Some(slice::from_raw_parts( 34 | self.base.byte_add(self.offset_to_data + entry.offset as usize) as *const u8, 35 | entry.size as usize, 36 | )) 37 | } 38 | } 39 | 40 | pub fn header(&self) -> &RamdiskHeader { 41 | unsafe { &*self.base } 42 | } 43 | 44 | pub fn entries(&self) -> &[RamdiskEntry] { 45 | let entries_base = unsafe { self.base.byte_add(mem::size_of::()) as *const RamdiskEntry }; 46 | unsafe { slice::from_raw_parts(entries_base, self.header().num_entries as usize) } 47 | } 48 | 49 | /// Get the memory region occupied by the ramdisk, in the form `(address, size)`. 50 | pub fn memory_region(&self) -> (PAddr, usize) { 51 | (PAddr::new(self.base as usize).unwrap(), self.header().size as usize) 52 | } 53 | } 54 | 55 | impl Filesystem for Ramdisk { 56 | fn load(&mut self, path: &str) -> Result { 57 | self.entry_data(path).map(|data| File { path: path.to_string(), data }).ok_or(()) 58 | } 59 | 60 | fn close(&mut self, _file: super::File) {} 61 | } 62 | -------------------------------------------------------------------------------- /seed/seed_uefi/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bitflags" 5 | version = "1.2.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "0.1.10" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 14 | 15 | [[package]] 16 | name = "efistub" 17 | version = "0.1.0" 18 | dependencies = [ 19 | "log", 20 | "uefi", 21 | ] 22 | 23 | [[package]] 24 | name = "log" 25 | version = "0.4.8" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 28 | dependencies = [ 29 | "cfg-if", 30 | ] 31 | 32 | [[package]] 33 | name = "proc-macro2" 34 | version = "1.0.6" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" 37 | dependencies = [ 38 | "unicode-xid", 39 | ] 40 | 41 | [[package]] 42 | name = "quote" 43 | version = "1.0.2" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 46 | dependencies = [ 47 | "proc-macro2", 48 | ] 49 | 50 | [[package]] 51 | name = "syn" 52 | version = "1.0.11" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" 55 | dependencies = [ 56 | "proc-macro2", 57 | "quote", 58 | "unicode-xid", 59 | ] 60 | 61 | [[package]] 62 | name = "ucs2" 63 | version = "0.2.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "517566d61c8a1c347e75ef90f70099e27de717f931cc31119ad31544641aca64" 66 | 67 | [[package]] 68 | name = "uefi" 69 | version = "0.4.1" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "b8f9079624e954676c948c660b4f4e5a1cf57e7455036bbb27cb76a87e0d4f77" 72 | dependencies = [ 73 | "bitflags", 74 | "log", 75 | "ucs2", 76 | "uefi-macros", 77 | ] 78 | 79 | [[package]] 80 | name = "uefi-macros" 81 | version = "0.3.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "111ba394b5fe37b5d80ccc224dec2d5f0cba33f962f236f66a9c536c5dd5d878" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "syn", 88 | ] 89 | 90 | [[package]] 91 | name = "unicode-xid" 92 | version = "0.2.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 95 | -------------------------------------------------------------------------------- /seed/seed_uefi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seed_uefi" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | seed = { path = ".." } 9 | hal = { path = "../../lib/hal" } 10 | hal_x86_64 = { path = "../../lib/hal_x86_64" } 11 | log = "0.4" 12 | spinning_top = "0.3.0" 13 | uefi = { version = "0.24.0", features = ["alloc", "global_allocator"] } 14 | mer = { path = "../../lib/mer" } 15 | gfxconsole = { path = "../../lib/gfxconsole" } 16 | mulch = { path = "../../lib/mulch" } 17 | heapless = "0.8.0" 18 | serde = { version = "1", default-features = false, features = ["derive", "alloc"] } 19 | picotoml = { path = "../../lib/picotoml" } 20 | -------------------------------------------------------------------------------- /seed/seed_uefi/src/allocator.rs: -------------------------------------------------------------------------------- 1 | use core::{cell::Cell, ops::Range}; 2 | use hal::memory::{Frame, FrameAllocator, FrameSize, PAddr, Size4KiB}; 3 | use uefi::table::boot::{AllocateType, BootServices}; 4 | 5 | /// `BootFrameAllocator` is the allocator we use in the bootloader to allocate memory for the 6 | /// kernel page tables. It pre-allocates a preset number of frames using the UEFI boot services, 7 | /// which allows us to map things into the page tables without worrying about invalidating the 8 | /// memory map by allocating for new entries. 9 | /// 10 | /// We use `Cell` for interior mutability within the allocator. This is safe because the bootloader 11 | /// is single-threaded and non-reentrant. 12 | pub struct BootFrameAllocator { 13 | /// This is the first frame that cannot be allocated by this allocator 14 | end_frame: Frame, 15 | 16 | /// This points to the next frame available for allocation. When `next_frame + 1 == end_frame`, 17 | /// the allocator cannot allocate any more frames. 18 | next_frame: Cell, 19 | } 20 | 21 | impl BootFrameAllocator { 22 | pub fn new(boot_services: &BootServices, num_frames: usize) -> BootFrameAllocator { 23 | let start_frame_address = boot_services 24 | .allocate_pages(AllocateType::AnyPages, crate::PAGE_TABLE_MEMORY_TYPE, num_frames) 25 | .expect("Failed to allocate frames for page table allocator"); 26 | 27 | // Zero all the memory so the page tables start with everything unmapped 28 | unsafe { 29 | boot_services.set_mem(start_frame_address as usize as *mut _, num_frames * Size4KiB::SIZE, 0); 30 | } 31 | 32 | let start_frame = Frame::contains(PAddr::new(start_frame_address as usize).unwrap()); 33 | BootFrameAllocator { end_frame: start_frame + num_frames, next_frame: Cell::new(start_frame) } 34 | } 35 | } 36 | 37 | impl FrameAllocator for BootFrameAllocator { 38 | fn allocate_n(&self, n: usize) -> Range { 39 | if (self.next_frame.get() + n) > self.end_frame { 40 | panic!("Bootloader frame allocator ran out of frames!"); 41 | } 42 | 43 | let frame = self.next_frame.get(); 44 | self.next_frame.update(|frame| frame + n); 45 | 46 | frame..(frame + n) 47 | } 48 | 49 | fn free_n(&self, _: Frame, _: usize) { 50 | /* 51 | * NOTE: We should only free physical memory in the bootloader when we unmap the stack 52 | * guard page. Because of the simplicity of our allocator, we can't do anything 53 | * useful with the freed frame, so we just leak it. 54 | */ 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /seed/seed_uefi/src/logger.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use gfxconsole::{Framebuffer, GfxConsole}; 3 | use hal_x86_64::hw::serial::SerialPort; 4 | use log::{LevelFilter, Log, Metadata, Record}; 5 | use seed::boot_info::VideoModeInfo; 6 | use spinning_top::Spinlock; 7 | 8 | pub static LOGGER: Spinlock = Spinlock::new(Logger::Uninit); 9 | 10 | struct LogWrapper; 11 | 12 | impl Log for LogWrapper { 13 | fn enabled(&self, _: &Metadata) -> bool { 14 | true 15 | } 16 | 17 | fn log(&self, record: &Record) { 18 | use core::fmt::Write; 19 | 20 | if self.enabled(record.metadata()) { 21 | LOGGER.lock().write_fmt(format_args!("[{}] {}\n", record.level(), record.args())).unwrap(); 22 | } 23 | } 24 | 25 | fn flush(&self) {} 26 | } 27 | 28 | pub enum Logger { 29 | Uninit, 30 | Serial(SerialPort), 31 | Graphical { serial_port: SerialPort, console: GfxConsole }, 32 | } 33 | 34 | impl Logger { 35 | /// Initialize the logger, initially just printing to the serial port. Once a graphics device has been 36 | /// initialized, `switch_to_graphical` can be called to switch to logging both to serial and the graphical 37 | /// device. 38 | pub fn init() { 39 | *LOGGER.lock() = Logger::Serial(unsafe { SerialPort::new(hal_x86_64::hw::serial::COM1) }); 40 | log::set_logger(&LogWrapper).unwrap(); 41 | log::set_max_level(LevelFilter::Trace); 42 | } 43 | 44 | pub fn switch_to_graphical( 45 | VideoModeInfo { framebuffer_address, pixel_format, width, height, stride }: &VideoModeInfo, 46 | ) { 47 | let framebuffer = match pixel_format { 48 | seed::boot_info::PixelFormat::Rgb32 => { 49 | Framebuffer::new(usize::from(*framebuffer_address) as *mut u32, *width, *height, *stride, 0, 8, 16) 50 | } 51 | seed::boot_info::PixelFormat::Bgr32 => { 52 | Framebuffer::new(usize::from(*framebuffer_address) as *mut u32, *width, *height, *stride, 16, 8, 0) 53 | } 54 | }; 55 | *LOGGER.lock() = Logger::Graphical { 56 | serial_port: unsafe { SerialPort::new(hal_x86_64::hw::serial::COM1) }, 57 | console: GfxConsole::new(framebuffer, 0x0000aaff, 0xffffffff), 58 | }; 59 | } 60 | } 61 | 62 | impl fmt::Write for Logger { 63 | fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { 64 | match self { 65 | Logger::Uninit => panic!("Tried to log before it was initialized!"), 66 | Logger::Serial(serial_port) => serial_port.write_str(s), 67 | Logger::Graphical { serial_port, console } => { 68 | serial_port.write_str(s)?; 69 | console.write_str(s) 70 | } 71 | } 72 | } 73 | } 74 | 75 | unsafe impl Sync for Logger {} 76 | unsafe impl Send for Logger {} 77 | -------------------------------------------------------------------------------- /seed/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate alloc; 4 | 5 | pub mod boot_info; 6 | pub mod ramdisk; 7 | 8 | use alloc::{string::String, vec::Vec}; 9 | use serde::Deserialize; 10 | 11 | #[derive(Clone, Debug, Deserialize)] 12 | pub struct SeedConfig { 13 | pub user_tasks: Vec, 14 | } 15 | -------------------------------------------------------------------------------- /seed/src/ramdisk.rs: -------------------------------------------------------------------------------- 1 | use core::{ffi::CStr, fmt}; 2 | 3 | #[repr(C)] 4 | pub struct RamdiskHeader { 5 | pub magic: [u8; 8], 6 | /// The total size of the ramdisk, including this header, the entry table, and all of the 7 | /// entries. 8 | pub size: u32, 9 | pub num_entries: u32, 10 | } 11 | 12 | impl RamdiskHeader { 13 | pub const MAGIC: [u8; 8] = *b"RAMDISK_"; 14 | } 15 | 16 | /// Describes a file held in the ramdisk. 17 | #[repr(C)] 18 | pub struct RamdiskEntry { 19 | /// The UTF-8 encoded name of the file. Must be null-terminated. 20 | pub name: [u8; Self::NAME_LENGTH], 21 | pub offset: u32, 22 | pub size: u32, 23 | } 24 | 25 | impl RamdiskEntry { 26 | pub const NAME_LENGTH: usize = 32; 27 | 28 | pub fn new(name: &str, offset: u32, size: u32) -> Result { 29 | if name.as_bytes().len() > (Self::NAME_LENGTH - 1) { 30 | return Err(()); 31 | } 32 | 33 | let mut name_bytes = [0; Self::NAME_LENGTH]; 34 | name_bytes[..name.as_bytes().len()].copy_from_slice(name.as_bytes()); 35 | Ok(RamdiskEntry { name: name_bytes, offset, size }) 36 | } 37 | 38 | pub fn name(&self) -> Result<&str, ()> { 39 | CStr::from_bytes_until_nul(&self.name).map_err(|_| ())?.to_str().map_err(|_| ()) 40 | } 41 | } 42 | 43 | impl fmt::Debug for RamdiskEntry { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | f.debug_struct("RamdiskEntry") 46 | .field("name", &self.name()) 47 | .field("offset", &self.offset) 48 | .field("size", &self.size) 49 | .finish() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /site/_config.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/site/_config.yml -------------------------------------------------------------------------------- /site/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Poplar 5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 |
13 |

Poplar

14 | 15 | 21 |
22 | 23 | {{ content }} 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /site/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 | Poplar is a microkernel and userspace written in Rust. It is still early in development. 6 | 7 | * Poplar is **not** a UNIX 8 | * Processes talk to the kernel through a very minimal system call interface 9 | * Processes communicate with each other through message passing facilitated by the kernel 10 | * Drivers live in userspace 11 | 12 | The best place to learn more about Poplar is [the book](https://isaacwoods.github.io/poplar/book). 13 | -------------------------------------------------------------------------------- /site/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/site/logo.png -------------------------------------------------------------------------------- /site/logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/site/logo_dark.png -------------------------------------------------------------------------------- /site/poplar_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacWoods/poplar/c406bd4f14c15c38cd85735f6afc262842e2cffc/site/poplar_text.png -------------------------------------------------------------------------------- /site/style.css: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | body { 5 | background: white; 6 | font: 18px/1.4 Helvetica; 7 | color: #4e5054; 8 | } 9 | 10 | div.header { 11 | display: flex; 12 | align-items: center; 13 | padding: 20px 0; 14 | } 15 | 16 | h1.logo { 17 | font-family: sans-serif; 18 | margin-right: 20px; 19 | } 20 | 21 | nav { 22 | display: inline-block; 23 | } 24 | 25 | nav a { 26 | color: black; 27 | text-decoration: none; 28 | border: 3px solid; 29 | padding: 5px 5px 5px 5px; 30 | } 31 | 32 | nav a:hover { 33 | text-decoration: underline; 34 | } 35 | -------------------------------------------------------------------------------- /tools/rust_gdb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Exit on error 4 | set -e 5 | 6 | RUSTC_SYSROOT="$(rustc --print=sysroot)" 7 | GDB_PYTHON_MODULE_DIR="$RUSTC_SYSROOT/lib/rustlib/etc" 8 | PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIR" exec gdb \ 9 | --directory="$GDB_PYTHON_MODULE_DIR" \ 10 | -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIR" \ 11 | "$@" 12 | -------------------------------------------------------------------------------- /tools/tftp_serve/.cargo/config: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | runner = "sudo -E" 3 | -------------------------------------------------------------------------------- /tools/tftp_serve/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tftp_serve" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | byteorder = "1" 9 | tokio = { version = "1", features = ["full"] } 10 | futures = "0.3" 11 | -------------------------------------------------------------------------------- /tools/xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | xflags = "0.3.1" 9 | xshell = "0.1.14" 10 | eyre = "0.6.5" 11 | color-eyre = "0.5.11" 12 | gpt = "3.1.0" 13 | fatfs = "0.3.5" 14 | fscommon = "0.1.1" 15 | serde = { version = "1.0.152", features = ["derive"] } 16 | toml = "0.7.2" 17 | colored = "2.0.4" 18 | serialport = "4.2.2" 19 | seed = { path = "../../seed/" } 20 | fs_extra = "1.3.0" 21 | -------------------------------------------------------------------------------- /tools/xtask/src/riscv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod qemu; 2 | -------------------------------------------------------------------------------- /tools/xtask/src/serial.rs: -------------------------------------------------------------------------------- 1 | use serialport::SerialPort; 2 | use std::{path::Path, time::Duration}; 3 | 4 | pub struct Serial { 5 | port: Box, 6 | } 7 | 8 | impl Serial { 9 | pub fn new(device: &Path, baud: u32) -> Self { 10 | let port = 11 | serialport::new(device.to_str().unwrap(), baud).timeout(Duration::from_secs(10)).open().unwrap(); 12 | Self { port } 13 | } 14 | 15 | pub fn listen(mut self) -> ! { 16 | loop { 17 | let mut buffer = [0u8; 256]; 18 | let read_buffer = { 19 | let bytes_read = self.port.read(&mut buffer).unwrap(); 20 | if bytes_read == 0 { 21 | continue; 22 | } 23 | 24 | &mut buffer[0..bytes_read] 25 | }; 26 | print!("{}", String::from_utf8_lossy(read_buffer)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tools/xtask/src/x64/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod qemu; 2 | -------------------------------------------------------------------------------- /user/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "simple_fb", 4 | "platform_bus", 5 | "usb_bus_xhci", 6 | "hello_world", 7 | "usb_bus_ehci", 8 | "usb_hid", 9 | "virtio_gpu", 10 | "fb_console", 11 | "service_host", 12 | ] 13 | resolver = "2" 14 | 15 | [profile.dev] 16 | debug = false 17 | panic = "abort" 18 | 19 | [profile.release] 20 | panic = "abort" 21 | -------------------------------------------------------------------------------- /user/fb_console/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fb_console" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std", features = ["async"] } 9 | log = "0.4" 10 | service_host = { path = "../service_host" } 11 | gfxconsole = { path = "../../lib/gfxconsole" } 12 | ptah = { path = "../../lib/ptah" } 13 | platform_bus = { path = "../platform_bus" } 14 | spinning_top = "0.3.0" 15 | thingbuf = { version = "0.1.6", default-features = false, features = ["alloc"] } 16 | ginkgo = { path = "../../ginkgo", default-features = false, features = ["poplar"] } 17 | -------------------------------------------------------------------------------- /user/hello_world/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello_world" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std" } 9 | service_host = { path = "../service_host" } 10 | -------------------------------------------------------------------------------- /user/hello_world/src/main.rs: -------------------------------------------------------------------------------- 1 | use service_host::ServiceHostClient; 2 | 3 | fn main() { 4 | std::poplar::syscall::early_log("Hello, World!").unwrap(); 5 | // println!("Hello, world!"); 6 | 7 | let service_host = ServiceHostClient::new(); 8 | let service_channel = service_host.register_service("hello_world").unwrap(); 9 | } 10 | -------------------------------------------------------------------------------- /user/platform_bus/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "platform_bus" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "platform_bus" 9 | path = "src/lib.rs" 10 | 11 | [[bin]] 12 | name = "platform_bus" 13 | path = "src/main.rs" 14 | 15 | [dependencies] 16 | std = { path = "../../lib/std", features = ["async", "ddk"] } 17 | poplar = { path = "../../lib/poplar" } 18 | service_host = { path = "../service_host" } 19 | log = "0.4" 20 | ptah = { path = "../../lib/ptah" } 21 | spinning_top = "0.3.0" 22 | pci_types = { path = "../../lib/pci_types" } 23 | -------------------------------------------------------------------------------- /user/platform_bus/src/service/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pci; 2 | -------------------------------------------------------------------------------- /user/service_host/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "service_host" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std" } 9 | poplar = { path = "../../lib/poplar" } 10 | log = "0.4" 11 | ptah = { path = "../../lib/ptah" } 12 | -------------------------------------------------------------------------------- /user/simple_fb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "simple_fb" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std" } 9 | log = "0.4" 10 | gfxconsole = { path = "../../lib/gfxconsole" } 11 | ptah = { path = "../../lib/ptah" } 12 | -------------------------------------------------------------------------------- /user/simple_fb/src/main.rs: -------------------------------------------------------------------------------- 1 | use gfxconsole::Framebuffer; 2 | use log::info; 3 | use std::{ 4 | mem::MaybeUninit, 5 | poplar::{ 6 | early_logger::EarlyLogger, 7 | syscall::{self, FramebufferInfo, PixelFormat}, 8 | Handle, 9 | }, 10 | }; 11 | 12 | pub fn main() { 13 | log::set_logger(&EarlyLogger).unwrap(); 14 | log::set_max_level(log::LevelFilter::Trace); 15 | info!("Simple framebuffer driver is running!"); 16 | 17 | let mut framebuffer = make_framebuffer(); 18 | let mut yields = 0; 19 | 20 | loop { 21 | framebuffer.clear(0xffaaaaaa); 22 | framebuffer.draw_string( 23 | &format!("The framebuffer driver has yielded {} times!", yields), 24 | 400, 25 | 400, 26 | 0xffff0000, 27 | ); 28 | yields += 1; 29 | 30 | syscall::yield_to_kernel(); 31 | } 32 | } 33 | 34 | fn make_framebuffer() -> Framebuffer { 35 | /* 36 | * This is the virtual address the framebuffer will be mapped to in our address space. 37 | * NOTE: this address was basically pulled out of thin air. 38 | */ 39 | const FRAMEBUFFER_ADDRESS: usize = 0x00000005_00000000; 40 | 41 | let (framebuffer_handle, framebuffer_info) = { 42 | let mut framebuffer_info: MaybeUninit = MaybeUninit::uninit(); 43 | 44 | let framebuffer_handle = 45 | syscall::get_framebuffer(framebuffer_info.as_mut_ptr()).expect("Failed to get handle to framebuffer!"); 46 | 47 | (framebuffer_handle, unsafe { framebuffer_info.assume_init() }) 48 | }; 49 | 50 | unsafe { 51 | syscall::map_memory_object(framebuffer_handle, Handle::ZERO, Some(FRAMEBUFFER_ADDRESS), 0x0 as *mut _) 52 | .unwrap(); 53 | } 54 | assert_eq!(framebuffer_info.pixel_format, PixelFormat::Bgr32); 55 | 56 | Framebuffer::new( 57 | FRAMEBUFFER_ADDRESS as *mut u32, 58 | framebuffer_info.width as usize, 59 | framebuffer_info.height as usize, 60 | framebuffer_info.stride as usize, 61 | 16, 62 | 8, 63 | 0, 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /user/usb_bus_ehci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb_bus_ehci" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std", features = ["ddk"] } 9 | log = "0.4" 10 | service_host = { path = "../service_host" } 11 | platform_bus = { path = "../platform_bus" } 12 | ptah = { path = "../../lib/ptah" } 13 | bit_field = "0.10" 14 | mycelium-bitfield = "0.1.4" 15 | bitflags = "2.4.1" 16 | usb = { path = "../../lib/usb" } 17 | spinning_top = "0.3.0" 18 | -------------------------------------------------------------------------------- /user/usb_bus_xhci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb_bus_xhci" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std" } 9 | log = "0.4" 10 | platform_bus = { path = "../platform_bus" } 11 | ptah = { path = "../../lib/ptah" } 12 | bit_field = "0.10" 13 | mulch = { path = "../../lib/mulch" } 14 | -------------------------------------------------------------------------------- /user/usb_hid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "usb_hid" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std", features = ["ddk"] } 9 | log = "0.4" 10 | service_host = { path = "../service_host" } 11 | platform_bus = { path = "../platform_bus" } 12 | ptah = { path = "../../lib/ptah" } 13 | usb = { path = "../../lib/usb" } 14 | -------------------------------------------------------------------------------- /user/virtio_gpu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtio_gpu" 3 | version = "0.1.0" 4 | authors = ["Isaac Woods"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | std = { path = "../../lib/std", features = ["ddk"] } 9 | log = "0.4" 10 | service_host = { path = "../service_host" } 11 | platform_bus = { path = "../platform_bus" } 12 | bit_field = "0.10" 13 | virtio = { path = "../../lib/virtio" } 14 | -------------------------------------------------------------------------------- /user/x86_64-poplar.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": "32", 8 | "os": "none", 9 | "executables": true, 10 | "linker-flavor": "ld.lld", 11 | "linker": "rust-lld", 12 | "panic-strategy": "abort", 13 | "disable-redzone": true, 14 | "features": "-mmx,-sse,+soft-float", 15 | "rustc-abi": "x86-softfloat" 16 | } 17 | --------------------------------------------------------------------------------