19 Commits

Author SHA1 Message Date
Red Bear OS 796faa0532 bootloader: drop side borders; even top/bottom rules around wordmark
The side box borders looked crooked: the wordmark's letters end at a different
column each row (a naturally ragged right edge), so the gap before the right
`|` varied row to row even though the pipe column was constant.

Replace the boxed frame with the wordmark block centered by its widest row and
bracketed by two horizontal rules of that exact width. A solid repeated rule is
always even, and with no side borders the ragged letter edge is never framed.
Width is now computed from the wordmark at runtime, so the rules and centering
stay self-consistent.

Verified: faithful render simulation shows identical top/bottom rules and a
centered block; cargo check passes for x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 19:58:38 +09:00
Red Bear OS ac6068f9ad bootloader: bold wordmark in a tight centered box
The first header render was ugly on a real (wide) UEFI console: the frame was
stretched to the full console width, leaving a small, thin figlet wordmark
floating in a huge dotted box, and the "standard" figlet line-art read as jagged
in the low-res boot font.

  - Switch the wordmark to the figlet "banner" font: bold solid #-block letters
    that read clearly at boot-console resolution (one line, pure ASCII).
  - Draw a tight box that hugs the wordmark and center the whole box on screen,
    instead of a full-width frame. On a 154-column console the box is ~73 wide
    and centered, not stretched edge to edge.
  - Left-justify each wordmark row (the art is column-0 aligned); centering rows
    individually would have broken the letters' vertical alignment.

Verified: cargo check passes for x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 19:49:20 +09:00
Red Bear OS 3fd4403ecb bootloader: framed centered wordmark, progress bars, color polish
Beautification pass on the text UI (all pure ASCII so it renders identically on
the UEFI Unicode console and the VGA CP437 console, which truncates chars to
bytes):

  - Os::text_columns(): new trait method to query the real console width.
    BIOS returns the VGA width; UEFI queries SimpleTextOutput QueryMode for the
    current mode's columns (both fall back to 80). Enables true centering.
  - draw_header now draws a full-width frame around a centered figlet "RedBear
    OS" wordmark, with a centered version/platform subtitle, in brand red/cyan.
    Falls back to a plain centered name on consoles narrower than the wordmark.
  - Loading progress lines (live / kernel / initfs) gain a fixed-width ASCII
    progress bar next to the MiB counters, and turn green on completion.
  - A centered green "Booting RedBear OS..." closes the loading screen.

Verified: cargo check passes for x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 18:46:31 +09:00
Red Bear OS d7d719775b bootloader: clean per-phase screens + Red Bear branding
Comprehensive fix for overlapping bootloader text. The loader ran three phases
(filesystem, resolution menu, loading) that all wrote to one never-fully-cleared
screen at absolute cursor positions, so they overprinted each other. Most
visibly, the loading phase began printing "live: .../..." exactly where the menu
had left the cursor (the "Autobooting" row), so "live" landed on top of the
countdown. clear_text() also only ran for the 2nd+ video output, so on a single
display the menu was drawn under the header.

Introduce draw_header(os): clear the screen and draw a consistent, branded
"Red Bear OS" title + version/platform + separator. Call it at the start of each
phase so every screen is clean and no phase overprints another:
  - Phase 1 (filesystem / password prompt)
  - Phase 2 (resolution menu, per video output)
  - Phase 3 (kernel/initfs load + optional live preload)

Also:
  - Rebrand user-facing strings: "Redox OS Bootloader" -> "Red Bear OS", and
    the env editor title -> "Red Bear OS Boot Environment Editor". (RedoxFS /
    the RedoxFtw initfs magic are left as-is: those are real format names.)
  - Drop the raw "Hardware descriptor: {:x?}" Debug dump from the UI.
  - Indent all loading-phase progress lines (RedoxFS/live/kernel/initfs) to
    match the header for a consistent layout.

Verified: cargo check passes for x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 18:35:09 +09:00
Red Bear OS 6407d28c26 bootloader: fix duplicate "Autobooting in X seconds" line
The countdown row was derived as list_y - 4, which landed one row too low
(on the "Use Up/Down" line) instead of on the header line printed before the
loop. The in-loop countdown therefore rendered a second "Autobooting in X
seconds" copy directly below the static header, showing the phrase twice.

Capture the header's row with get_text_position() right before printing it, so
the loop updates that exact line in place — one countdown line, no stale copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 18:25:55 +09:00
Red Bear OS f159a47f1c bootloader: wire categorized resolution menu + UEFI garbled-text fix
Ports two pieces of menu work that were stranded on the abandoned
`bootloader-detached-pending` lineage (a divergent full-history upstream-1.0.0
rebase that was never merged into this build branch and never pushed):

  - 3d41cf7 "categorized resolution menu": replaces the flat area-sorted grid
    selector with a categorized list (4K / 2.5K / FullHD / HD / 1024x768) plus
    a "More resolutions" sub-menu (select_obscure) for uncommon modes.
  - 9a12ee2 "fix garbled text — pad all console lines to consistent width":
    the UEFI text console does not clear old characters when a shorter line
    overwrites a longer one at the same position, so menu/countdown/live-mode
    lines are padded to fixed widths ({:<50}/{:<70}/{:<40}) and fully
    overwritten each frame.

Adapted to this branch's select_mode signature and merged with this branch's
best_resolution() fallback: the initial highlight prefers 1280x720, then the
display's EDID-preferred mode, then the largest available — so hardware native
resolution is not lost by the categorized default.

Verified: cargo check passes for both x86_64-unknown-uefi (--bin) and
x86-unknown-none (--lib). Supersedes the menu work on
bootloader-detached-pending, which is being removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 18:15:31 +09:00
Red Bear OS 092784c552 bootloader: restore color boot output (lost on 1.0.0 rebase)
Re-adds the TextColor enum + Os::set_text_color (no-op default, VGA fg
override for BIOS, EFI SetAttribute for UEFI) and colors the default-resolution
message green + the autoboot countdown yellow. The countdown + default
1280x720 were already present; only the color mechanism was lost when the
bootloader was rebased to upstream 1.0.0 (the color commits stayed on
bootloader-detached-pending).
2026-07-16 22:04:18 +09:00
Red Bear OS 6e11964121 uefi: demote expected no-GPT and no-EDID fallbacks from warn to debug 2026-07-16 07:30:29 +09:00
Red Bear OS c78c3a63c0 BIOS: implement get_key_timeout with INT 16h/01h polling + INT 15h/86h delay
Fixes autoboot countdown blocking forever because the default trait impl
calls blocking get_key(). Uses non-destructive key check (INT 16h AH=01h)
with EDX sentinel to detect key availability without EFLAGS access, and
INT 15h AH=86h for 10ms polling intervals.
2026-07-12 05:02:08 +03:00
Red Bear OS 2f79630b0e Cargo.toml: 1.0.0+rb0.3.0 -> 1.0.0+rb0.3.1
Bootloader fork was the only Cat 2 fork that didn't get its
+rb suffix updated during the 0.3.0->0.3.1 sync-versions run.
Manual fix applied. Also regenerated its Cargo.lock to match
the new version.
2026-07-12 01:20:43 +03:00
Red Bear OS 6b43b7f9c5 0.3.0: bump local fork lockfile versions to +rb0.3.0 2026-07-06 20:41:30 +03:00
vasilito 62fa9f9a1b 0.3.0: bump version suffix to +rb0.3.0 2026-07-06 19:47:08 +03:00
vasilito ea5b2418e6 redbear: migrate bootloader patches into local fork
Apply the full Red Bear bootloader patch set:
- P0-gpt-partition-offset
- fix-uefi-alloc-panic
- redox.patch (Makefile/mk and misc fixes)
- P1-bootloader-timeout-and-default-resolution
- P2-live-preload-guard
- P3-uefi-live-image-safe-read
- P4-live-large-iso-boot
- P5-live-preload-cap-1gib

Also switch redoxfs dependency to the local fork path.
2026-07-06 08:03:08 +03:00
Red Bear OS 6c3c312819 fix: use local path deps for redox_syscall 2026-07-05 23:54:34 +03:00
Red Bear OS e7bc6a56dc version: use +rb0.2.5 build metadata instead of -rb0.2.5 pre-release
Semver pre-release suffix (-rb0.2.5) breaks Cargo's [patch.crates-io]
matching for transitive deps. Build metadata (+rb0.2.5) is semver-
compatible: ^0.9.0 matches 0.9.0+rb0.2.5, patch redirection works,
and the Red Bear suffix is still visible in the version string.
2026-07-05 09:16:12 +03:00
Red Bear OS db006c0b72 fork: add Red Bear author attribution to Cargo.toml 2026-07-05 08:48:16 +03:00
Red Bear OS 83a34a1515 fork: bump to -rb1 version suffix
Per local/AGENTS.md \xC2\xA7 "Category 2 - Local forks of upstream packages",
all Cat 2 forks must use the `<upstream-tag>-rb<N>` version convention. The
`-rb1` suffix is a Cargo pre-release identifier that prevents upstream
`<upstream-tag>` from silently substituting for this fork in transitive
dependency resolution.

- The base tag (e.g. 0.9.0 for redox_syscall) tracks upstream
  unchanged.
- The `rb1` part records that one Red Bear patch round has been
  applied on top of the upstream tag.

This is the initial policy enforcement pass. The branch is
considered unfrozen at this point; once a freeze is declared, future
`-rbN` increments will land on a frozen `0.X.Y` branch per
local/AGENTS.md.
2026-07-04 10:07:31 +03:00
Red Bear OS 3cc7ed909c bios: add -Zunstable-options for custom JSON target spec
Rust nightly requires -Zunstable-options for custom target JSON specs
(such as x86-unknown-none.json). Without it, the build fails with:
  error: error loading target specification: custom targets are
  unstable and require `-Zunstable-options`

UEFI already uses the built-in x86_64-unknown-uefi target, so it
didn't need this flag. The kernel recipe already has this fix; the
bootloader BIOS Makefile was missing it.
2026-06-28 04:40:40 +03:00
Red Bear OS 89c68d0738 Red Bear OS bootloader baseline from 0.1.0 pre-patched archive 2026-06-27 09:21:43 +03:00
593 changed files with 7448 additions and 112634 deletions
+2 -12
View File
@@ -1,12 +1,2 @@
target/
sysroot/
# Local settings folder for Visual Studio Code
.vscode/
# Local settings folder for Jetbrains products (RustRover, IntelliJ, CLion)
.idea/
# Local settings folder for Visual Studio Professional
.vs/
# Local settings folder for the devcontainer extension that most IDEs support.
.devcontainer/
Cargo.lock
/build
/target
+29 -36
View File
@@ -1,42 +1,35 @@
image: "redoxos/redoxer:latest"
workflow:
rules:
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PROJECT_NAMESPACE == "redox-os"'
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
before_script:
- apt-get install nasm
- rustup component add rust-src
stages:
- build
- cross-build
- test
- host
build:i686:
stage: host
script:
- mkdir -p target/i686
- cd target/i686
- TARGET=x86-unknown-none make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.bin `pwd`/bootloader-live.bin
build:x86_64:
stage: host
script:
- mkdir -p target/x86_64
- cd target/x86_64
- TARGET=x86_64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
build:aarch64:
stage: host
script:
- mkdir -p target/aarch64
- cd target/aarch64
- TARGET=aarch64-unknown-uefi make -f ${CI_PROJECT_DIR}/Makefile -C `pwd` `pwd`/bootloader.efi `pwd`/bootloader-live.efi
fmt:
stage: build
stage: host
script:
- rustup component add rustfmt
- CHECK_ONLY=1 ./fmt.sh
x86_64:
stage: build
script:
- rustup component add rustfmt
- ./check.sh
i586:
stage: cross-build
script:
- ./check.sh --arch=i586
aarch64:
stage: cross-build
script:
- ./check.sh --arch=aarch64
riscv64gc:
stage: cross-build
script:
- ./check.sh --arch=riscv64gc
boot:
stage: test
script:
- timeout -s KILL 9m ./check.sh --test
- rustup component add rustfmt-preview
- cargo fmt -- --check
-92
View File
@@ -1,92 +0,0 @@
<!-- Thank you for taking the time to submit an issue! By following these comments and filling out the sections below, you can help the developers get the necessary information to fix your issue. Please provide a single issue per report. You can also preview this report before submitting it. Feel free to modify/remove sections to fit the nature of your issue. -->
<!-- Please search to check that your issue has not been created already. By preventing duplicate issues, you can help keep the repository organized. If your current issue has already been created and is still unresolved, you can contribute by commenting there. -->
<!-- Replace the empty checkbox [ ] below with a checked one [x] if you have already searched for your issue. -->
- [ ] I agree that I have searched opened and closed issues to prevent duplicates.
--------------------
## Description
<!-- Briefly summarize/describe the issue that you are experiencing below. -->
Replace me
## Environment info
<!-- To understand where your issue originates, please include some relevant information about your environment. -->
<!-- If you are using a pre-built release of Redox, please specify the release version below. -->
- Redox OS Release:
0.0.0 Remove me
<!-- If you have built Redox OS yourself, please provide the following information: -->
- Operating system:
Replace me
- `uname -a`:
`Replace me`
- `rustc -V`:
`Replace me`
- `git rev-parse HEAD`:
`Replace me`
<!-- Depending on your issue, additional information about your environment (network config, package versions, dependencies, etc.) can also help. You can list that below. -->
- Replace me:
Replace me
## Steps to reproduce
<!-- If possible, please list the steps to reproduce ("trigger") your issue below. Being detailed definitely helps speed up bug fixes. -->
1. Replace me
2. Replace me
3. ...
## Behavior
<!-- It may seem obvious to know what to expect, but isolating the behavior from everything else simplifies the development process. Remember to provide a single issue in this report. You can use the References section below to link your issues together. -->
<!-- Describe the behavior you expect your steps should yield (i.e., correct behavior). -->
- **Expected behavior**:
Replace me
<!-- Describe the behavior you observed when running your steps (i.e., buggy behavior). -->
- **Actual behavior**:
Replace me
<!-- **Logs?** Posting a log can help developers find your particular issue more easily. Please wrap your code in code blocks using triple back-ticks ``` to increase readability. -->
```
Replace me
```
<!-- **Solution?** Have a solution in mind? Propose your solution below. -->
- **Proposed solution**:
Replace me
<!-- **Screenshots?** Make it easier to get your point across with screenshots. You can drag & drop or paste your images below. -->
## Optional references
<!-- If you have found issues or pull requests that are related to or blocking this issue, please link them below. See https://help.github.com/articles/autolinked-references-and-urls/ for more options. You can also link related code snippets by providing the permalink. See https://help.github.com/articles/creating-a-permanent-link-to-a-code-snippet/ for more information. -->
Related to:
- #0000 Remove me
- Replace me
- ...
Blocked by:
- #0000 Remove me
- ...
## Optional extras
<!-- If you have other relevant information not found in other sections, you can include it below. -->
Replace me
<!-- **Code?** Awesome! You can also create a pull request with a reference to this issue. -->
<!-- **Files?** Attach your relevant files by dragging & dropping or pasting them below. -->
<!-- You also can preview your report before submitting it. Thanks for contributing to Redox! -->
@@ -1,25 +0,0 @@
**Problem**: [describe the problem you try to solve with this PR.]
**Solution**: [describe carefully what you change by this PR.]
**Changes introduced by this pull request**:
- [...]
- [...]
- [...]
**Drawbacks**: [if any, describe the drawbacks of this pull request.]
**TODOs**: [what is not done yet.]
**Fixes**: [what issues this fixes.]
**State**: [the state of this PR, e.g. WIP, ready, etc.]
**Blocking/related**: [issues or PRs blocking or being related to this issue.]
**Other**: [optional: for other relevant information that should be known or cannot be described in the other fields.]
------
_The above template is not necessary for smaller PRs._
+2
View File
@@ -0,0 +1,2 @@
[editor]
auto-format = false
+5
View File
@@ -0,0 +1,5 @@
[[language]]
name = "rust"
# TODO: Add more targets (BIOS, x86_32)
# Uncomment this line and set cargo.target to your target to get accurate completions
# config = { cargo.target = "aarch64-unknown-uefi", check.targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] }
-84
View File
@@ -1,84 +0,0 @@
# Base functions intentionally removed/replaced by Red Bear commits.
# These exist in upstream but were replaced with RB-specific implementations
# or removed as part of RB refactoring. Documented per Phase 17 verification.
# initfs — RB uses different initfs tooling
bootstrap/src/initfs.rs:pub unsafe redox_fcntl_v0
initfs/src/lib.rs:pub root_inode
initfs/tools/src/lib.rs:allocate
initfs/tools/src/lib.rs:allocate_contents
initfs/tools/src/lib.rs:count
initfs/tools/src/lib.rs:new
initfs/tools/src/lib.rs:pub build_initfs
initfs/tools/src/lib.rs:write_inode_table
# graphics drivers — RB refactored KMS/display layer
drivers/graphics/console-draw/src/lib.rs:pub from_psf
drivers/graphics/driver-graphics/src/kms/connector.rs:pub set_connector_edid
drivers/graphics/driver-graphics/src/kms/connector.rs:update_from_edid
drivers/graphics/driver-graphics/src/kms/objects.rs:into_object
drivers/graphics/driver-graphics/src/kms/objects.rs:pub add_plane
drivers/graphics/driver-graphics/src/kms/objects.rs:pub get_framebuffer_maybe_closed
drivers/graphics/driver-graphics/src/kms/objects.rs:pub get_plane
drivers/graphics/driver-graphics/src/kms/objects.rs:pub plane_ids
drivers/graphics/driver-graphics/src/kms/objects.rs:pub planes
drivers/graphics/driver-graphics/src/kms/objects.rs:pub remove_framebuffer_if_closed
drivers/graphics/driver-graphics/src/kms/objects.rs:remove
drivers/graphics/driver-graphics/src/kms/objects.rs:try_from_object
drivers/graphics/driver-graphics/src/kms/properties.rs:pub remove_blob
drivers/graphics/driver-graphics/src/lib.rs:fb_has_any_use
drivers/graphics/driver-graphics/src/lib.rs:get_unique
drivers/graphics/driver-graphics/src/lib.rs:set_plane
# ihdgd (Intel GPU) — RB driver refactoring
drivers/graphics/ihdgd/src/device/mod.rs:add_kms_pipe
drivers/graphics/ihdgd/src/device/mod.rs:unsafe new
drivers/graphics/ihdgd/src/device/mod.rs:unsafe new_pci_bar
drivers/graphics/ihdgd/src/device/scheme.rs:get_unique
drivers/graphics/ihdgd/src/device/scheme.rs:set_plane
# vesad — RB framebuffer refactoring
drivers/graphics/vesad/src/scheme.rs:get_unique
drivers/graphics/vesad/src/scheme.rs:set_plane
# virtio-gpu — RB async GPU refactoring
drivers/graphics/virtio-gpud/src/main.rs:daemon
drivers/graphics/virtio-gpud/src/scheme.rs:async create_dumb_buffer_inner
drivers/graphics/virtio-gpud/src/scheme.rs:async disable_cursor
drivers/graphics/virtio-gpud/src/scheme.rs:async move_cursor
drivers/graphics/virtio-gpud/src/scheme.rs:async resource_attach_backing
drivers/graphics/virtio-gpud/src/scheme.rs:async resource_create_2d
drivers/graphics/virtio-gpud/src/scheme.rs:async send_request_cursor
drivers/graphics/virtio-gpud/src/scheme.rs:async update_cursor
drivers/graphics/virtio-gpud/src/scheme.rs:get_unique
drivers/graphics/virtio-gpud/src/scheme.rs:set_plane
# input daemons — RB uses different daemon pattern
drivers/inputd/src/main.rs:daemon
# USB HID — RB input refactoring
drivers/input/usbhidd/src/main.rs:send_gamepad_axis
drivers/input/usbhidd/src/main.rs:send_gamepad_button
# virtio-net — RB uses different daemon pattern
drivers/net/virtio-netd/src/main.rs:daemon
# pcid — RB PCI refactoring
drivers/pcid/src/driver_interface/mod.rs:connect_default
drivers/pcid/src/scheme.rs:pub mmap_prep
# redoxerd — RB executor refactoring
drivers/redoxerd/src/main.rs:pub getpty
# xhcid (USB) — RB USB refactoring
drivers/usb/xhcid/src/driver_interface.rs:pub xhci_dci
drivers/usb/xhcid/src/xhci/mod.rs:new
drivers/usb/xhcid/src/xhci/mod.rs:pub get
drivers/usb/xhcid/src/xhci/mod.rs:pub index
# netstack — RB network stack refactoring
netstack/src/main.rs:get_network_adapter
# ramfs — RB filesystem refactoring
ramfs/src/scheme.rs:as_inode
ramfs/src/scheme.rs:relpathat
Generated
+135 -2888
View File
File diff suppressed because it is too large Load Diff
+43 -143
View File
@@ -1,157 +1,57 @@
[workspace]
resolver = "2"
members = [
"audiod",
"config",
"daemon",
"dhcpd",
"dhcpv6d",
"netdiag",
"init",
"initfs",
"initfs/tools",
"ipcd",
"logd",
"netstack",
"ptyd",
"ramfs",
"redbear-ufw",
"randd",
"scheme-utils",
"zerod",
[package]
name = "redox_bootloader"
version = "1.0.0+rb0.3.1"
authors = ["Jeremy Soller <jackpot51@gmail.com>", "vasilito <adminpupkin@gmail.com>"]
edition = "2024"
"drivers/common",
"drivers/executor",
# UEFI uses bin target
[[bin]]
name = "bootloader"
path = "src/main.rs"
"drivers/acpid",
"drivers/hwd",
"drivers/pcid",
"drivers/rtcd",
"drivers/vboxd",
"drivers/inputd",
"drivers/virtio-core",
# BIOS uses lib target
[lib]
name = "bootloader"
path = "src/main.rs"
crate-type = ["staticlib"]
"drivers/audio/ac97d",
"drivers/audio/ihdad",
"drivers/audio/sb16d",
[dependencies]
bitflags = "1.3.2"
linked_list_allocator = "0.10.5"
log = "0.4.17"
redox_syscall = { path = "../syscall" }
spin = "0.9.5"
"drivers/graphics/console-draw",
"drivers/graphics/fbbootlogd",
"drivers/graphics/driver-graphics",
"drivers/graphics/fbcond",
"drivers/graphics/graphics-ipc",
"drivers/graphics/ihdgd",
"drivers/graphics/vesad",
"drivers/graphics/virtio-gpud",
[dependencies.redoxfs]
path = "../redoxfs"
default-features = false
features = ["log"]
"drivers/input/ps2d",
"drivers/input/usbhidd",
"drivers/thermald",
[target.'cfg(target_os = "uefi")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
"drivers/net/driver-network",
"drivers/net/e1000d",
"drivers/net/ixgbed",
"drivers/net/rtl8139d",
"drivers/net/rtl8168d",
"drivers/net/virtio-netd",
#TODO: riscv cannot use target_os = "uefi" at this time
[target.'cfg(target_arch = "riscv64")'.dependencies]
redox_uefi = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
redox_uefi_std = { git = "https://gitlab.redox-os.org/redox-os/uefi.git" }
"drivers/redoxerd",
[target."aarch64-unknown-uefi".dependencies]
dmidecode = "0.8.0"
"drivers/storage/ahcid",
"drivers/storage/bcm2835-sdhcid",
"drivers/storage/driver-block",
"drivers/storage/ided",
"drivers/storage/lived", # TODO: not really a driver...
"drivers/storage/nvmed",
"drivers/storage/usbscsid",
"drivers/storage/virtio-blkd",
[target."x86_64-unknown-uefi".dependencies]
x86 = "0.52.0"
"drivers/usb/xhcid",
"drivers/usb/usbctl",
"drivers/usb/usbhubd",
"drivers/usb/ucsid",
[target.'cfg(any(target_arch = "aarch64", target_arch = "riscv64"))'.dependencies]
byteorder = { version = "1", default-features = false }
fdt = { git = "https://github.com/repnop/fdt.git", rev = "2fb1409edd1877c714a0aa36b6a7c5351004be54" }
"drivers/acpi-rs",
"drivers/i2c/i2c-interface",
"drivers/i2c/i2cd",
"drivers/i2c/amd-mp2-i2cd",
"drivers/i2c/designware",
"drivers/i2c/dw-acpi-i2cd",
"drivers/i2c/intel-lpss-i2cd",
[features]
default = []
live = []
serial_debug = []
"drivers/gpio/gpiod",
"drivers/gpio/intel-gpiod",
"drivers/gpio/i2c-gpio-expanderd",
"drivers/input/i2c-hidd",
"drivers/input/intel-thc-hidd",
"drivers/acpi-resource",
]
# Bootstrap needs it's own profile configuration
exclude = ["bootstrap"]
# Low-level Redox OS crates should be kept in sync using workspace dependencies
# Remember to also update bootstrap dependencies, those are not in the workspace
[workspace.dependencies]
acpi = { git = "https://gitlab.redox-os.org/redox-os/acpi.git", branch = "redox-6.x" }
anyhow = "1"
bitflags = "2"
clap = "4"
drm = "0.15.0"
drm-sys = "0.8.1"
edid = "0.3.0" #TODO: edid is abandoned, fork it and maintain?
fdt = "0.1.5"
libc = "0.2.181"
log = "0.4"
libredox = { path = "../libredox", default-features = true }
orbclient = "0.3.51"
parking_lot = { git = "https://github.com/Amanieu/parking_lot.git", rev = "0.12.3", default-features = false }
pico-args = "0.5"
plain = "0.2.3"
ransid = "0.4"
redox_event = "0.4.8"
redox-ioctl = { path = "../relibc/redox-ioctl" }
redox-log = { git = "https://gitlab.redox-os.org/redox-os/redox-log.git" }
redox-rt = { path = "../relibc/redox-rt", default-features = false }
redox-scheme = { path = "../redox-scheme" }
redox_syscall = { path = "../syscall", features = ["std"] }
redox_termios = "0.1.3"
ron = "0.8.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
slab = "0.4.9"
smallvec = "1"
spin = "0.10"
static_assertions = "1.1.0"
thiserror = "2"
toml = "1"
[workspace.lints.rust]
missing_docs = "allow" #TODO: set to deny when all public functions are documented
[workspace.lints.clippy]
missing_safety_doc = "warn" #TODO: set to deny when all safety documentation is completed
precedence = "deny"
[patch.crates-io]
# Red Bear OS Phase I: s2idle / Modern Standby support.
# The [patch.crates-io] replaces the upstream gitlab.redox-os.org
# redox_syscall (which lacks the new AcpiVerb::EnterS2Idle /
# ExitS2Idle variants) with the local fork at
# local/sources/syscall/ (a sibling directory of base/, both
# under local/sources/). The local fork is the upstream
# gitlab.redox-os.org/redox-os/syscall @ 79cb6d9 with our
# Red Bear OS P1 commit (cfa7f0c) on top. The version field
# stays at upstream 0.8.1 — periodic rebase via
# 'git fetch upstream && git rebase upstream/master' is the
# workflow when upstream changes. Hardware-agnostic — works
# for any platform with Modern Standby firmware (Dell, HP,
# Lenovo, LG Gram, etc.).
redox_syscall = { path = "../syscall" }
libredox = { path = "../libredox" }
redox-scheme = { path = "../redox-scheme" }
[patch."https://gitlab.redox-os.org/redox-os/relibc.git"]
redox-ioctl = { path = "../relibc/redox-ioctl" }
redoxfs = { path = "../redoxfs" }
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 Redox OS
Copyright (c) 2017-2022 Redox OS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+21 -126
View File
@@ -1,131 +1,26 @@
TARGET ?= x86_64-unknown-redox
LINKER ?= $(shell redoxer env which $(shell redoxer env printenv LD))
BOARD ?=
BUILD_TYPE ?= release
BUILD_FLAGS ?= --release
CARGO ?= redoxer
CARGO_HOST ?= env -u CARGO -u RUSTFLAGS cargo
TARGET?=x86_64-unknown-uefi
SOURCE:=$(dir $(realpath $(lastword $(MAKEFILE_LIST))))
BUILD:=$(CURDIR)
export RUST_TARGET_PATH?=$(SOURCE)/targets
CARGO_ARGS=--release --locked --target $(TARGET) \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem
SRC_DIR ?= $(CURDIR)
BUILD_DIR ?= $(shell pwd)/target/$(TARGET)/build
DESTDIR ?= ./sysroot
SYSROOT ?= $(shell pwd)/target/$(TARGET)/sysroot
TARGET_DIR = $(BUILD_DIR)/$(TARGET)/$(BUILD_TYPE)
BUILD_FLAGS += --target-dir $(BUILD_DIR)
INITFS_BINS = init logd ramfs randd zerod \
acpid fbbootlogd fbcond hwd inputd lived \
pcid rtcd vesad
INITFS_DRIVERS_BINS = nvmed virtio-blkd virtio-gpud
BASE_BINS = inputd pcid redoxerd audiod dhcpd ipcd ptyd netstack
DRIVERS_BINS = e1000d ihdad ihdgd ixgbed rtl8139d rtl8168d \
usbctl usbhidd usbhubd usbscsid virtio-netd xhcid
ifneq (,$(filter i586-unknown-redox i686-unknown-redox x86_64-unknown-redox,$(TARGET)))
INITFS_BINS += ps2d
INITFS_DRIVERS_BINS += ahcid ided
DRIVERS_BINS += ac97d sb16d vboxd
endif
ifeq ($(TARGET),aarch64-unknown-redox)
ifeq ($(BOARD),raspi3b)
INITFS_BINS += bcm2835-sdhcid
endif
endif
INITFS_CARGO_ARGS = $(foreach bin,$(INITFS_BINS),-p $(bin))
INITFS_DRIVERS_CARGO_ARGS = $(foreach bin,$(INITFS_DRIVERS_BINS),-p $(bin))
BASE_CARGO_ARGS = $(foreach bin,$(BASE_BINS),-p $(bin))
DRIVERS_CARGO_ARGS = $(foreach bin,$(DRIVERS_BINS),-p $(bin))
.PHONY: all base install install-base test
all: base
install: install-base
include $(SOURCE)/mk/$(TARGET).mk
clean:
rm -rf $(SRC_DIR)/target $(SRC_DIR)/sysroot $(SYSROOT) $(TARGET_DIR)
rm -rf build target
# test if booting
test: all
$(MAKE) install
redoxer exec --folder ./sysroot/:/ true
$(BUILD)/filesystem:
mkdir -p $(BUILD)
rm -f $@.partial
mkdir $@.partial
fallocate -l 1MiB $@.partial/kernel
mv $@.partial $@
# test with interactive gui
test-gui: all
$(MAKE) install
redoxer exec --gui --folder ./sysroot/:/ ion
# -----------------------------------------------------------------------------
# base
# -----------------------------------------------------------------------------
$(SYSROOT)/bin/redoxfs:
REDOXER_SYSROOT=$(SYSROOT) redoxer pkg redoxfs
base:
@mkdir -pv "$(BUILD_DIR)"
# Build daemons and drivers
CARGO_PROFILE_RELEASE_OPT_LEVEL=s CARGO_PROFILE_RELEASE_PANIC=abort \
$(CARGO) build $(BUILD_FLAGS) \
--manifest-path "$(SRC_DIR)/Cargo.toml" \
$(BASE_CARGO_ARGS) $(DRIVERS_CARGO_ARGS)
# Build initfs daemons and drivers
# FIXME fix whatever issue (feature unification?) causes most logs to be omitted
# if this is merged with the above build command.
CARGO_PROFILE_RELEASE_OPT_LEVEL=s CARGO_PROFILE_RELEASE_PANIC=abort \
$(CARGO) build $(BUILD_FLAGS) \
--manifest-path "$(SRC_DIR)/Cargo.toml" \
$(INITFS_CARGO_ARGS) $(INITFS_DRIVERS_CARGO_ARGS)
# Build bootstrap
cd "$(SRC_DIR)/bootstrap" && RUSTFLAGS= $(CARGO) rustc $(BUILD_FLAGS) \
-- -Ctarget-feature=+crt-static -Clinker="$(LINKER)"
install-base: base $(SYSROOT)/bin/redoxfs
@mkdir -pv "$(DESTDIR)/usr/bin" "$(DESTDIR)/usr/lib/drivers"
@mkdir -pv "$(DESTDIR)/usr/lib/init.d/" "$(DESTDIR)/usr/lib/pcid.d"
# Distribute binaries
@for bin in $(BASE_BINS); do \
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/bin"; \
done
@for bin in $(DRIVERS_BINS); do \
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/lib/drivers"; \
done
# Copy configurations
@cp -v "$(SRC_DIR)/init.d"/* "$(DESTDIR)/usr/lib/init.d/"
@find "$(SRC_DIR)/drivers" -maxdepth 3 -type f -name 'config.toml' | while read -r conf; do \
driver=$$(basename "$$(dirname "$$conf")"); \
cp -v "$$conf" "$(DESTDIR)/usr/lib/pcid.d/$$driver.toml"; \
done
rm -rf "$(BUILD_DIR)/initfs"
# Distribute initfs binaries
@mkdir -pv "$(BUILD_DIR)/initfs/bin" "$(BUILD_DIR)/initfs/lib/drivers"
for bin in $(INITFS_BINS); do \
cp -v "$(TARGET_DIR)/$$bin" "$(BUILD_DIR)/initfs/bin"; \
done
for bin in $(INITFS_DRIVERS_BINS); do \
cp -v "$(TARGET_DIR)/$$bin" "$(BUILD_DIR)/initfs/lib/drivers"; \
done
cp "$(SYSROOT)/bin/redoxfs" "$(BUILD_DIR)/initfs/bin"
# Copy initfs config files
@mkdir -p "$(BUILD_DIR)/initfs/lib/init.d" "$(BUILD_DIR)/initfs/lib/pcid.d"
cp "$(SRC_DIR)/init.initfs.d"/* "$(BUILD_DIR)/initfs/lib/init.d/"
cp "$(SRC_DIR)/drivers/initfs.toml" "$(BUILD_DIR)/initfs/lib/pcid.d/initfs.toml"
# Build initfs
$(CARGO_HOST) run --manifest-path "$(SRC_DIR)/initfs/tools/Cargo.toml" --bin redox-initfs-ar -- \
"$(BUILD_DIR)/initfs" "$(TARGET_DIR)/bootstrap" -o "$(BUILD_DIR)/initfs.img"
# Distribute initfs
@mkdir -pv "$(DESTDIR)/usr/lib/boot"
cp -v "$(BUILD_DIR)/initfs.img" "$(DESTDIR)/usr/lib/boot/initfs"
# Device file symlinks
@mkdir -pv "$(DESTDIR)/dev"
ln -sf /scheme/null $(DESTDIR)/dev/null
ln -sf /scheme/pty/ptmx $(DESTDIR)/dev/ptmx
ln -sf /scheme/rand $(DESTDIR)/dev/random
ln -sf /scheme/rand $(DESTDIR)/dev/urandom
ln -sf /scheme/zero $(DESTDIR)/dev/zero
ln -sf libc:tty $(DESTDIR)/dev/tty
ln -sf libc:stdin $(DESTDIR)/dev/stdin
ln -sf libc:stdout $(DESTDIR)/dev/stdout
ln -sf libc:stderr $(DESTDIR)/dev/stderr
$(BUILD)/filesystem.bin: $(BUILD)/filesystem
mkdir -p $(BUILD)
rm -f $@.partial
fallocate -l 254MiB $@.partial
redoxfs-ar $@.partial $<
mv $@.partial $@
+51 -32
View File
@@ -1,43 +1,62 @@
# Base
# Bootloader
Repository containing various system daemons, that are considered fundamental for the OS.
Redox OS Bootloader
You can see what each component does in the following list:
## Requirements
- audiod : Daemon used to process the sound drivers audio
- bootstrap : First code that the kernel executes, responsible for spawning the init daemon
- daemon : Redox daemon library
- drivers
- init : Daemon used to start most system components and programs
- initfs : Filesystem with the necessary system components to run RedoxFS
- ipcd : Daemon used for inter-process communication
- logd : Daemon used to log system components and daemons
- netstack : Daemon used for networking
- ptyd : Daemon used for pseudo-terminal
- ramfs : RAM filesystem
- randd : Daemon used for random number generation
- zerod : Daemon used to discard all writes and fill read buffers with zero
These software needs to be available on the PATH at build time:
+ [mtools](https://www.gnu.org/software/mtools/)
+ [nasm](https://nasm.us/)
+ [redoxfs-ar](https://gitlab.redox-os.org/redox-os/redoxfs)
## Building
```sh
make TARGET=<triplet> BUILD=build all
```
The `<triplet>` is one of:
| ARCH | Boot Mode | Triplets |
|---|---|---|
| `i686` | BIOS | `x86-unknown-none` |
| `x86_64` | BIOS | `x86-unknown-none` |
| `x86_64` | UEFI | `x86_64-unknown-uefi` |
| `aarch64` | UEFI | `aarch64-unknown-uefi` |
| `riscv64gc` | UEFI | `riscv64gc-unknown-uefi` |
See [mk directory](./mk) for more information of how the build is working.
## Entry points
Please read [Boot Process](https://doc.redox-os.org/book/boot-process.html) in the Redox OS Book for an introductory guide.
In this source code, some interesting files for entry points are:
+ BIOS boot stages: [asm/x86-unknown-none/bootloader.asm](./asm/x86-unknown-none/bootloader.asm)
+ BIOS boot entry: `fn start` at [src/os/bios/mod.rs](./src/os/bios/mod.rs)
+ UEFI boot entry: `fn main` at [src/os/uefi/mod.rs](src/os/uefi/mod.rs)
+ Common boot process: `fn main` at [src/main.rs](src/main.rs)
+ UEFI kernel entry: `fn kernel_entry` in each arch:
- `x86_64`: [src/os/uefi/arch/x86_64.rs](src/os/uefi/arch/x86_64.rs)
- `aarch64`: [src/os/uefi/arch/aarch64.rs](src/os/uefi/arch/aarch64.rs)
- `riscv64gc`: [src/os/uefi/arch/riscv64/mod.rs](src/os/uefi/arch/riscv64/mod.rs)
## Debugging
### QEMU
```sh
make TARGET=<triplet> BUILD=build qemu
```
## How To Contribute
To learn how to contribute you need to read the following document:
To learn how to contribute to this system component you need to read the following document:
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
If you want to contribute to drivers read its [README](drivers/README.md)
## Development
To learn how to do development with these system components inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
### How To Build
It is recommended to build this system component via the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page.
To build and test outside the build system, [install redoxer](https://doc.redox-os.org/book/ci.html) then use `check.sh` script to build or test:
- `./check.sh` - Check build for x86_64
- `./check.sh --arch=ARCH` - Check build for specific ARCH (`aarch64`, `i586`, `riscv64gc`)
- `./check.sh --all` - Check build for all ARCH
- `./check.sh --test` - Check the base system boots up on x86_64
You can also use `make install` to inspect the content on `./sysroot`, or `make test-gui` to test booting with orbital interactively.
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
+17
View File
@@ -0,0 +1,17 @@
interrupt_vector_table:
b . @ Reset
b .
b . @ SWI instruction
b .
b .
b .
b .
b .
.comm stack, 0x10000 @ Reserve 64k stack in the BSS
_start:
.globl _start
ldr sp, =stack+0x10000 @ Set up the stack
bl kstart @ Jump to the main function
1:
b 1b @ Halt
+31
View File
@@ -0,0 +1,31 @@
sectalign off
; stage 1 is sector 0, loaded at 0x7C00
%include "stage1.asm"
; GPT area from sector 1 to 33, loaded at 0x7E00
times (33*512) db 0
; stage 2, loaded at 0xC000
stage2:
%include "stage2.asm"
align 512, db 0
stage2.end:
; the maximum size of stage2 is 4 KiB
times (4*1024)-($-stage2) db 0
; ISO compatibility, uses up space until 0x12400
%include "iso.asm"
times 3072 db 0 ; Pad to 0x13000
; stage3, loaded at 0x13000
stage3:
%defstr STAGE3_STR %[STAGE3]
incbin STAGE3_STR
align 512, db 0
.end:
; the maximum size of the boot loader portion is 384 KiB
times (384*1024)-($-$$) db 0
+176
View File
@@ -0,0 +1,176 @@
SECTION .text
USE16
cpuid_required_features:
.edx equ cpuid_edx.fpu | cpuid_edx.pse | cpuid_edx.pge | cpuid_edx.fxsr
.ecx equ 0
cpuid_check:
; If bit 21 of EFLAGS can be changed, then CPUID is supported
pushfd ;Save EFLAGS
pushfd ;Store EFLAGS
xor dword [esp],0x00200000 ;Invert the ID bit in stored EFLAGS
popfd ;Load stored EFLAGS (with ID bit inverted)
pushfd ;Store EFLAGS again (ID bit may or may not be inverted)
pop eax ;eax = modified EFLAGS (ID bit may or may not be inverted)
xor eax,[esp] ;eax = whichever bits were changed
popfd ;Restore original EFLAGS
test eax,0x00200000 ;eax = zero if ID bit can't be changed, else non-zero
jz .no_cpuid
mov eax, 1
cpuid
and edx, cpuid_required_features.edx
cmp edx, cpuid_required_features.edx
jne .error
and ecx, cpuid_required_features.ecx
cmp ecx, cpuid_required_features.ecx
jne .error
ret
.no_cpuid:
mov si, .msg_cpuid
call print
mov si, .msg_line
call print
jmp .halt
.error:
push ecx
push edx
mov si, .msg_features
call print
mov si, .msg_line
call print
mov si, .msg_edx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.edx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.edx
call print_hex
mov si, .msg_line
call print
mov si, .msg_ecx
call print
pop ebx
push ebx
shr ebx, 16
call print_hex
pop ebx
call print_hex
mov si, .msg_must_contain
call print
mov ebx, cpuid_required_features.ecx
shr ebx, 16
call print_hex
mov ebx, cpuid_required_features.ecx
call print_hex
mov si, .msg_line
call print
.halt:
cli
hlt
jmp .halt
.msg_cpuid: db "CPUID not supported",0
.msg_features: db "Required CPU features are not present",0
.msg_line: db 13,10,0
.msg_edx: db "EDX ",0
.msg_ecx: db "ECX ",0
.msg_must_contain: db " must contain ",0
cpuid_edx:
.fpu equ 1 << 0
.vme equ 1 << 1
.de equ 1 << 2
.pse equ 1 << 3
.tsc equ 1 << 4
.msr equ 1 << 5
.pae equ 1 << 6
.mce equ 1 << 7
.cx8 equ 1 << 8
.apic equ 1 << 9
.sep equ 1 << 11
.mtrr equ 1 << 12
.pge equ 1 << 13
.mca equ 1 << 14
.cmov equ 1 << 15
.pat equ 1 << 16
.pse_36 equ 1 << 17
.psn equ 1 << 18
.clfsh equ 1 << 19
.ds equ 1 << 21
.acpi equ 1 << 22
.mmx equ 1 << 23
.fxsr equ 1 << 24
.sse equ 1 << 25
.sse2 equ 1 << 26
.ss equ 1 << 27
.htt equ 1 << 28
.tm equ 1 << 29
.ia64 equ 1 << 30
.pbe equ 1 << 31
cpuid_ecx:
.sse3 equ 1 << 0
.pclmulqdq equ 1 << 1
.dtes64 equ 1 << 2
.monitor equ 1 << 3
.ds_cpl equ 1 << 4
.vmx equ 1 << 5
.smx equ 1 << 6
.est equ 1 << 7
.tm2 equ 1 << 8
.ssse3 equ 1 << 9
.cnxt_id equ 1 << 10
.sdbg equ 1 << 11
.fma equ 1 << 12
.cmpxchg16b equ 1 << 13
.xtpr equ 1 << 14
.pdcm equ 1 << 15
.pcid equ 1 << 17
.dca equ 1 << 18
.sse4_1 equ 1 << 19
.sse4_2 equ 1 << 20
.x2apic equ 1 << 21
.movbe equ 1 << 22
.popcnt equ 1 << 23
.tsc_deadline equ 1 << 24
.aes equ 1 << 25
.xsave equ 1 << 26
.osxsave equ 1 << 27
.avx equ 1 << 28
.f16c equ 1 << 29
.rdrand equ 1 << 30
.hypervisor equ 1 << 31
+128
View File
@@ -0,0 +1,128 @@
SECTION .text ; cannot use .data
struc GDTEntry
.limitl resw 1
.basel resw 1
.basem resb 1
.attribute resb 1
.flags__limith resb 1
.baseh resb 1
endstruc
gdt_attr:
.present equ 1 << 7
.ring1 equ 1 << 5
.ring2 equ 1 << 6
.ring3 equ 1 << 5 | 1 << 6
.user equ 1 << 4
;user
.code equ 1 << 3
; code
.conforming equ 1 << 2
.readable equ 1 << 1
; data
.expand_down equ 1 << 2
.writable equ 1 << 1
.accessed equ 1 << 0
;system
; legacy
.tssAvailabe16 equ 0x1
.ldt equ 0x2
.tssBusy16 equ 0x3
.call16 equ 0x4
.task equ 0x5
.interrupt16 equ 0x6
.trap16 equ 0x7
.tssAvailabe32 equ 0x9
.tssBusy32 equ 0xB
.call32 equ 0xC
.interrupt32 equ 0xE
.trap32 equ 0xF
; long mode
.ldt32 equ 0x2
.tssAvailabe64 equ 0x9
.tssBusy64 equ 0xB
.call64 equ 0xC
.interrupt64 equ 0xE
.trap64 equ 0xF
gdt_flag:
.granularity equ 1 << 7
.available equ 1 << 4
;user
.default_operand_size equ 1 << 6
; code
.long_mode equ 1 << 5
; data
.reserved equ 1 << 5
gdtr:
dw gdt.end + 1 ; size
dq gdt ; offset
gdt:
.null equ $ - gdt
dq 0
.lm64_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
at GDTEntry.flags__limith, db gdt_flag.long_mode
at GDTEntry.baseh, db 0
iend
.lm64_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0
at GDTEntry.baseh, db 0
iend
.pm32_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm32_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF | gdt_flag.granularity | gdt_flag.default_operand_size
at GDTEntry.baseh, db 0
iend
.pm16_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code | gdt_attr.readable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.pm16_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0xFFFF
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0xF
at GDTEntry.baseh, db 0
iend
.end equ $ - gdt
+161
View File
@@ -0,0 +1,161 @@
; Simple ISO emulation with el torito
; Fill until CD sector 0x10
times (0x10*2048)-($-$$) db 0
; Volume record
;TODO: fill in more fields
iso_volume_record:
db 1 ; Type volume record
db "CD001" ; Identifier
db 1 ; Version
db 0 ; Unused
times 32 db ' ' ; System identifier
.volume_id: ; Volume identifier
db 'Redox OS'
times 32-($-.volume_id) db ' '
times 8 db 0 ; Unused
db 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15 ; Volume space size (0x15)
times 32 db 0 ; Unused
db 0x01, 0x00, 0x00, 0x01 ; Volume set size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x00, 0x08, 0x08, 0x00 ; Logical block size in little and big endian
times 156-($-iso_volume_record) db 0
; Root directory entry
.root_directory:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
times 128 db ' ' ; Volume set identifier
times 128 db ' ' ; Publisher identifier
times 128 db ' ' ; Data preparer identifier
times 128 db ' ' ; Application identifier
times 37 db ' ' ; Copyright file ID
times 37 db ' ' ; Abstract file ID
times 37 db ' ' ; Bibliographic file ID
times 881-($-iso_volume_record) db 0
db 1 ; File structure version
; Fill until CD sector 0x11
times (0x11*2048)-($-$$) db 0
; Boot record
iso_boot_record:
db 0 ; Type boot record
db "CD001" ; Identifier
db 1 ; Version
db "EL TORITO SPECIFICATION" ; Boot system identifier
times 0x47-($ - iso_boot_record) db 0 ; Padding
dd 0x13 ; Sector of boot catalog
; Fill until CD sector 0x12
times (0x12*2048)-($-$$) db 0
; Terminator
iso_terminator:
db 0xFF ; Type terminator
db "CD001" ; Identifier
db 1 ; Version
; Fill until CD sector 0x13
times (0x13*2048)-($-$$) db 0
; Boot catalog
iso_boot_catalog:
; Validation entry
.validation:
db 1 ; Header ID
db 0 ; Platform ID (x86)
dw 0 ; Reserved
times 24 db 0 ; ID string
dw 0x55aa ; Checksum
dw 0xaa55 ; Key
; Default entry
.default:
db 0x88 ; Bootable
db 4 ; Hard drive emulation
dw 0 ; Load segment (0 is platform default)
db 0xEE ; Partition type (0xEE is protective MBR)
db 0 ; Unused
dw 1 ; Sector count
dd 0 ; Start address for virtual disk
times 20 db 0 ; Padding
; EFI section header entry
.efi_section_header:
db 0x91 ; Final header
db 0xEF ; Platform ID (EFI)
dw 1 ; Number of section header entries
times 28 db 0 ; ID string
; EFI section entry
.efi_section_entry:
db 0x88 ; Bootable
db 0 ; No emulation
dw 0 ; Load segment (0 is platform default)
db 0 ; Partition type (not used)
db 0 ; Unused
dw 512 ; Sector count (1 MiB = 512 CD sectors)
dd 512 ; Start address for virtual disk (1 MiB = 512 CD sectors)
times 20 db 0 ; Padding
; Fill until CD sector 0x14
times (0x14*2048)-($-$$) db 0
iso_root_directory:
.self:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x00 ; File identifier
.parent:
db 0x22 ; Length of entry
db 0x00 ; Length of extended attributes
db 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14 ; Location of extent (0x14)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x02 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x01 ; Length of file identifier
db 0x01 ; File identifier
.boot_cat:
db 0x2C ; Length of entry
db 0x00 ; Length of extended attributes
db 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13 ; Location of extent (0x13)
db 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00 ; Size of extent
db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; Recording time
db 0x00 ; File flags
db 0x00 ; Interleaved file unit size
db 0x00 ; Interleaved gap size
db 0x01, 0x00, 0x00, 0x01 ; Volume sequence number
db 0x0A ; Length of file identifier
db "BOOT.CAT;1",0 ; File identifier
; Fill until CD sector 0x15
times (0x15*2048)-($-$$) db 0
+56
View File
@@ -0,0 +1,56 @@
SECTION .text
USE32
long_mode:
.func: dq 0
.page_table: dd 0
.entry:
; disable interrupts
cli
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
; enable FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 5 | 1 << 4
mov cr4, eax
; load long mode GDT
lgdt [gdtr]
; enable long mode
mov ecx, 0xC0000080 ; Read from the EFER MSR.
rdmsr
or eax, 1 << 11 | 1 << 8 ; Set the Long-Mode-Enable and NXE bit.
wrmsr
; set page table
mov eax, [.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
or eax, 1 << 31 | 1 << 16 | 1 ;Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
mov cr0, eax
; far jump to enable Long Mode and load CS with 64 bit segment
jmp gdt.lm64_code:.inner
USE64
.inner:
; load all the other segments with 64 bit data segments
mov rax, gdt.lm64_data
mov ds, rax
mov es, rax
mov fs, rax
mov gs, rax
mov ss, rax
; jump to specified function
mov rax, [.func]
jmp rax
+67
View File
@@ -0,0 +1,67 @@
SECTION .text
USE16
; provide function for printing in x86 real mode
; print a string and a newline
; CLOBBER
; ax
print_line:
mov al, 13
call print_char
mov al, 10
jmp print_char
; print a string
; IN
; si: points at zero-terminated String
; CLOBBER
; si, ax
print:
pushf
cld
.loop:
lodsb
test al, al
jz .done
call print_char
jmp .loop
.done:
popf
ret
; print a character
; IN
; al: character to print
print_char:
pusha
mov bx, 7
mov ah, 0x0e
int 0x10
popa
ret
; print a number in hex
; IN
; bx: the number
; CLOBBER
; al, cx
print_hex:
mov cx, 4
.lp:
mov al, bh
shr al, 4
cmp al, 0xA
jb .below_0xA
add al, 'A' - 0xA - '0'
.below_0xA:
add al, '0'
call print_char
shl bx, 4
loop .lp
ret
+36
View File
@@ -0,0 +1,36 @@
SECTION .text
USE16
protected_mode:
.func: dd 0
.entry:
; disable interrupts
cli
; load protected mode GDT
lgdt [gdtr]
; set protected mode bit of cr0
mov eax, cr0
or eax, 1
mov cr0, eax
; far jump to load CS with 32 bit segment
jmp gdt.pm32_code:.inner
USE32
.inner:
; load all the other segments with 32 bit data segments
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; jump to specified function
mov eax, [.func]
jmp eax
+222
View File
@@ -0,0 +1,222 @@
ORG 0x7C00
SECTION .text
USE16
stage1: ; dl comes with disk
; initialize segment registers
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
; initialize stack
mov sp, 0x7C00
; initialize CS
push ax
push word .set_cs
retf
.set_cs:
; save disk number
mov [disk], dl
mov si, stage_msg
call print
mov al, '1'
call print_char
call print_line
; read CHS gemotry
; CL (bits 0-5) = maximum sector number
; CL (bits 6-7) = high bits of max cylinder number
; CH = low bits of maximum cylinder number
; DH = maximum head number
mov ah, 0x08
mov dl, [disk]
xor di, di
int 0x13
jc error ; carry flag set on error
mov bl, ch
mov bh, cl
shr bh, 6
mov [chs.c], bx
shr dx, 8
inc dx ; returns heads - 1
mov [chs.h], dx
and cl, 0x3f
mov [chs.s], cl
mov eax, (stage2 - stage1) / 512
mov bx, stage2
mov cx, (stage3.end - stage2) / 512
mov dx, 0
call load
mov si, stage_msg
call print
mov al, '2'
call print_char
call print_line
jmp stage2.entry
; load some sectors from disk to a buffer in memory
; buffer has to be below 1MiB
; IN
; ax: start sector
; bx: offset of buffer
; cx: number of sectors (512 Bytes each)
; dx: segment of buffer
; CLOBBER
; ax, bx, cx, dx, si
; TODO rewrite to (eventually) move larger parts at once
; if that is done increase buffer_size_sectors in startup-common to that (max 0x80000 - startup_end)
load:
cmp cx, 127
jbe .good_size
pusha
mov cx, 127
call load
popa
add eax, 127
add dx, 127 * 512 / 16
sub cx, 127
jmp load
.good_size:
mov [DAPACK.addr], eax
mov [DAPACK.buf], bx
mov [DAPACK.count], cx
mov [DAPACK.seg], dx
call print_dapack
cmp byte [chs.s], 0
jne .chs
;INT 0x13 extended read does not work on CDROM!
mov dl, [disk]
mov si, DAPACK
mov ah, 0x42
int 0x13
jc error ; carry flag set on error
ret
.chs:
; calculate CHS
xor edx, edx
mov eax, [DAPACK.addr]
div dword [chs.s] ; divide by sectors
mov ecx, edx ; move sector remainder to ecx
xor edx, edx
div dword [chs.h] ; divide by heads
; eax has cylinders, edx has heads, ecx has sectors
; Sector cannot be greater than 63
inc ecx ; Sector is base 1
cmp ecx, 63
ja error_chs
; Head cannot be greater than 255
cmp edx, 255
ja error_chs
; Cylinder cannot be greater than 1023
cmp eax, 1023
ja error_chs
; Move CHS values to parameters
mov ch, al
shl ah, 6
and cl, 0x3f
or cl, ah
shl dx, 8
; read from disk using CHS
mov al, [DAPACK.count]
mov ah, 0x02 ; disk read (CHS)
mov bx, [DAPACK.buf]
mov dl, [disk]
push es ; save ES
mov es, [DAPACK.seg]
int 0x13
pop es ; restore EC
jc error ; carry flag set on error
ret
print_dapack:
mov bx, [DAPACK.addr + 2]
call print_hex
mov bx, [DAPACK.addr]
call print_hex
mov al, '#'
call print_char
mov bx, [DAPACK.count]
call print_hex
mov al, ' '
call print_char
mov bx, [DAPACK.seg]
call print_hex
mov al, ':'
call print_char
mov bx, [DAPACK.buf]
call print_hex
call print_line
ret
error_chs:
mov ah, 0
error:
call print_line
mov bh, 0
mov bl, ah
call print_hex
mov al, ' '
call print_char
mov si, error_msg
call print
call print_line
.halt:
cli
hlt
jmp .halt
%include "print.asm"
stage_msg: db "Stage ",0
error_msg: db "ERROR",0
disk: db 0
chs:
.c: dd 0
.h: dd 0
.s: dd 0
DAPACK:
db 0x10
db 0
.count: dw 0 ; int 13 resets this to # of blocks actually read/written
.buf: dw 0 ; memory buffer destination address (0:7c00)
.seg: dw 0 ; in memory page zero
.addr: dq 0 ; put the lba to read in this spot
times 446-($-$$) db 0
partitions: times 4 * 16 db 0
db 0x55
db 0xaa
+134
View File
@@ -0,0 +1,134 @@
SECTION .text
USE16
stage2.entry:
; check for required features
call cpuid_check
; enable A20-Line via IO-Port 92, might not work on all motherboards
in al, 0x92
or al, 2
out 0x92, al
mov dword [protected_mode.func], stage3.entry
jmp protected_mode.entry
%include "cpuid.asm"
%include "gdt.asm"
%include "long_mode.asm"
%include "protected_mode.asm"
%include "thunk.asm"
USE32
stage3.entry:
; stage3 stack at 448 KiB (512KiB minus 64KiB disk buffer)
mov esp, 0x70000
; push arguments
mov eax, thunk.int16
push eax
mov eax, thunk.int15
push eax
mov eax, thunk.int13
push eax
mov eax, thunk.int10
push eax
xor eax, eax
mov al, [disk]
push eax
mov eax, kernel.entry
push eax
mov eax, [stage3 + 0x18]
call eax
.halt:
cli
hlt
jmp .halt
kernel:
.stack: dq 0
.func: dq 0
.args: dq 0
.entry:
; page_table: usize
mov eax, [esp + 4]
mov [long_mode.page_table], eax
; stack: u64
mov eax, [esp + 8]
mov [.stack], eax
mov eax, [esp + 12]
mov [.stack + 4], eax
; func: u64
mov eax, [esp + 16]
mov [.func], eax
mov eax, [esp + 20]
mov [.func + 4], eax
; args: *const KernelArgs
mov eax, [esp + 24]
mov [.args], eax
; long_mode: usize
mov eax, [esp + 28]
test eax, eax
jz .inner32
mov eax, .inner64
mov [long_mode.func], eax
jmp long_mode.entry
.inner32:
; disable paging
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
;TODO: PAE (1 << 5)
; enable FXSAVE/FXRSTOR, Page Global, and Page Size Extension
mov eax, cr4
or eax, 1 << 9 | 1 << 7 | 1 << 4
mov cr4, eax
; set page table
mov eax, [long_mode.page_table]
mov cr3, eax
; enabling paging and protection simultaneously
mov eax, cr0
; Bit 31: Paging, Bit 16: write protect kernel, Bit 0: Protected Mode
or eax, 1 << 31 | 1 << 16 | 1
mov cr0, eax
; enable FPU
;TODO: move to Rust
mov eax, cr0
and al, 11110011b ; Clear task switched (3) and emulation (2)
or al, 00100010b ; Set numeric error (5) monitor co-processor (1)
mov cr0, eax
fninit
mov esp, [.stack]
mov eax, [.args]
push eax
mov eax, [.func]
call eax
.halt32:
cli
hlt
jmp .halt32
USE64
.inner64:
mov rsp, [.stack]
mov rax, [.func]
mov rdi, [.args]
call rax
.halt64:
cli
hlt
jmp .halt64
+149
View File
@@ -0,0 +1,149 @@
SECTION .text
USE32
thunk:
.int10:
mov dword [.func], .int10_real
jmp .enter
.int13:
mov dword [.func], .int13_real
jmp .enter
.int15:
mov dword [.func], .int15_real
jmp .enter
.int16:
mov dword [.func], .int16_real
jmp .enter
.func: dd 0
.esp: dd 0
.cr0: dd 0
.enter:
; save flags
pushfd
; save registers
pushad
; save esp
mov [.esp], esp
; load gdt
lgdt [gdtr]
; far jump to protected mode 16-bit
jmp gdt.pm16_code:.pm16
.exit:
; set segment selectors to 32-bit protected mode
mov eax, gdt.pm32_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; restore esp
mov esp, [.esp]
; restore registers
popad
; restore flags
popfd
; return
ret
USE16
.int10_real:
int 0x10
ret
.int13_real:
int 0x13
ret
.int15_real:
int 0x15
ret
.int16_real:
int 0x16
ret
.pm16:
; set segment selectors to protected mode 16-bit
mov eax, gdt.pm16_data
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; save cr0
mov eax, cr0
mov [.cr0], eax
; disable paging and protected mode
and eax, 0x7FFFFFFE
mov cr0, eax
; far jump to real mode
jmp 0:.real
.real:
; set segment selectors to real mode
mov eax, 0
mov ds, eax
mov es, eax
mov fs, eax
mov gs, eax
mov ss, eax
; set stack
mov esp, 0x7C00 - 64
; load registers and ES
pop es
pop edi
pop esi
pop ebp
pop ebx
pop edx
pop ecx
pop eax
; enable interrupts
sti
; call real mode function
call [.func]
; disable interrupts
cli
; save registers and ES
push eax
push ecx
push edx
push ebx
push ebp
push esi
push edi
push es
; load gdt (BIOS sometimes overwrites this)
lgdt [gdtr]
; restore cr0, will enable protected mode
mov eax, [.cr0]
mov cr0, eax
; far jump to protected mode 32-bit
jmp gdt.pm32_code:.exit
-19
View File
@@ -1,19 +0,0 @@
[package]
name = "audiod"
description = "Sound daemon"
version = "0.1.0"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
edition = "2021"
[dependencies]
daemon = { path = "../daemon" }
redox_syscall = { workspace = true, features = ["std"] }
libc.workspace = true
libredox = { workspace = true, features = ["mkns"] }
redox-scheme.workspace = true
scheme-utils = { path = "../scheme-utils" }
anyhow.workspace = true
ioslice = "0.6.0"
[lints]
workspace = true
-101
View File
@@ -1,101 +0,0 @@
//! The audio daemon for RedoxOS.
use std::mem::MaybeUninit;
use std::ptr::addr_of_mut;
use std::sync::{Arc, Mutex};
use std::{mem, process, slice, thread};
use anyhow::Context;
use ioslice::IoSlice;
use libredox::flag;
use libredox::{error::Result, Fd};
use redox_scheme::Socket;
use scheme_utils::ReadinessBased;
use daemon::SchemeDaemon;
use self::scheme::AudioScheme;
mod scheme;
extern "C" fn sigusr_handler(_sig: usize) {}
fn thread(scheme: Arc<Mutex<AudioScheme>>, pid: usize, hw_file: Fd) -> Result<()> {
loop {
let buffer = scheme.lock().unwrap().buffer();
let buffer_u8 = unsafe {
slice::from_raw_parts(buffer.as_ptr() as *const u8, mem::size_of_val(&buffer))
};
// Wake up the scheme thread
libredox::call::kill(pid, libredox::flag::SIGUSR1 as u32)?;
hw_file.write(&buffer_u8)?;
}
}
fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
// Handle signals from the hw thread
let new_sigaction = unsafe {
let mut sigaction = MaybeUninit::<libc::sigaction>::uninit();
addr_of_mut!((*sigaction.as_mut_ptr()).sa_flags).write(0);
libc::sigemptyset(addr_of_mut!((*sigaction.as_mut_ptr()).sa_mask));
addr_of_mut!((*sigaction.as_mut_ptr()).sa_sigaction).write(sigusr_handler as usize);
sigaction.assume_init()
};
libredox::call::sigaction(flag::SIGUSR1, Some(&new_sigaction), None)?;
let pid = libredox::call::getpid()?;
let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) {
Ok(fd) => fd,
Err(err) if err.errno() == syscall::ENODEV => {
eprintln!("audiod: no audio hardware detected");
return Ok(());
}
Err(err) => return Err(err).context("failed to open /scheme/audiohw"),
};
let socket = Socket::create().context("failed to create scheme")?;
let scheme = Arc::new(Mutex::new(AudioScheme::new()));
let _ = daemon.ready_sync_scheme(&socket, &mut *scheme.lock().unwrap());
// Enter a constrained namespace
let ns = libredox::call::mkns(&[
IoSlice::new(b"memory"),
IoSlice::new(b"rand"), // for HashMap
])
.context("failed to make namespace")?;
libredox::call::setns(ns).context("failed to set namespace")?;
// Spawn a thread to mix and send audio data
let scheme_thread = scheme.clone();
let _thread = thread::spawn(move || thread(scheme_thread, pid, hw_file));
let mut readiness = ReadinessBased::new(&socket, 16);
loop {
readiness.read_and_process_requests(&mut *scheme.lock().unwrap())?;
readiness.poll_all_requests(&mut *scheme.lock().unwrap())?;
readiness.write_responses()?;
}
}
fn main() {
SchemeDaemon::new(inner);
}
fn inner(x: SchemeDaemon) -> ! {
match daemon(x) {
Ok(()) => {
process::exit(0);
}
Err(err) => {
eprintln!("audiod: {}", err);
process::exit(1);
}
}
}
-177
View File
@@ -1,177 +0,0 @@
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use std::collections::VecDeque;
use std::str;
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EWOULDBLOCK};
use redox_scheme::scheme::SchemeSync;
use syscall::schemev2::NewFdFlags;
// The strict buffer size of the audiohw: driver
const HW_BUFFER_SIZE: usize = 512;
// The desired buffer size of each handle
const HANDLE_BUFFER_SIZE: usize = 4096;
enum Handle {
Audio { buffer: VecDeque<(i16, i16)> },
// TODO: move volume to audiohw:?
// TODO: Use SYS_CALL to handle this better?
Volume,
SchemeRoot,
}
pub struct AudioScheme {
handles: HandleMap<Handle>,
volume: i32,
}
impl AudioScheme {
pub fn new() -> Self {
AudioScheme {
handles: HandleMap::new(),
volume: 50,
}
}
pub fn buffer(&mut self) -> [(i16, i16); HW_BUFFER_SIZE] {
let mut mix_buffer = [(0i16, 0i16); HW_BUFFER_SIZE];
// Multiply each sample by the cube of volume divided by 100
// This mimics natural perception of loudness
let volume_factor = ((self.volume as f32) / 100.0).powi(3);
for (_id, handle) in self.handles.iter_mut() {
match handle {
Handle::Audio { ref mut buffer } => {
let mut i = 0;
while i < mix_buffer.len() {
if let Some(sample) = buffer.pop_front() {
let left = (sample.0 as f32 * volume_factor) as i16;
let right = (sample.1 as f32 * volume_factor) as i16;
mix_buffer[i].0 = mix_buffer[i].0.saturating_add(left);
mix_buffer[i].1 = mix_buffer[i].1.saturating_add(right);
} else {
break;
}
i += 1;
}
}
_ => (),
}
}
mix_buffer
}
}
impl SchemeSync for AudioScheme {
fn scheme_root(&mut self) -> Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
return Err(Error::new(EACCES));
}
let (handle, flags) = match path.trim_matches('/') {
"" => (
Handle::Audio {
buffer: VecDeque::new(),
},
NewFdFlags::empty(),
),
"volume" => (Handle::Volume, NewFdFlags::POSITIONED),
_ => return Err(Error::new(ENOENT)),
};
let id = self.handles.insert(handle);
Ok(OpenResult::ThisScheme { number: id, flags })
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
off: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
//TODO: check flags for readable
match self.handles.get_mut(id)? {
Handle::Audio { buffer: _ } => {
//TODO: audio input?
Err(Error::new(EBADF))
}
Handle::Volume => {
let Ok(off) = usize::try_from(off) else {
return Ok(0);
};
//TODO: should we allocate every time?
let bytes = format!("{}", self.volume).into_bytes();
let src = bytes.get(off..).unwrap_or(&[]);
let len = src.len().min(buf.len());
buf[..len].copy_from_slice(&src[..len]);
Ok(len)
}
Handle::SchemeRoot => Err(Error::new(EBADF)),
}
}
fn write(
&mut self,
id: usize,
buf: &[u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
//TODO: check flags for writable
match self.handles.get_mut(id)? {
Handle::Audio { ref mut buffer } => {
if buffer.len() >= HANDLE_BUFFER_SIZE {
Err(Error::new(EWOULDBLOCK))
} else {
let mut i = 0;
while i + 4 <= buf.len() {
buffer.push_back((
(buf[i] as i16) | ((buf[i + 1] as i16) << 8),
(buf[i + 2] as i16) | ((buf[i + 3] as i16) << 8),
));
i += 4;
}
Ok(i)
}
}
Handle::Volume => {
//TODO: support other offsets?
if offset == 0 {
let value = str::from_utf8(buf)
.map_err(|_| Error::new(EINVAL))?
.trim()
.parse::<i32>()
.map_err(|_| Error::new(EINVAL))?;
if value >= 0 && value <= 100 {
self.volume = value;
Ok(buf.len())
} else {
Err(Error::new(EINVAL))
}
} else {
// EOF
Ok(0)
}
}
Handle::SchemeRoot => Err(Error::new(EBADF)),
}
}
}
-3
View File
@@ -1,3 +0,0 @@
[unstable]
build-std = ["core", "alloc", "compiler_builtins"]
build-std-features = ["compiler-builtins-mem"]
-239
View File
@@ -1,239 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "bootstrap"
version = "0.0.0"
dependencies = [
"arrayvec",
"hashbrown",
"libredox",
"linked_list_allocator",
"log",
"plain",
"redox-initfs",
"redox-path 0.3.1",
"redox-rt",
"redox-scheme",
"redox_syscall",
"slab",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "generic-rt"
version = "0.1.0"
[[package]]
name = "goblin"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8"
dependencies = [
"log",
"plain",
"scroll",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "ioslice"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e571352c8a3b89074d12e3ee5173ffe162159105352aaaf1fc5764da747e31b"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.18+rb0.3.1"
dependencies = [
"bitflags",
"libc",
"plain",
"redox_syscall",
]
[[package]]
name = "linked_list_allocator"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897"
dependencies = [
"spinning_top",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox-initfs"
version = "0.2.0"
dependencies = [
"plain",
]
[[package]]
name = "redox-path"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-path"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ec1b67c01c63545205ea6d218b336751399f0d8974c1a635c28604bedb81bc"
[[package]]
name = "redox-rt"
version = "0.1.0"
dependencies = [
"bitflags",
"generic-rt",
"goblin",
"ioslice",
"libredox",
"plain",
"redox-path 0.4.0",
"redox_syscall",
]
[[package]]
name = "redox-scheme"
version = "0.11.2+rb0.3.1"
dependencies = [
"libredox",
"redox_syscall",
]
[[package]]
name = "redox_syscall"
version = "0.9.0+rb0.3.1"
dependencies = [
"bitflags",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scroll"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add"
dependencies = [
"scroll_derive",
]
[[package]]
name = "scroll_derive"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "spinning_top"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
dependencies = [
"lock_api",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
-46
View File
@@ -1,46 +0,0 @@
[package]
name = "bootstrap"
description = "Userspace bootstrapper"
version = "0.0.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2024"
license = "MIT"
[workspace.dependencies]
libredox = { path = "../../libredox", default-features = false, features = ["base", "protocol", "redox_syscall"] }
redox_syscall = { path = "../../syscall" }
redox-scheme = { path = "../../redox-scheme", default-features = false }
[workspace]
[dependencies]
hashbrown = { version = "0.15", default-features = false, features = [
"inline-more",
"default-hasher",
] }
linked_list_allocator = "0.10"
libredox = { workspace = true }
log = { version = "0.4", default-features = false }
plain = "0.2"
redox-initfs = { path = "../initfs", default-features = false }
redox_syscall = { workspace = true }
redox-scheme = { workspace = true }
redox-path = "0.3.1"
slab = { version = "0.4.9", default-features = false }
arrayvec = { version = "0.7.6", default-features = false }
[target.'cfg(target_os = "redox")'.dependencies]
redox-rt = { path = "../../relibc/redox-rt", default-features = false }
[profile.release]
panic = "abort"
lto = "fat"
opt-level = "s"
[profile.dev]
panic = "abort"
opt-level = "s"
[patch.crates-io]
redox_syscall = { path = "../../syscall" }
libredox = { path = "../../libredox" }
-14
View File
@@ -1,14 +0,0 @@
use std::env;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if arch == "x86" {
arch = "i586".to_owned();
}
println!("cargo::rustc-link-arg=-z");
println!("cargo::rustc-link-arg=max-page-size=4096");
println!("cargo::rustc-link-arg=-T");
println!("cargo::rustc-link-arg={manifest_dir}/src/{arch}.ld");
}
-55
View File
@@ -1,55 +0,0 @@
ENTRY(_start)
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-53
View File
@@ -1,53 +0,0 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
// Setup a stack.
ldr x8, ={number}
ldr x0, ={fd}
ldr x1, ={map} // pointer to Map struct
ldr x2, ={map_size} // size of Map struct
svc 0
// Failure if return value is zero
cbz x0, 1f
// Failure if return value is negative
tbnz x0, 63, 1f
// Set up stack frame
mov sp, x0
add sp, sp, #{stack_size}
mov fp, sp
// Stack has the same alignment as `size`.
bl start
// `start` must never return.
// failure, emit undefined instruction
1:
udf #0
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
-365
View File
@@ -1,365 +0,0 @@
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::str::FromStr;
use hashbrown::HashMap;
use redox_scheme::Socket;
use libredox::protocol::O_CLOEXEC;
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::{O_DIRECTORY, O_RDONLY, O_STAT};
use syscall::CallFlags;
use syscall::{Error, EINTR};
use redox_rt::proc::*;
use crate::KernelSchemeMap;
struct Logger;
impl log::Log for Logger {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &log::Record) {
let file = record.file().unwrap_or("");
let line = record.line().unwrap_or(0);
let level = record.level();
let msg = record.args();
let _ = syscall::write(
1,
alloc::format!("[{file}:{line} {level}] {msg}\n").as_bytes(),
);
}
fn flush(&self) {}
}
const KERNEL_METADATA_BASE: usize = crate::arch::USERMODE_END - syscall::KERNEL_METADATA_SIZE;
pub fn main() -> ! {
let mut cursor = KERNEL_METADATA_BASE;
let kernel_scheme_infos = unsafe {
let base_ptr = cursor as *const u8;
let infos_len = *(base_ptr as *const usize);
let infos_ptr = base_ptr.add(core::mem::size_of::<usize>()) as *const KernelSchemeInfo;
let slice = core::slice::from_raw_parts(infos_ptr, infos_len);
cursor += core::mem::size_of::<usize>() // kernel scheme number size
+ infos_len // kernel scheme number
* core::mem::size_of::<KernelSchemeInfo>();
slice
};
let scheme_creation_cap = unsafe {
let base_ptr = cursor as *const u8;
FdGuard::new(*(base_ptr as *const usize))
};
let cur_context_idx = scheme_creation_cap.as_raw_fd() + 1;
let mut kernel_schemes = KernelSchemeMap::new(kernel_scheme_infos);
let auth = kernel_schemes
.0
.remove(&GlobalSchemes::Proc)
.expect("failed to get proc fd");
let this_thr_fd = syscall::dup_into(auth.as_raw_fd(), cur_context_idx, b"cur-context")
.map(FdGuard::new)
.expect("failed to open open_via_dup")
.to_upper()
.unwrap();
let this_thr_fd = unsafe { redox_rt::initialize_freestanding(this_thr_fd) };
let mut env_bytes = [0_u8; 4096];
let mut envs = {
let fd = FdGuard::new(
redox_rt::sys::openat(
kernel_schemes
.get(GlobalSchemes::Sys)
.expect("failed to get sys fd")
.as_raw_fd(),
"env",
O_RDONLY | O_CLOEXEC,
0,
)
.expect("bootstrap: failed to open env"),
);
let bytes_read = fd
.read(&mut env_bytes)
.expect("bootstrap: failed to read env");
if bytes_read >= env_bytes.len() {
// TODO: Handle this, we can allocate as much as we want in theory.
panic!("env is too large");
}
let env_bytes = &mut env_bytes[..bytes_read];
env_bytes
.split(|&c| c == b'\n')
.filter(|var| !var.is_empty())
.filter(|var| !var.starts_with(b"INITFS_"))
.collect::<Vec<_>>()
};
envs.push(b"RUST_BACKTRACE=1");
//envs.push(b"LD_DEBUG=all");
envs.push(b"LD_LIBRARY_PATH=/scheme/initfs/lib");
log::set_max_level(log::LevelFilter::Warn);
if let Some(log_env) = envs
.iter()
.find_map(|var| var.strip_prefix(b"BOOTSTRAP_LOG_LEVEL="))
{
if let Ok(Ok(log_level)) = str::from_utf8(&log_env).map(|s| log::LevelFilter::from_str(s)) {
log::set_max_level(log_level);
}
}
let _ = log::set_logger(&Logger);
unsafe extern "C" {
// The linker script will define this as the location of the initfs header.
static __initfs_header: u8;
// The linker script will define this as the end of the executable (excluding initfs).
static __bss_end: u8;
}
let initfs_start = core::ptr::addr_of!(__initfs_header);
let initfs_length = unsafe {
(*(core::ptr::addr_of!(__initfs_header) as *const redox_initfs::types::Header))
.initfs_size
.get() as usize
};
let (scheme_creation_cap, auth, kernel_schemes, initfs_fd) = spawn(
"initfs daemon",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
false,
|write_fd, socket, _, _| unsafe {
crate::initfs::run(
core::slice::from_raw_parts(initfs_start, initfs_length),
write_fd,
socket,
);
},
);
// Unmap initfs data as only the initfs scheme implementation needs it.
unsafe {
let executable_end = core::ptr::addr_of!(__bss_end)
.add(core::ptr::addr_of!(__bss_end).align_offset(syscall::PAGE_SIZE));
syscall::funmap(
executable_end as usize,
initfs_length.next_multiple_of(syscall::PAGE_SIZE)
- (executable_end.offset_from(initfs_start) as usize),
)
.unwrap();
}
let (scheme_creation_cap, auth, kernel_schemes, proc_fd) = spawn(
"process manager",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
true,
|write_fd, socket, auth, mut kernel_schemes| {
let event = kernel_schemes
.0
.remove(&GlobalSchemes::Event)
.expect("failed to get event fd");
drop(kernel_schemes);
crate::procmgr::run(write_fd, socket, auth, event)
},
);
let scheme_creation_cap_dup = scheme_creation_cap
.dup(b"")
.expect("failed to dup scheme creation cap");
let (_, _, _, initns_fd) = spawn(
"init namespace manager",
auth,
&this_thr_fd,
scheme_creation_cap,
kernel_schemes,
false,
|write_fd, socket, _, kernel_schemes| {
let mut schemes = HashMap::default();
for (scheme, fd) in kernel_schemes.0.into_iter() {
schemes.insert(scheme.as_str().to_string(), Arc::new(fd));
}
schemes.insert(
"proc".to_string(),
// A bit dirty, but necessary as the parent process still needs access to it. Rust
// doesn't know that the fd got cloned by fork.
Arc::new(FdGuard::new(proc_fd.as_raw_fd())),
);
schemes.insert("initfs".to_string(), Arc::new(initfs_fd));
crate::initnsmgr::run(write_fd, socket, schemes, scheme_creation_cap_dup)
},
);
let (init_proc_fd, init_thr_fd) = unsafe { make_init(proc_fd.take()) };
// from this point, this_thr_fd is no longer valid
const CWD: &[u8] = b"/scheme/initfs";
let initfs_root_fd = initns_fd
.openat_into_upper("/scheme/initfs", O_DIRECTORY, 0)
.expect("failed to open initfs root fd");
let cwd_fd = initfs_root_fd
.openat_into_upper("", O_STAT, 0)
.expect("failed to open cwd fd");
let filetable_binary_fd = init_thr_fd
.dup_into_upper(b"filetable-binary")
.expect("faild to create filetable-binary fd");
let extrainfo = ExtraInfo {
cwd: Some(CWD),
sigprocmask: 0,
sigignmask: 0,
umask: redox_rt::sys::get_umask(),
thr_fd: init_thr_fd.as_raw_fd(),
proc_fd: init_proc_fd.as_raw_fd(),
ns_fd: Some(initns_fd.take()),
cwd_fd: Some(cwd_fd.as_raw_fd()),
filetable_fd: Some(filetable_binary_fd.as_raw_fd()),
same_process: true,
};
let exe_path = "/scheme/initfs/bin/init";
let exe_reference = "bin/init";
let image_file = initfs_root_fd
.openat_into_upper(exe_reference, O_RDONLY | O_CLOEXEC, 0)
.expect("failed to open init");
let FexecResult::Interp {
path: interp_path,
interp_override,
} = fexec_impl(
image_file,
init_thr_fd,
init_proc_fd,
exe_path.as_bytes(),
&[exe_path.as_bytes()],
&envs,
&extrainfo,
None,
)
.unwrap_or_else(|e| {
panic!("fexec_impl error: {:?}", e);
})
.unwrap_or_else(|| panic!("fexec_impl returned None (no interpreter)"))
else {
panic!("fexec_impl returned unexpected variant");
};
// According to elf(5), PT_INTERP requires that the interpreter path be
// null-terminated. Violating this should therefore give the "format error" ENOEXEC.
let interp_cstr = CStr::from_bytes_with_nul(&interp_path).expect("interpreter not valid C str");
let interp_path = interp_cstr.to_str().expect("interpreter not UTF-8");
let redox_path = redox_path::RedoxPath::from_absolute(interp_path)
.expect("interpreter path is not a Scheme-rooted path");
let (_, interp_reference) = redox_path
.as_parts()
.expect("redox_path is not scheme root path");
let interp_file = initfs_root_fd
.openat_into_upper(interp_reference.as_ref(), O_RDONLY | O_CLOEXEC, 0)
.expect("failed to open dynamic linker");
fexec_impl(
interp_file,
init_thr_fd,
init_proc_fd,
exe_path.as_bytes(),
&[exe_path.as_bytes()],
&envs,
&extrainfo,
Some(interp_override),
)
.expect("failed to execute init");
unreachable!()
}
pub(crate) fn spawn(
name: &str,
auth: FdGuard,
this_thr_fd: &FdGuardUpper,
scheme_creation_cap: FdGuard,
kernel_schemes: KernelSchemeMap,
nonblock: bool,
inner: impl FnOnce(FdGuard, Socket, FdGuard, KernelSchemeMap) -> !,
) -> (FdGuard, FdGuard, KernelSchemeMap, FdGuard) {
let read = FdGuard::new(
redox_rt::sys::openat(
kernel_schemes
.get(GlobalSchemes::Pipe)
.expect("failed to get pipe fd")
.as_raw_fd(),
"",
O_CLOEXEC,
0,
)
.expect("failed to open sync read pipe"),
);
// The write pipe will not inherit O_CLOEXEC, but is closed by the daemon later.
let write = FdGuard::new(
redox_rt::sys::dup(read.as_raw_fd(), b"write").expect("failed to open sync write pipe"),
);
match fork_impl(&ForkArgs::Init {
this_thr_fd,
auth: &auth,
}) {
Err(err) => {
panic!("Failed to fork in order to start {name}: {err}");
}
// Continue serving the scheme as the child.
Ok(0) => {
drop(read);
let socket = Socket::create_inner(scheme_creation_cap.as_raw_fd(), nonblock)
.expect("failed to open proc scheme socket");
drop(scheme_creation_cap);
inner(write, socket, auth, kernel_schemes)
}
// Return in order to execute init, as the parent.
Ok(_) => {
drop(write);
let mut new_fd = usize::MAX;
let fd_bytes = unsafe {
core::slice::from_raw_parts_mut(
core::slice::from_mut(&mut new_fd).as_mut_ptr() as *mut u8,
core::mem::size_of::<usize>(),
)
};
loop {
match redox_rt::sys::sys_call_ro(
read.as_raw_fd(),
fd_bytes,
CallFlags::FD | CallFlags::FD_UPPER,
&[],
) {
Err(Error { errno: EINTR }) => continue,
_ => break,
}
}
(
scheme_creation_cap,
auth,
kernel_schemes,
FdGuard::new(new_fd),
)
}
}
}
-55
View File
@@ -1,55 +0,0 @@
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-55
View File
@@ -1,55 +0,0 @@
ENTRY(_start)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-49
View File
@@ -1,49 +0,0 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 0x8000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
mov eax, {number}
mov ebx, {fd}
mov ecx, offset {map} # pointer to Map struct
mov edx, {map_size} # size of Map struct
int 0x80
# Test for success (nonzero value).
cmp eax, 0
jg 1f
# (failure)
ud2
1:
# Subtract 16 since all instructions seem to hate non-canonical ESP values :)
lea esp, [eax+{stack_size}-16]
mov ebp, esp
# Stack has the same alignment as `size`.
call start
# `start` must never return.
ud2
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
-543
View File
@@ -1,543 +0,0 @@
use core::convert::TryFrom;
#[allow(deprecated)]
use core::hash::{BuildHasherDefault, SipHasher};
use core::str;
use alloc::string::String;
use hashbrown::HashMap;
use redox_initfs::{InitFs, Inode, InodeDir, InodeKind, InodeStruct, types::Timespec};
use redox_rt::proc::FdGuard;
use redox_scheme::{
CallerCtx, OpenResult, RequestKind,
scheme::{SchemeState, SchemeSync},
};
use redox_scheme::{SignalBehavior, Socket};
use syscall::PAGE_SIZE;
use syscall::data::Stat;
use syscall::dirent::DirEntry;
use syscall::dirent::DirentBuf;
use syscall::dirent::DirentKind;
use syscall::error::*;
use syscall::flag::*;
use syscall::schemev2::NewFdFlags;
enum Handle {
Node(Node),
SchemeRoot,
}
impl Handle {
fn as_node(&self) -> Result<&Node> {
match self {
Handle::Node(n) => Ok(n),
_ => Err(Error::new(EBADF)),
}
}
fn as_node_mut(&mut self) -> Result<&mut Node> {
match self {
Handle::Node(n) => Ok(n),
_ => Err(Error::new(EBADF)),
}
}
}
struct Node {
inode: Inode,
// TODO: Any better way to implement fpath? Or maybe work around it, e.g. by giving paths such
// as `initfs:__inodes__/<inode>`?
filename: String,
}
pub struct InitFsScheme {
#[allow(deprecated)]
handles: HashMap<usize, Handle, BuildHasherDefault<SipHasher>>,
next_id: usize,
fs: InitFs<'static>,
}
impl InitFsScheme {
pub fn new(bytes: &'static [u8]) -> Self {
Self {
handles: HashMap::default(),
next_id: 0,
fs: InitFs::new(bytes, Some(PAGE_SIZE.try_into().unwrap()))
.expect("failed to parse initfs"),
}
}
fn get_inode(fs: &InitFs<'static>, inode: Inode) -> Result<InodeStruct<'static>> {
fs.get_inode(inode).ok_or_else(|| Error::new(EIO))
}
fn next_id(&mut self) -> usize {
assert_ne!(self.next_id, usize::MAX, "usize overflow in initfs scheme");
self.next_id += 1;
self.next_id
}
}
struct Iter {
dir: InodeDir<'static>,
idx: u32,
}
impl Iterator for Iter {
type Item = Result<redox_initfs::Entry<'static>>;
fn next(&mut self) -> Option<Self::Item> {
let entry = self.dir.get_entry(self.idx).map_err(|_| Error::new(EIO));
self.idx += 1;
entry.transpose()
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.dir.entry_count().ok() {
Some(size) => {
let size =
usize::try_from(size).expect("expected u32 to be convertible into usize");
(size, Some(size))
}
None => (0, None),
}
}
}
fn inode_len(inode: InodeStruct<'static>) -> Result<usize> {
Ok(match inode.kind() {
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?.len(),
InodeKind::Dir(dir) => (Iter { dir, idx: 0 }).fold(0, |len, entry| {
len + entry
.and_then(|entry| entry.name().map_err(|_| Error::new(EIO)))
.map_or(0, |name| name.len() + 1)
}),
InodeKind::Link(link) => link.data().map_err(|_| Error::new(EIO))?.len(),
InodeKind::Unknown => return Err(Error::new(EIO)),
})
}
impl SchemeSync for InitFsScheme {
fn openat(
&mut self,
dirfd: usize,
path: &str,
flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if !matches!(
self.handles.get(&dirfd).ok_or(Error::new(EBADF))?,
Handle::SchemeRoot | Handle::Node(_)
) {
return Err(Error::new(EACCES));
}
let mut components = path
// trim leading and trailing slash
.trim_matches('/')
// divide into components
.split('/')
// filter out double slashes (e.g. /usr//bin/...)
.filter(|c| !c.is_empty());
let mut current_inode = InitFs::ROOT_INODE;
while let Some(component) = components.next() {
match component {
"." => continue,
".." => {
let _ = components.next_back();
continue;
}
_ => (),
}
let current_inode_struct = Self::get_inode(&self.fs, current_inode)?;
let dir = match current_inode_struct.kind() {
InodeKind::Dir(dir) => dir,
// TODO: Support symlinks in other position than xopen target
InodeKind::Link(_) => {
return Err(Error::new(EOPNOTSUPP));
}
// If we still have more components in the path, and the file tree for that
// particular branch is not all directories except the last, then that file cannot
// exist.
InodeKind::File(_) | InodeKind::Unknown => return Err(Error::new(ENOENT)),
};
let mut entries = Iter { dir, idx: 0 };
current_inode = loop {
let entry_res = match entries.next() {
Some(e) => e,
None => return Err(Error::new(ENOENT)),
};
let entry = entry_res?;
let name = entry.name().map_err(|_| Error::new(EIO))?;
if name == component.as_bytes() {
break entry.inode();
}
};
}
// xopen target is link -- return EXDEV so that the file is opened as a link.
// TODO: Maybe follow initfs-local symlinks here? Would be faster
let is_link = matches!(
Self::get_inode(&self.fs, current_inode)?.kind(),
InodeKind::Link(_)
);
let o_stat_nofollow = flags & O_STAT != 0 && flags & O_NOFOLLOW != 0;
let o_symlink = flags & O_SYMLINK != 0;
if is_link && !o_stat_nofollow && !o_symlink {
return Err(Error::new(EXDEV));
}
let id = self.next_id();
let old = self.handles.insert(
id,
Handle::Node(Node {
inode: current_inode,
filename: path.into(),
}),
);
assert!(old.is_none());
Ok(OpenResult::ThisScheme {
number: id,
flags: NewFdFlags::POSITIONED,
})
}
fn read(
&mut self,
id: usize,
buffer: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let Ok(offset) = usize::try_from(offset) else {
return Ok(0);
};
let handle = self
.handles
.get_mut(&id)
.ok_or(Error::new(EBADF))?
.as_node_mut()?;
match Self::get_inode(&self.fs, handle.inode)?.kind() {
InodeKind::File(file) => {
let data = file.data().map_err(|_| Error::new(EIO))?;
let src_buf = &data[core::cmp::min(offset, data.len())..];
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
Ok(to_copy)
}
InodeKind::Dir(_) => Err(Error::new(EISDIR)),
InodeKind::Link(link) => {
let link_data = link.data().map_err(|_| Error::new(EIO))?;
let src_buf = &link_data[core::cmp::min(offset, link_data.len())..];
let to_copy = core::cmp::min(src_buf.len(), buffer.len());
buffer[..to_copy].copy_from_slice(&src_buf[..to_copy]);
Ok(to_copy)
}
InodeKind::Unknown => Err(Error::new(EIO)),
}
}
fn getdents<'buf>(
&mut self,
id: usize,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
let Ok(offset) = u32::try_from(opaque_offset) else {
return Ok(buf);
};
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
let InodeKind::Dir(dir) = Self::get_inode(&self.fs, handle.inode)?.kind() else {
return Err(Error::new(ENOTDIR));
};
let iter = Iter { dir, idx: offset };
for (index, entry) in iter.enumerate() {
let entry = entry?;
buf.entry(DirEntry {
// TODO: Add getter
//inode: entry.inode(),
inode: 0,
name: entry
.name()
.ok()
.and_then(|utf8| core::str::from_utf8(utf8).ok())
.ok_or(Error::new(EIO))?,
next_opaque_id: index as u64 + 1,
kind: DirentKind::Unspecified,
})?;
}
Ok(buf)
}
fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result<u64> {
let handle = self
.handles
.get_mut(&id)
.ok_or(Error::new(EBADF))?
.as_node_mut()?;
Ok(inode_len(Self::get_inode(&self.fs, handle.inode)?)? as u64)
}
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
let _handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
Ok(0)
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
// TODO: Copy scheme part in kernel
let scheme_path = b"/scheme/initfs";
let scheme_bytes = core::cmp::min(scheme_path.len(), buf.len());
buf[..scheme_bytes].copy_from_slice(&scheme_path[..scheme_bytes]);
let source = handle.filename.as_bytes();
let path_bytes = core::cmp::min(buf.len() - scheme_bytes, source.len());
buf[scheme_bytes..scheme_bytes + path_bytes].copy_from_slice(&source[..path_bytes]);
Ok(scheme_bytes + path_bytes)
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?.as_node()?;
let Timespec { sec, nsec } = self.fs.image_creation_time();
let inode = Self::get_inode(&self.fs, handle.inode)?;
stat.st_ino = inode.id();
stat.st_mode = inode.mode()
| match inode.kind() {
InodeKind::Dir(_) => MODE_DIR,
InodeKind::File(_) => MODE_FILE,
InodeKind::Link(_) => MODE_SYMLINK,
_ => 0,
};
stat.st_uid = 0;
stat.st_gid = 0;
stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX);
stat.st_ctime = sec.get();
stat.st_ctime_nsec = nsec.get();
stat.st_mtime = sec.get();
stat.st_mtime_nsec = nsec.get();
Ok(())
}
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
if !self.handles.contains_key(&id) {
return Err(Error::new(EBADF));
}
Ok(())
}
fn mmap_prep(
&mut self,
id: usize,
offset: u64,
size: usize,
flags: MapFlags,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
let Handle::Node(node) = handle else {
return Err(Error::new(EBADF));
};
let data = match Self::get_inode(&self.fs, node.inode)?.kind() {
InodeKind::File(file) => file.data().map_err(|_| Error::new(EIO))?,
InodeKind::Dir(_) => return Err(Error::new(EISDIR)),
InodeKind::Link(_) => return Err(Error::new(ELOOP)),
InodeKind::Unknown => return Err(Error::new(EIO)),
};
if flags.contains(MapFlags::PROT_WRITE) {
return Err(Error::new(EPERM));
}
let Some(last_addr) = offset.checked_add(size as u64) else {
return Err(Error::new(EINVAL));
};
if last_addr > data.len().next_multiple_of(PAGE_SIZE) as u64 {
return Err(Error::new(EINVAL));
}
Ok(data.as_ptr() as usize)
}
}
pub fn run(bytes: &'static [u8], sync_pipe: FdGuard, socket: Socket) -> ! {
log::info!("bootstrap: starting initfs scheme");
let mut state = SchemeState::new();
let mut scheme = InitFsScheme::new(bytes);
// send open-capability to bootstrap
let new_id = scheme.next_id();
scheme.handles.insert(new_id, Handle::SchemeRoot);
let cap_fd = socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("failed to issue initfs root fd");
let _ = syscall::call_rw(
sync_pipe.as_raw_fd(),
&mut cap_fd.to_ne_bytes(),
CallFlags::FD,
&[],
);
drop(sync_pipe);
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("bootstrap: failed to read scheme request from kernel")
else {
break;
};
match req.kind() {
RequestKind::Call(req) => {
let resp = req.handle_sync(&mut scheme, &mut state);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
RequestKind::OnClose { id } => {
scheme.handles.remove(&id);
}
_ => (),
}
}
unreachable!()
}
// TODO: Restructure bootstrap so it calls into relibc, or a split-off derivative without the C
// parts, such as "redox-rt".
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_read_v1(fd: usize, ptr: *mut u8, len: usize) -> isize {
Error::mux(syscall::read(fd, unsafe {
core::slice::from_raw_parts_mut(ptr, len)
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_write_v1(fd: usize, ptr: *const u8, len: usize) -> isize {
Error::mux(syscall::write(fd, unsafe {
core::slice::from_raw_parts(ptr, len)
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_openat_v1(
fd: usize,
buf: *const u8,
path_len: usize,
flags: u32,
fcntl_flags: u32,
) -> isize {
let path = unsafe { core::slice::from_raw_parts(buf, path_len) };
let path_str = match core::str::from_utf8(path) {
Ok(s) => s,
Err(_) => return -(syscall::EINVAL as isize),
};
Error::mux(syscall::openat(fd, path_str, flags as usize, fcntl_flags as usize)) as isize
}
#[unsafe(no_mangle)]
pub unsafe fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> isize {
Error::mux(syscall::dup(fd, unsafe {
core::slice::from_raw_parts(buf, len)
})) as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn redox_close_v1(fd: usize) -> isize {
Error::mux(syscall::close(fd)) as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn redox_fcntl_v0(fd: usize, cmd: usize, arg: usize) -> isize {
Error::mux(syscall::fcntl(fd, cmd, arg)) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_strerror_v1(
dst: *mut u8,
dst_len: *mut usize,
error: u32,
) -> isize {
let msg = match error {
x if x == syscall::EPERM as u32 => "Operation not permitted",
x if x == syscall::ENOENT as u32 => "No such file or directory",
x if x == syscall::EINTR as u32 => "Interrupted system call",
x if x == syscall::EIO as u32 => "I/O error",
x if x == syscall::EBADF as u32 => "Bad file descriptor",
x if x == syscall::EAGAIN as u32 => "Resource temporarily unavailable",
x if x == syscall::ENOMEM as u32 => "Cannot allocate memory",
x if x == syscall::EACCES as u32 => "Permission denied",
x if x == syscall::EFAULT as u32 => "Bad address",
x if x == syscall::EBUSY as u32 => "Device or resource busy",
x if x == syscall::EEXIST as u32 => "File exists",
x if x == syscall::ENOTDIR as u32 => "Not a directory",
x if x == syscall::EISDIR as u32 => "Is a directory",
x if x == syscall::EINVAL as u32 => "Invalid argument",
x if x == syscall::ENOSYS as u32 => "Function not implemented",
x if x == syscall::ENOTEMPTY as u32 => "Directory not empty",
_ => "Unknown error",
};
let msg_bytes = msg.as_bytes();
unsafe {
let avail = *dst_len;
let copy_len = avail.min(msg_bytes.len());
core::ptr::copy_nonoverlapping(msg_bytes.as_ptr(), dst, copy_len);
*dst_len = copy_len;
copy_len as isize
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_sys_call_v0(
fd: usize,
payload: *mut u8,
payload_len: usize,
flags: usize,
metadata: *const u64,
metadata_len: usize,
) -> isize {
let flags = CallFlags::from_bits_retain(flags);
let metadata = unsafe { core::slice::from_raw_parts(metadata, metadata_len) };
let result = if flags.contains(CallFlags::READ) {
let payload = unsafe { core::slice::from_raw_parts_mut(payload, payload_len) };
if flags.contains(CallFlags::WRITE) {
syscall::call_rw(fd, payload, flags, metadata)
} else {
syscall::call_ro(fd, payload, flags, metadata)
}
} else {
let payload = unsafe { core::slice::from_raw_parts(payload, payload_len) };
syscall::call_wo(fd, payload, flags, metadata)
};
Error::mux(result) as isize
}
-570
View File
@@ -1,570 +0,0 @@
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::mem;
use hashbrown::HashMap;
use libredox::protocol::{NsDup, NsPermissions};
use log::{error, warn};
use redox_path::RedoxPath;
use redox_path::RedoxScheme;
use redox_rt::proc::FdGuard;
// Namespace state is shared between the dispatcher and (from the worker-offload
// step) the open workers, so it uses redox_rt's Send/Sync Mutex instead of the
// single-threaded Rc<RefCell>. NOTE: this Mutex is a spinlock -- never hold it
// across a blocking syscall; resolve the cap_fd under the lock, clone the Arc,
// and do the blocking openat with the lock released. See
// local/docs/INITNSMGR-CONCURRENCY-DESIGN.md.
use redox_rt::sync::Mutex;
use redox_scheme::{
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
scheme::{SchemeState, SchemeSync},
};
use syscall::Stat;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::{CallFlags, FobtainFdFlags, error::*, schemev2::NewFdFlags};
#[derive(Debug, Clone)]
struct Namespace {
schemes: HashMap<String, Arc<FdGuard>>,
}
impl Namespace {
fn fork(&self, buf: &[u8]) -> Result<Self> {
let mut schemes = HashMap::new();
let mut cursor = 0;
while cursor < buf.len() {
let len = read_num::<usize>(&buf[cursor..])?;
cursor += mem::size_of::<usize>();
let name = String::from_utf8(Vec::from(&buf[cursor..cursor + len]))
.map_err(|_| Error::new(EINVAL))?;
cursor += len;
if name.ends_with('*') {
let prefix = &name[..name.len() - 1];
for (registered_name, fd) in &self.schemes {
if registered_name.starts_with(prefix) {
schemes.insert(registered_name.clone(), fd.clone());
}
}
} else {
let Some(fd) = self.schemes.get(&name) else {
warn!("Scheme {} not found in namespace", name);
continue;
};
schemes.insert(name, fd.clone());
}
}
Ok(Self { schemes })
}
fn get_scheme_fd(&self, scheme: &str) -> Option<&Arc<FdGuard>> {
self.schemes.get(scheme)
}
fn remove_scheme(&mut self, scheme: &str) -> Option<()> {
self.schemes.remove(scheme).map(|_| ())
}
}
// No `Debug` derive: redox_rt's Mutex wraps an UnsafeCell and is not Debug.
// `Clone` clones the Arc (a refcount bump), not the namespace.
#[derive(Clone)]
struct NamespaceAccess {
namespace: Arc<Mutex<Namespace>>,
permission: NsPermissions,
}
impl NamespaceAccess {
fn has_permission(&self, permission: NsPermissions) -> bool {
self.permission.contains(permission)
}
}
#[derive(Clone)]
struct SchemeRegister {
target_namespace: Arc<Mutex<Namespace>>,
scheme_name: String,
}
impl SchemeRegister {
fn register(&self, fd: FdGuard) -> Result<()> {
let mut ns = self.target_namespace.lock();
if ns.schemes.contains_key(&self.scheme_name) {
return Err(Error::new(EEXIST));
}
ns.schemes.insert(self.scheme_name.clone(), Arc::new(fd));
Ok(())
}
}
#[derive(Clone)]
enum Handle {
Access(NamespaceAccess),
Register(SchemeRegister),
List(NamespaceAccess),
}
pub struct NamespaceScheme<'sock> {
socket: &'sock Socket,
handles: HashMap<usize, Handle>,
root_namespace: Namespace,
next_id: usize,
scheme_creation_cap: FdGuard,
}
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
impl<'sock> NamespaceScheme<'sock> {
pub fn new(
socket: &'sock Socket,
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuard,
) -> Self {
Self {
socket,
handles: HashMap::new(),
root_namespace: Namespace { schemes },
next_id: 0,
scheme_creation_cap,
}
}
fn add_namespace(&mut self, id: usize, schemes: Namespace, permission: NsPermissions) {
let handle = Handle::Access(NamespaceAccess {
namespace: Arc::new(Mutex::new(schemes)),
permission,
});
self.handles.insert(id, handle);
}
fn get_ns_access(&self, id: usize) -> Option<&NamespaceAccess> {
let handle = self.handles.get(&id);
match handle {
Some(Handle::Access(access)) => Some(access),
_ => None,
}
}
fn open_namespace_resource(
&self,
ns_access: &NamespaceAccess,
reference: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
match reference {
"scheme-creation-cap" => {
if !ns_access.has_permission(NsPermissions::SCHEME_CREATE) {
error!("Permission denied to get scheme creation capability");
return Err(Error::new(EACCES));
}
Ok(libredox::call::dup(self.scheme_creation_cap.as_raw_fd(), &[])?)
}
_ => {
error!("Unknown special reference: {}", reference);
return Err(Error::new(EINVAL));
}
}
}
fn open_scheme_resource(
&self,
ns: &Namespace,
scheme: &str,
reference: &str,
flags: usize,
fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<usize> {
let Some(cap_fd) = ns.get_scheme_fd(scheme) else {
log::info!("Scheme {:?} not found in namespace", scheme);
return Err(Error::new(ENODEV));
};
let scheme_fd = syscall::openat(
cap_fd.as_raw_fd(),
reference,
flags,
fcntl_flags as usize,
)?;
Ok(scheme_fd)
}
fn fork_namespace(&mut self, namespace: Arc<Mutex<Namespace>>, names: &[u8]) -> Result<usize> {
let new_id = self.next_id;
let new_namespace = namespace.lock().fork(names).map_err(|e| {
error!("Failed to fork namespace {}: {}", new_id, e);
e
})?;
self.add_namespace(
new_id,
new_namespace,
NsPermissions::all().difference(HIGH_PERMISSIONS),
);
self.next_id += 1;
Ok(new_id)
}
fn shrink_permissions(
&mut self,
mut ns: NamespaceAccess,
permission: NsPermissions,
) -> Result<usize> {
ns.permission = ns.permission.intersection(permission);
let next_id = self.next_id;
self.handles.insert(next_id, Handle::Access(ns));
self.next_id += 1;
Ok(next_id)
}
}
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
fn openat(
&mut self,
fd: usize,
path: &str,
flags: usize,
fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
let ns_access = {
let handle = self.handles.get(&fd);
match handle {
Some(Handle::Access(access)) => Some(access),
_ => None,
}
}
.ok_or_else(|| {
error!("Namespace with ID {} not found", fd);
Error::new(ENOENT)
})?;
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
let res_fd = match scheme.as_ref() {
"namespace" => self.open_namespace_resource(
ns_access,
reference.as_ref(),
flags,
fcntl_flags,
ctx,
)?,
"" => {
if !ns_access.has_permission(NsPermissions::LIST) {
error!("Permission denied to list schemes in namespace {}", fd);
return Err(Error::new(EACCES));
}
let new_id = self.next_id;
self.next_id += 1;
self.handles.insert(new_id, Handle::List(ns_access.clone()));
return Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
});
}
// STEP 1 (mechanical): the lock is held across the blocking openat
// inside open_scheme_resource. That is safe only because this is
// still single-threaded. STEP 3 must resolve+clone the cap_fd under
// a short lock here and run the openat with the lock released, or
// the spinlock would burn a worker while a provider is slow.
_ => self.open_scheme_resource(
&ns_access.namespace.lock(),
scheme.as_ref(),
reference.as_ref(),
flags,
fcntl_flags,
ctx,
)?,
};
Ok(OpenResult::OtherScheme { fd: res_fd })
}
fn dup(&mut self, id: usize, buf: &[u8], _ctx: &CallerCtx) -> Result<OpenResult> {
let ns_access = self.get_ns_access(id).ok_or_else(|| {
error!("Namespace with ID {} not found", id);
Error::new(ENOENT)
})?;
let raw_kind = read_num::<usize>(buf)?;
let Some(kind) = NsDup::try_from_raw(raw_kind) else {
error!("Unknown dup kind: {}", raw_kind);
return Err(Error::new(EINVAL));
};
let payload = &buf[mem::size_of::<NsDup>()..];
let new_id = match kind {
NsDup::ForkNs => {
let ns = ns_access.namespace.clone();
let _ = ns_access;
self.fork_namespace(ns, payload)?
}
NsDup::ShrinkPermissions => self.shrink_permissions(
ns_access.clone(),
NsPermissions::from_bits_truncate(read_num::<usize>(payload)?),
)?,
NsDup::IssueRegister => {
let name = core::str::from_utf8(payload).map_err(|_| Error::new(EINVAL))?;
let scheme_name = RedoxScheme::new(name).ok_or_else(|| {
error!("Invalid scheme name: {}", name);
Error::new(EINVAL)
})?;
if !ns_access.has_permission(NsPermissions::INSERT) {
error!(
"Permission denied to issue register capability for namespace {}",
id
);
return Err(Error::new(EACCES));
}
let new_id = self.next_id;
let register_cap = Handle::Register(SchemeRegister {
target_namespace: ns_access.namespace.clone(),
scheme_name: scheme_name.as_ref().to_string(),
});
self.handles.insert(new_id, register_cap);
self.next_id += 1;
new_id
}
};
Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
})
}
fn unlinkat(&mut self, fd: usize, path: &str, flags: usize, ctx: &CallerCtx) -> Result<()> {
let ns_access = self.get_ns_access(fd).ok_or_else(|| {
error!("Namespace with ID {} not found", fd);
Error::new(ENOENT)
})?;
let mut ns = ns_access.namespace.lock();
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
let (scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
if reference.as_ref().is_empty() {
if !ns_access.has_permission(NsPermissions::DELETE) {
error!("Permission denied to remove scheme for namespace {}", fd);
return Err(Error::new(EACCES));
}
match ns.remove_scheme(scheme.as_ref()) {
Some(_) => return Ok(()),
None => {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
}
}
}
let Some(cap_fd) = ns.get_scheme_fd(scheme.as_ref()) else {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
};
syscall::unlinkat(cap_fd.as_raw_fd(), reference, flags)?;
Ok(())
}
fn on_close(&mut self, id: usize) {
self.handles.remove(&id);
}
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let namespace_id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
let handle = self.handles.get(&namespace_id).ok_or_else(|| {
error!("Namespace with ID {} not found", namespace_id);
Error::new(ENOENT)
})?;
let Handle::Register(register_cap) = handle else {
error!(
"Handle with ID {} is not a register capability",
namespace_id
);
return Err(Error::new(EACCES));
};
if num_fds == 0 {
return Ok(0);
}
if num_fds > 1 {
error!("Can only send one fd at a time");
return Err(Error::new(EINVAL));
}
let mut new_fd = usize::MAX;
if let Err(e) = sendfd_request.obtain_fd(
&self.socket,
FobtainFdFlags::UPPER_TBL,
core::slice::from_mut(&mut new_fd),
) {
error!("on_sendfd: obtain_fd failed with error: {:?}", e);
return Err(e);
}
register_cap.register(FdGuard::new(new_fd))?;
Ok(num_fds)
}
fn getdents<'buf>(
&mut self,
id: usize,
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
let Handle::List(ns_access) = self.handles.get(&id).ok_or(Error::new(EBADF))? else {
return Err(Error::new(ENOTDIR));
};
if !ns_access.has_permission(NsPermissions::LIST) {
return Err(Error::new(EACCES));
}
let ns = ns_access.namespace.lock();
let opaque_offset = opaque_offset as usize;
for (i, (name, _)) in ns.schemes.iter().enumerate().skip(opaque_offset) {
if name.is_empty() {
continue;
}
if let Err(err) = buf.entry(DirEntry {
kind: DirentKind::Unspecified,
name: &name.clone(),
inode: 0,
next_opaque_id: i as u64 + 1,
}) {
if err.errno == EINVAL && i > opaque_offset {
// POSIX allows partial result of getdents
break;
} else {
return Err(err);
}
}
}
Ok(buf)
}
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
let resource_stat = match self.handles.get(&id).ok_or(Error::new(EBADF))? {
Handle::List(_) => Stat {
st_mode: 0o444 | syscall::MODE_DIR,
st_uid: 0,
st_gid: 0,
st_size: 0,
..Default::default()
},
Handle::Access(_) | Handle::Register(_) => Stat {
st_mode: 0o666 | syscall::MODE_FILE,
st_uid: 0,
st_gid: 0,
st_size: 0,
..Default::default()
},
};
*stat = resource_stat;
Ok(())
}
}
trait NumFromBytes: Sized + Debug {
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error>;
}
macro_rules! num_from_bytes_impl {
($($t:ty),*) => {
$(
impl NumFromBytes for $t {
fn from_le_bytes_slice(buffer: &[u8]) -> Result<Self, Error> {
let size = mem::size_of::<Self>();
let buffer_slice = buffer.get(..size).and_then(|s| s.try_into().ok());
if let Some(slice) = buffer_slice {
Ok(Self::from_le_bytes(slice))
} else {
error!(
"read_num: buffer is too short to read num of size {} (buffer len: {})",
size, buffer.len()
);
Err(Error::new(EINVAL))
}
}
}
)*
};
}
num_from_bytes_impl!(usize);
fn read_num<T>(buffer: &[u8]) -> Result<T, Error>
where
T: NumFromBytes,
{
T::from_le_bytes_slice(buffer)
}
pub fn run(
sync_pipe: FdGuard,
socket: Socket,
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuard,
) -> ! {
let mut state = SchemeState::new();
let mut scheme = NamespaceScheme::new(&socket, schemes, scheme_creation_cap);
// send namespace fd to bootstrap
let new_id = scheme.next_id;
scheme.add_namespace(new_id, scheme.root_namespace.clone(), NsPermissions::all());
scheme.next_id += 1;
let cap_fd = scheme
.socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("nsmgr: failed to create namespace fd");
let _ = syscall::call_wo(
sync_pipe.as_raw_fd(),
&cap_fd.to_ne_bytes(),
CallFlags::FD,
&[],
);
drop(sync_pipe);
log::info!("bootstrap: namespace scheme start!");
loop {
let Some(req) = socket
.next_request(SignalBehavior::Restart)
.expect("bootstrap: failed to read scheme request from kernel")
else {
break;
};
match req.kind() {
RequestKind::Call(req) => {
let resp = req.handle_sync(&mut scheme, &mut state);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
RequestKind::OnClose { id } => scheme.on_close(id),
RequestKind::SendFd(sendfd_request) => {
let result = scheme.on_sendfd(&sendfd_request);
let resp = Response::new(result, sendfd_request);
if !socket
.write_response(resp, SignalBehavior::Restart)
.expect("bootstrap: failed to write scheme response to kernel")
{
break;
}
}
_ => (),
}
}
unreachable!()
}
-170
View File
@@ -1,170 +0,0 @@
#![no_std]
#![no_main]
#![allow(internal_features)]
#![feature(core_intrinsics, str_from_raw_parts, never_type)]
#[cfg(target_arch = "aarch64")]
#[path = "aarch64.rs"]
pub mod arch;
#[cfg(target_arch = "x86")]
#[path = "i686.rs"]
pub mod arch;
#[cfg(target_arch = "x86_64")]
#[path = "x86_64.rs"]
pub mod arch;
#[cfg(target_arch = "riscv64")]
#[path = "riscv64.rs"]
pub mod arch;
pub mod exec;
pub mod initfs;
pub mod initnsmgr;
pub mod procmgr;
pub mod start;
extern crate alloc;
use core::cell::UnsafeCell;
use alloc::collections::btree_map::BTreeMap;
use redox_rt::proc::FdGuard;
use syscall::data::Map;
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::MapFlags;
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
use core::fmt::Write;
// Try fd 1 first (opened in start()). If that fails, open a fresh debug handle.
struct Writer {
fd: usize,
}
impl Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
// Try writing to our fd. If it fails and we're on fd 1,
// attempt to open a fresh debug handle.
if libredox::call::write(self.fd, s.as_bytes()).is_ok() {
return Ok(());
}
if self.fd == 1 {
let debug_root =
syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
if let Ok(new_fd) =
libredox::call::openat(debug_root, "", syscall::O_WRONLY as i32, 0)
{
self.fd = new_fd;
let _ = libredox::call::write(self.fd, s.as_bytes());
}
}
Ok(()) // Always succeed so writeln! continues formatting
}
}
let _ = writeln!(Writer { fd: 1 }, "{}", info);
core::intrinsics::abort();
}
const HEAP_OFF: usize = arch::USERMODE_END / 2;
struct Allocator;
#[global_allocator]
static ALLOCATOR: Allocator = Allocator;
struct AllocStateInner {
heap: Option<linked_list_allocator::Heap>,
heap_top: usize,
}
struct AllocState(UnsafeCell<AllocStateInner>);
unsafe impl Send for AllocState {}
unsafe impl Sync for AllocState {}
static ALLOC_STATE: AllocState = AllocState(UnsafeCell::new(AllocStateInner {
heap: None,
heap_top: HEAP_OFF + SIZE,
}));
const SIZE: usize = 1024 * 1024;
const HEAP_INCREASE_BY: usize = SIZE;
unsafe impl alloc::alloc::GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
let state = unsafe { &mut (*ALLOC_STATE.0.get()) };
let heap = state.heap.get_or_insert_with(|| {
state.heap_top = HEAP_OFF + SIZE;
let _ = unsafe {
syscall::fmap(
!0,
&Map {
offset: 0,
size: SIZE,
address: HEAP_OFF,
flags: MapFlags::PROT_WRITE
| MapFlags::PROT_READ
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_FIXED_NOREPLACE,
},
)
}
.expect("failed to map initial heap");
unsafe { linked_list_allocator::Heap::new(HEAP_OFF as *mut u8, SIZE) }
});
match heap.allocate_first_fit(layout) {
Ok(p) => p.as_ptr(),
Err(_) => {
if layout.size() > HEAP_INCREASE_BY || layout.align() > 4096 {
return core::ptr::null_mut();
}
let _ = unsafe {
syscall::fmap(
!0,
&Map {
offset: 0,
size: HEAP_INCREASE_BY,
address: state.heap_top,
flags: MapFlags::PROT_WRITE
| MapFlags::PROT_READ
| MapFlags::MAP_PRIVATE
| MapFlags::MAP_FIXED_NOREPLACE,
},
)
}
.expect("failed to extend heap");
unsafe { heap.extend(HEAP_INCREASE_BY) };
state.heap_top += HEAP_INCREASE_BY;
return unsafe { self.alloc(layout) };
}
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: core::alloc::Layout) {
unsafe {
(&mut *ALLOC_STATE.0.get())
.heap
.as_mut()
.unwrap()
.deallocate(core::ptr::NonNull::new(ptr).unwrap(), layout)
}
}
}
pub struct KernelSchemeMap(BTreeMap<GlobalSchemes, FdGuard>);
impl KernelSchemeMap {
fn new(kernel_scheme_infos: &[KernelSchemeInfo]) -> Self {
let mut map = BTreeMap::new();
for info in kernel_scheme_infos {
if let Some(scheme_id) = GlobalSchemes::try_from_raw(info.scheme_id) {
map.insert(scheme_id, FdGuard::new(info.fd));
}
}
Self(map)
}
fn get(&self, scheme: GlobalSchemes) -> Option<&FdGuard> {
self.0.get(&scheme)
}
}
File diff suppressed because it is too large Load Diff
-52
View File
@@ -1,52 +0,0 @@
ENTRY(_start)
OUTPUT_FORMAT(elf64-littleriscv)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
*(.sdata*)
. = ALIGN(4096);
__data_end = .;
__bss_start = .;
*(.bss*)
*(.sbss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-47
View File
@@ -1,47 +0,0 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 1 << 38; // Assuming Sv39
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
li a7, {number}
li a0, {fd}
la a1, {map} # pointer to Map struct
li a2, {map_size} # size of Map struct
ecall
# Test for success (nonzero value).
bne a0, x0, 2f
# (failure)
unimp
2:
li sp, {stack_size}
add sp, sp, a0
mv fp, x0
jal start
# `start` must never return.
unimp
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
-83
View File
@@ -1,83 +0,0 @@
use syscall::flag::MapFlags;
mod offsets {
unsafe extern "C" {
static __text_start: u8;
static __text_end: u8;
static __rodata_start: u8;
static __rodata_end: u8;
static __data_start: u8;
static __bss_end: u8;
}
pub fn text() -> (usize, usize) {
unsafe {
(
&__text_start as *const u8 as usize,
&__text_end as *const u8 as usize,
)
}
}
pub fn rodata() -> (usize, usize) {
unsafe {
(
&__rodata_start as *const u8 as usize,
&__rodata_end as *const u8 as usize,
)
}
}
pub fn data_and_bss() -> (usize, usize) {
unsafe {
(
&__data_start as *const u8 as usize,
&__bss_end as *const u8 as usize,
)
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn start() -> ! {
let (text_start, text_end) = offsets::text();
let (rodata_start, rodata_end) = offsets::rodata();
let (data_start, data_end) = offsets::data_and_bss();
let debug_fd = syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
let _ = syscall::openat_into(debug_fd, 0, "", syscall::O_RDONLY, 0);
let _ = syscall::openat_into(debug_fd, 1, "", syscall::O_WRONLY, 0);
let _ = syscall::openat_into(debug_fd, 2, "", syscall::O_WRONLY, 0);
unsafe {
let _ = syscall::mprotect(4096, 4096, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE)
.expect("mprotect failed for initfs header page");
let _ = syscall::mprotect(
text_start,
text_end - text_start,
MapFlags::PROT_READ | MapFlags::PROT_EXEC | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .text");
let _ = syscall::mprotect(
rodata_start,
rodata_end - rodata_start,
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .rodata");
let _ = syscall::mprotect(
data_start,
data_end - data_start,
MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .data/.bss");
let _ = syscall::mprotect(
data_end,
crate::arch::STACK_START - data_end,
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for rest of memory");
}
crate::exec::main();
}
-55
View File
@@ -1,55 +0,0 @@
ENTRY(_start)
OUTPUT_FORMAT(elf64-x86-64)
SECTIONS {
. = 4096 + 4096; /* Reserved for the null page and the initfs header prepended by redox-initfs-ar */
__initfs_header = . - 4096;
. += SIZEOF_HEADERS;
. = ALIGN(4096);
.text : {
__text_start = .;
*(.text*)
. = ALIGN(4096);
__text_end = .;
}
.rodata : {
__rodata_start = .;
*(.rodata*)
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got)
}
.got.plt : {
*(.got.plt)
. = ALIGN(4096);
__rodata_end = .;
}
.data : {
__data_start = .;
*(.data*)
. = ALIGN(4096);
__data_end = .;
*(.tbss*)
. = ALIGN(4096);
*(.tdata*)
. = ALIGN(4096);
__bss_start = .;
*(.bss*)
. = ALIGN(4096);
__bss_end = .;
}
/DISCARD/ : {
*(.comment*)
*(.eh_frame*)
*(.gcc_except_table*)
*(.note*)
*(.rel.eh_frame*)
}
}
-49
View File
@@ -1,49 +0,0 @@
use core::mem;
use syscall::{data::Map, flag::MapFlags, number::SYS_FMAP};
const STACK_SIZE: usize = 64 * 1024; // 64 KiB
pub const USERMODE_END: usize = 0x0000_8000_0000_0000;
pub const STACK_START: usize = USERMODE_END - syscall::KERNEL_METADATA_SIZE - STACK_SIZE;
static MAP: Map = Map {
offset: 0,
size: STACK_SIZE,
flags: MapFlags::PROT_READ
.union(MapFlags::PROT_WRITE)
.union(MapFlags::MAP_PRIVATE)
.union(MapFlags::MAP_FIXED_NOREPLACE),
address: STACK_START, // highest possible user address
};
core::arch::global_asm!(
"
.globl _start
_start:
# Setup a stack.
mov rax, {number}
mov rdi, {fd}
mov rsi, offset {map} # pointer to Map struct
mov rdx, {map_size} # size of Map struct
syscall
# Test for success (nonzero value).
cmp rax, 0
jg 1f
# (failure)
ud2
1:
# Subtract 16 since all instructions seem to hate non-canonical RSP values :)
lea rsp, [rax+{stack_size}-16]
mov rbp, rsp
# Stack has the same alignment as `size`.
call start
# `start` must never return.
ud2
",
fd = const usize::MAX, // dummy fd indicates anonymous map
map = sym MAP,
map_size = const mem::size_of::<Map>(),
number = const SYS_FMAP,
stack_size = const STACK_SIZE,
);
-113
View File
@@ -1,113 +0,0 @@
#!/bin/bash
RED='\033[1;38;5;196m'
GREEN='\033[1;38;5;46m'
NC='\033[0m'
show_help() {
echo "Usage: $(basename "$0") [OPTIONS]"
echo ""
echo "Description:"
echo " Wrapper for redoxer to run checks or tests on Redox OS targets."
echo ""
echo "Options:"
echo " --test Run 'cargo test' instead of 'cargo check'"
echo " --all-target Run the command on all supported Redox architectures"
echo " --target=<target> Override the target architecture (e.g., i586-unknown-redox)"
echo " --arch=<arch> Override the target architecture using arch (e.g., i586)"
echo " --help Show this help message"
echo ""
echo "Supported Targets:"
for t in "${SUPPORTED_TARGETS[@]}"; do
echo " - $t"
done
echo ""
echo "Environment:"
echo " TARGET Sets the default target (overridden by --target)"
}
if ! command -v redoxer &> /dev/null; then
echo "Error: 'redoxer' CLI not found."
echo "Please install it: cargo install redoxer"
exit 1
fi
SUPPORTED_TARGETS=(
"x86_64-unknown-redox"
"i586-unknown-redox"
"aarch64-unknown-redox"
"riscv64gc-unknown-redox"
)
CURRENT_TARGET="${TARGET:-x86_64-unknown-redox}"
CHECK_ALL=false
CMD_ACTION="all"
while [[ $# -gt 0 ]]; do
case "$1" in
--all-target)
CHECK_ALL=true
;;
--test)
CMD_ACTION="test"
;;
--target=*)
CURRENT_TARGET="${1#*=}"
;;
--arch=*)
CURRENT_TARGET="${1#*=}-unknown-redox"
;;
--help)
show_help
exit 0
;;
*)
echo -e "${RED}Error: Unknown option '$1'${NC}"
show_help
exit 1
;;
esac
shift
done
run_redoxer() {
export TARGET=$1
redoxer toolchain || { echo -e "${RED}Fail: redoxer toolchain for: $target.${NC}" && exit 1; }
echo "----------------------------------------"
echo "Running make $CMD_ACTION for: $TARGET"
if make "$CMD_ACTION"; then
return 0
else
echo -e "${RED}Fail: $CMD_ACTION $TARGET failed.${NC}"
return 1
fi
}
if [ "$CHECK_ALL" = true ]; then
echo "Running $CMD_ACTION for all supported Redox targets..."
has_error=false
for target in "${SUPPORTED_TARGETS[@]}"; do
if ! run_redoxer "$target"; then
has_error=true
fi
done
echo "----------------------------------------"
if [ "$has_error" = true ]; then
echo -e "${RED}Summary: One or more targets failed.${NC}"
exit 1
else
echo -e "${GREEN}Summary: All targets passed!${NC}"
exit 0
fi
else
if run_redoxer "$CURRENT_TARGET"; then
echo -e "${GREEN}Success: $CMD_ACTION $CURRENT_TARGET passed.${NC}"
exit 0
else
exit 1
fi
fi
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "config"
description = "Configuration override library"
version = "0.0.0"
edition = "2024"
[dependencies]
[lints]
workspace = true
-40
View File
@@ -1,40 +0,0 @@
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::{fs, io};
pub fn config(name: &str) -> Result<Vec<PathBuf>, io::Error> {
config_for_dirs(&[
&Path::new("/usr/lib").join(format!("{name}.d")),
&Path::new("/etc").join(format!("{name}.d")),
])
}
pub fn config_for_initfs(name: &str) -> Result<Vec<PathBuf>, io::Error> {
config_for_dirs(&[
&Path::new("/scheme/initfs/lib").join(format!("{name}.d")),
&Path::new("/scheme/initfs/etc").join(format!("{name}.d")),
])
}
pub fn config_for_dirs(dirs: &[impl AsRef<Path>]) -> Result<Vec<PathBuf>, io::Error> {
// This must be a BTreeMap to iterate in sorted order.
let mut entries = BTreeMap::new();
for dir in dirs {
let dir = dir.as_ref();
if !dir.exists() {
// Skip non-existent dirs
continue;
}
for entry_res in fs::read_dir(&dir)? {
// This intentionally overwrites older entries with
// the same filename to allow overriding entries in
// one search dir with those in a later search dir.
let entry = entry_res?;
entries.insert(entry.file_name(), entry.path());
}
}
Ok(entries.into_values().collect())
}
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "daemon"
description = "Redox daemon library"
version = "0.0.0"
edition = "2024"
[dependencies]
libc.workspace = true
libredox.workspace = true
redox-scheme.workspace = true
redox_syscall.workspace = true
[lints]
workspace = true
-150
View File
@@ -1,150 +0,0 @@
//! A library for creating and managing daemons for RedoxOS.
#![feature(never_type)]
use std::io::{self, PipeWriter, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
use libredox::Fd;
use redox_scheme::Socket;
use redox_scheme::scheme::{SchemeAsync, SchemeSync};
unsafe fn get_fd(var: &str) -> RawFd {
let fd: RawFd = std::env::var(var).unwrap().parse().unwrap();
if unsafe { libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC) } == -1 {
panic!(
"daemon: failed to set CLOEXEC flag for {var} fd: {}",
io::Error::last_os_error()
);
}
fd
}
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: OwnedFd) {
cmd.env(env, format!("{}", fd.as_raw_fd()));
unsafe {
cmd.pre_exec(move || {
// Pass notify pipe to child
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
});
}
}
/// A long running background process that handles requests.
#[must_use = "Daemon::ready must be called"]
pub struct Daemon {
write_pipe: PipeWriter,
}
impl Daemon {
/// Create a new daemon.
pub fn new(f: impl FnOnce(Daemon) -> !) -> ! {
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
f(Daemon { write_pipe })
}
/// Notify the process that the daemon is ready to accept requests.
///
/// BrokenPipe is tolerated: init may have already closed its read end
/// during the startup phase. The daemon is operational regardless of
/// init's readiness tracking state.
pub fn ready(mut self) {
match self.write_pipe.write_all(&[0]) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::BrokenPipe => {}
Err(err) => {
eprintln!("daemon: failed to notify init of readiness: {err}");
}
}
}
/// Executes `Command` as a child process.
// FIXME remove once the service spawning of hwd and pcid-spawner is moved to init
#[deprecated]
pub fn spawn(mut cmd: Command) {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
let desc = format!("{cmd:?}");
if let Err(err) = cmd.spawn() {
eprintln!("daemon: failed to execute {desc}: {err}");
return;
}
// Release this process's copy of the INIT_NOTIFY write end. pass_fd
// moved it into cmd's pre_exec closure, so while cmd is alive the
// parent keeps the write end open and read_exact below never sees EOF
// if the child exits without notifying readiness — a deadlock (e.g.
// hwd spawning a duplicate acpid that exits early on EEXIST). Dropping
// cmd closes the parent's copy so a child exit yields EOF.
drop(cmd);
let mut data = [0];
match read_pipe.read_exact(&mut data) {
Ok(()) => {}
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
eprintln!("daemon: {desc} exited without notifying readiness");
}
Err(err) => {
eprintln!("daemon: failed to wait for {desc}: {err}");
}
}
}
}
/// A long running background process that handles requests using schemes.
#[must_use = "SchemeDaemon::ready must be called"]
pub struct SchemeDaemon {
write_pipe: PipeWriter,
}
impl SchemeDaemon {
/// Create a new daemon for use with schemes.
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
f(SchemeDaemon { write_pipe })
}
/// Notify the process that the scheme daemon is ready to accept requests.
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
let raw_fd = cap_fd.into_raw();
let res = syscall::call_wo(
self.write_pipe.as_raw_fd() as usize,
&raw_fd.to_ne_bytes(),
syscall::CallFlags::FD,
&[],
);
res?;
Ok(())
}
/// Notify the process that the synchronous scheme daemon is ready to accept requests.
pub fn ready_sync_scheme<S: SchemeSync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
/// Notify the process that the asynchronous scheme daemon is ready to accept requests.
pub fn ready_async_scheme<S: SchemeAsync>(
self,
socket: &Socket,
scheme: &mut S,
) -> syscall::Result<()> {
let cap_id = scheme.scheme_root()?;
let cap_fd = socket.create_this_scheme_fd(0, cap_id, 0, 0)?;
self.ready_with_fd(Fd::new(cap_fd))
}
}
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "dhcpd"
version = "0.0.0"
edition = "2024"
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
[dependencies]
[lints]
workspace = true
-37
View File
@@ -1,37 +0,0 @@
#[repr(C, packed)]
pub struct Dhcp {
pub op: u8,
pub htype: u8,
pub hlen: u8,
pub hops: u8,
pub tid: u32,
pub secs: u16,
pub flags: u16,
pub ciaddr: [u8; 4],
pub yiaddr: [u8; 4],
pub siaddr: [u8; 4],
pub giaddr: [u8; 4],
pub chaddr: [u8; 16],
pub sname: [u8; 64],
pub file: [u8; 128],
pub magic: u32,
pub options: [u8; 308],
}
pub const DHCPDISCOVER: u8 = 1;
pub const DHCPOFFER: u8 = 2;
pub const DHCPREQUEST: u8 = 3;
pub const DHCPDECLINE: u8 = 4;
pub const DHCPACK: u8 = 5;
pub const DHCPNAK: u8 = 6;
pub const DHCPRELEASE: u8 = 7;
pub const OPT_SUBNET_MASK: u8 = 1;
pub const OPT_ROUTER: u8 = 3;
pub const OPT_DNS: u8 = 6;
pub const OPT_REQUESTED_IP: u8 = 50;
pub const OPT_LEASE_TIME: u8 = 51;
pub const OPT_MESSAGE_TYPE: u8 = 53;
pub const OPT_SERVER_ID: u8 = 54;
pub const OPT_PARAM_REQUEST: u8 = 55;
pub const OPT_END: u8 = 255;
-381
View File
@@ -1,381 +0,0 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;
use std::{env, process, time};
use dhcp::{
Dhcp, DHCPACK, DHCPDISCOVER, DHCPNAK, DHCPOFFER, DHCPREQUEST, DHCPRELEASE,
OPT_DNS, OPT_LEASE_TIME, OPT_MESSAGE_TYPE, OPT_REQUESTED_IP, OPT_ROUTER,
OPT_SERVER_ID, OPT_SUBNET_MASK, OPT_END,
};
mod dhcp;
macro_rules! try_fmt {
($e:expr, $m:expr) => {
match $e {
Ok(ok) => ok,
Err(err) => return Err(format!("{}: {}", $m, err)),
}
};
}
fn get_cfg_value(path: &str) -> Result<String, String> {
let path = format!("/scheme/netcfg/{path}");
let mut file = File::open(&path).map_err(|_| format!("Can't open {path}"))?;
let mut result = String::new();
file.read_to_string(&mut result)
.map_err(|_| format!("Can't read {path}"))?;
Ok(result)
}
fn get_iface_cfg_value(iface: &str, cfg: &str) -> Result<String, String> {
let path = format!("ifaces/{iface}/{cfg}");
get_cfg_value(&path)
}
fn set_cfg_value(path: &str, value: &str) -> Result<(), String> {
let path = format!("/scheme/netcfg/{path}");
let mut file = OpenOptions::new()
.read(false)
.write(true)
.create(false)
.open(&path)
.map_err(|_| format!("Can't open {path}"))?;
file.write(value.as_bytes())
.map(|_| ())
.map_err(|_| format!("Can't write {value} to {path}"))?;
file.sync_data()
.map_err(|_| format!("Can't commit {value} to {path}"))
}
fn set_iface_cfg_value(iface: &str, cfg: &str, value: &str) -> Result<(), String> {
let path = format!("ifaces/{iface}/{cfg}");
set_cfg_value(&path, value)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
struct MacAddr {
bytes: [u8; 6],
}
impl MacAddr {
fn from_str(string: &str) -> Self {
MacAddr::try_parse_with_delimeter(string, ':')
.or_else(|| MacAddr::try_parse_with_delimeter(string, '-'))
.unwrap_or_default()
}
fn try_parse_with_delimeter(string: &str, delimeter: char) -> Option<MacAddr> {
let mut addr = MacAddr::default();
let mut segments = 0;
for part in string.split(delimeter) {
if segments >= addr.bytes.len() {
return None;
}
addr.bytes[segments] = match u8::from_str_radix(part, 16) {
Ok(b) => b,
_ => return None,
};
segments += 1;
}
if segments == addr.bytes.len() {
Some(addr)
} else {
None
}
}
fn to_string(&self) -> String {
format!(
"{:>02X}-{:>02X}-{:>02X}-{:>02X}-{:>02X}-{:>02X}",
self.bytes[0],
self.bytes[1],
self.bytes[2],
self.bytes[3],
self.bytes[4],
self.bytes[5]
)
}
}
fn dhcp(iface: &str, verbose: bool) -> Result<(), String> {
let current_mac = MacAddr::from_str(get_iface_cfg_value(iface, "mac")?.trim());
let _current_ip = get_iface_cfg_value(iface, "addr/list")?
.lines()
.next()
.map(|l| l.to_owned())
.unwrap_or("0.0.0.0".to_string());
if verbose {
println!("DHCP: MAC: {} Starting", current_mac.to_string());
}
let tid = try_fmt!(
time::SystemTime::now().duration_since(time::UNIX_EPOCH),
"failed to get time"
).subsec_nanos();
let socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind udp");
try_fmt!(socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "failed to connect");
// 8s (was 30s): a DHCP server that is present answers a DISCOVER in well
// under a second, so a long read timeout only serves to stall boot when no
// server responds (e.g. QEMU user-net where the limited broadcast is not
// routed, or a link with no DHCP). Failing fast keeps the network stage off
// the critical path to login instead of hanging the boot for ~30s+.
try_fmt!(socket.set_read_timeout(Some(Duration::new(8, 0))), "failed to set read timeout");
try_fmt!(socket.set_write_timeout(Some(Duration::new(8, 0))), "failed to set write timeout");
let mut subnet_option: Option<[u8; 4]> = None;
let mut router_option: Option<[u8; 4]> = None;
let mut dns_option: Option<[u8; 4]> = None;
let mut server_id_option: Option<[u8; 4]> = None;
let mut lease_time_secs: u32 = 86400;
// DHCPDISCOVER
{
let mut discover = Dhcp::default();
init_dhcp_header(&mut discover, current_mac, tid);
let disc_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPDISCOVER, OPT_END];
discover.options[..disc_opts.len()].copy_from_slice(disc_opts);
try_fmt!(send_dhcp(&discover, &socket), "failed to send discover");
if verbose { println!("DHCP: Sent Discover"); }
}
// Recv DHCPOFFER
let mut offer_data = [0; 65536];
try_fmt!(socket.recv(&mut offer_data), "failed to receive offer");
let offer = unsafe { &*(offer_data.as_ptr() as *const Dhcp) };
if verbose { println!("DHCP: Offer IP: {:?}", offer.yiaddr); }
parse_options(&offer.options, &mut |code, data| {
match code {
OPT_SUBNET_MASK if data.len() == 4 && subnet_option.is_none() => {
subnet_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_ROUTER if data.len() == 4 && router_option.is_none() => {
router_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_DNS if data.len() == 4 && dns_option.is_none() => {
dns_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_LEASE_TIME if data.len() == 4 => {
lease_time_secs = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
}
OPT_SERVER_ID if data.len() == 4 && server_id_option.is_none() => {
server_id_option = Some([data[0], data[1], data[2], data[3]]);
}
_ => {}
}
});
let mask_len = compute_prefix_len(subnet_option);
let new_ips = format!(
"{}.{}.{}.{}/{}\n",
offer.yiaddr[0], offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3], mask_len
);
try_fmt!(set_iface_cfg_value(iface, "addr/set", &new_ips), "failed to set ip");
apply_dhcp_config(iface, router_option, dns_option, verbose)?;
let server_id = server_id_option.unwrap_or([0, 0, 0, 0]);
// DHCPREQUEST
{
let mut request = Dhcp::default();
init_dhcp_header(&mut request, current_mac, tid);
let req_opts: &[u8] = &[
OPT_MESSAGE_TYPE, 1, DHCPREQUEST,
OPT_REQUESTED_IP, 4,
offer.yiaddr[0], offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3],
OPT_SERVER_ID, 4, server_id[0], server_id[1], server_id[2], server_id[3],
OPT_END,
];
request.options[..req_opts.len()].copy_from_slice(req_opts);
try_fmt!(send_dhcp(&request, &socket), "failed to send request");
if verbose { println!("DHCP: Sent Request"); }
}
// Recv DHCPACK
let mut ack_data = [0; 65536];
try_fmt!(socket.recv(&mut ack_data), "failed to receive ack");
if verbose { println!("DHCP: lease acquired, {}s lease time", lease_time_secs); }
// RFC 2131 lease lifecycle: RENEW at T1, REBIND at T2
let t1 = Duration::from_secs(lease_time_secs as u64 / 2);
let t2 = Duration::from_secs((lease_time_secs as u64 * 7) / 8);
let now = time::Instant::now();
let t1_deadline = now + t1;
let mut remaining = t1_deadline.saturating_duration_since(time::Instant::now());
while remaining > Duration::ZERO {
std::thread::sleep(std::cmp::min(remaining, Duration::from_secs(60)));
remaining = t1_deadline.saturating_duration_since(time::Instant::now());
}
if verbose { println!("DHCP: entering RENEW state"); }
{
let mut renew = Dhcp::default();
init_dhcp_header(&mut renew, current_mac, tid.wrapping_add(1));
renew.ciaddr = offer.yiaddr;
let rn_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
renew.options[..rn_opts.len()].copy_from_slice(rn_opts);
try_fmt!(send_dhcp(&renew, &socket), "failed to send renew");
}
socket.set_read_timeout(Some(t2.saturating_sub(t1))).ok();
match socket.recv(&mut ack_data) {
Ok(_) => {
let response = unsafe { &*(ack_data.as_ptr() as *const Dhcp) };
match get_message_type(&response.options) {
Some(DHCPACK) => { if verbose { println!("DHCP: renewed"); } }
Some(DHCPNAK) => {
if verbose { println!("DHCP: NAK, restarting"); }
return dhcp(iface, verbose);
}
_ => {}
}
}
Err(_) => {
if verbose { println!("DHCP: entering REBIND state"); }
let bind_socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind rebind");
try_fmt!(bind_socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "rebind connect");
let mut rebind = Dhcp::default();
init_dhcp_header(&mut rebind, current_mac, tid.wrapping_add(2));
rebind.ciaddr = offer.yiaddr;
let rb_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
rebind.options[..rb_opts.len()].copy_from_slice(rb_opts);
let _ = send_dhcp(&rebind, &bind_socket);
bind_socket.set_read_timeout(Some(Duration::from_secs(10))).ok();
if let Ok(_) = bind_socket.recv(&mut ack_data) {
if verbose { println!("DHCP: rebound"); }
} else {
if verbose { println!("DHCP: lease expired, restarting"); }
return dhcp(iface, verbose);
}
}
}
Ok(())
}
fn apply_dhcp_config(
iface: &str,
router: Option<[u8; 4]>,
dns: Option<[u8; 4]>,
verbose: bool,
) -> Result<(), String> {
if let Some(router) = router {
let route = format!("default via {}.{}.{}.{}", router[0], router[1], router[2], router[3]);
try_fmt!(set_cfg_value("route/add", &route), "failed to set route");
}
if let Some(mut dns) = dns {
if dns[0] == 127 {
dns = [9, 9, 9, 9];
if verbose { println!("DHCP: replaced loopback DNS with Quad9"); }
}
let ns = format!("{}.{}.{}.{}", dns[0], dns[1], dns[2], dns[3]);
try_fmt!(set_cfg_value("resolv/nameserver", &ns), "failed to set DNS");
}
Ok(())
}
fn compute_prefix_len(subnet: Option<[u8; 4]>) -> u32 {
let Some(subnet) = subnet else { return 24 };
let inverted: u32 = !u32::from_be_bytes(subnet);
inverted.leading_zeros()
}
fn parse_options(options: &[u8], cb: &mut dyn FnMut(u8, &[u8])) {
let mut i = 0;
while i < options.len() {
let code = options[i];
if code == 0 { i += 1; continue; }
if code == OPT_END { break; }
i += 1;
if i >= options.len() { break; }
let len = options[i] as usize;
i += 1;
if i + len > options.len() { break; }
cb(code, &options[i..i + len]);
i += len;
}
}
fn get_message_type(options: &[u8]) -> Option<u8> {
let mut msg_type = None;
parse_options(options, &mut |code, data| {
if code == OPT_MESSAGE_TYPE && data.len() == 1 {
msg_type = Some(data[0]);
}
});
msg_type
}
fn init_dhcp_header(pkt: &mut Dhcp, mac: MacAddr, tid: u32) {
*pkt = Dhcp::default();
pkt.op = 1;
pkt.htype = 1;
pkt.hlen = 6;
pkt.tid = tid;
pkt.flags = 0x8000u16.to_be();
pkt.chaddr[..6].copy_from_slice(&mac.bytes);
pkt.magic = 0x63825363u32.to_be();
}
fn send_dhcp(pkt: &Dhcp, socket: &UdpSocket) -> Result<(), String> {
let data = unsafe {
std::slice::from_raw_parts(pkt as *const Dhcp as *const u8, std::mem::size_of::<Dhcp>())
};
socket.send(data).map(|_| ()).map_err(|e| format!("send: {}", e))
}
impl Default for Dhcp {
fn default() -> Self {
Dhcp {
op: 0, htype: 0, hlen: 0, hops: 0, tid: 0, secs: 0, flags: 0,
ciaddr: [0; 4], yiaddr: [0; 4], siaddr: [0; 4], giaddr: [0; 4],
chaddr: [0; 16], sname: [0; 64], file: [0; 128], magic: 0,
options: [0; 308],
}
}
}
fn main() {
let mut verbose = false;
let iface = "eth0";
for arg in env::args().skip(1) {
match arg.as_ref() {
"-v" => verbose = true,
_ => (),
}
}
if let Err(err) = dhcp(iface, verbose) {
eprintln!("dhcpd: {err}");
process::exit(1);
}
}
#[cfg(test)]
mod test {
use super::MacAddr;
#[test]
fn from_str_test() {
let mac = MacAddr { bytes: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab] };
let empty_mac = MacAddr::default();
assert_eq!(mac, MacAddr::from_str("01:23:45:67:89:ab"));
assert_eq!(mac, MacAddr::from_str("1:23:45:67:89:ab"));
assert_eq!(mac, MacAddr::from_str("01:23:45:67:89:AB"));
assert_eq!(mac, MacAddr::from_str("01-23-45-67-89-ab"));
assert_eq!(empty_mac, MacAddr::from_str(""));
assert_eq!(empty_mac, MacAddr::from_str("01:23:45:67:89"));
assert_eq!(empty_mac, MacAddr::from_str("01:23:45:67:89:ab:cd"));
assert_eq!(empty_mac, MacAddr::from_str("x1:23:45:67:89:ab"));
assert_eq!(mac, MacAddr::from_str(&mac.to_string()));
}
}
-4
View File
@@ -1,4 +0,0 @@
[package]
name = "dhcpv6d"
version = "0.1.0"
edition = "2021"
-286
View File
@@ -1,286 +0,0 @@
//! dhcpv6d — DHCPv6 client daemon for Red Bear OS.
//!
//! Mirrors Linux 7.1's systemd-networkd `sd-dhcp6-client.c`.
//! Implements RFC 8415 state machine:
//! SOLICIT → ADVERTISE → REQUEST → REPLY
//!
//! Reference:
//! - `sd-dhcp6-client.c` — DHCPv6 client state machine
//! - RFC 8415 §17 — client behavior
//! - RFC 3315 — original DHCPv6 specification
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::net::{SocketAddr, UdpSocket};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{env, process, time};
const DHCPV6_CLIENT_PORT: u16 = 546;
const DHCPV6_SERVER_PORT: u16 = 547;
const MULTICAST_ADDR: &str = "ff02::1:2";
const SOLICIT: u8 = 1;
const ADVERTISE: u8 = 2;
const REQUEST: u8 = 3;
const REPLY: u8 = 7;
const RELEASE: u8 = 8;
const INFO_REQUEST: u8 = 11;
const OPT_CLIENTID: u16 = 1;
const OPT_SERVERID: u16 = 2;
const OPT_IA_NA: u16 = 3;
const OPT_IAADDR: u16 = 5;
const OPT_ORO: u16 = 6;
const OPT_DNS: u16 = 23;
const OPT_DOMAIN: u16 = 24;
macro_rules! try_fmt {
($e:expr, $m:expr) => {
match $e {
Ok(ok) => ok,
Err(err) => return Err(format!("{}: {}", $m, err)),
}
};
}
fn set_cfg(path: &str, value: &str) -> Result<(), String> {
let full = format!("/scheme/netcfg/{path}");
let mut f = OpenOptions::new().write(true).create(false).open(&full)
.map_err(|_| format!("open {}", full))?;
f.write_all(value.as_bytes()).map_err(|_| format!("write {}", full))?;
f.sync_data().map_err(|_| "sync failed".into())
}
fn get_mac() -> Result<[u8; 6], String> {
let s = fs::read_to_string("/scheme/netcfg/ifaces/eth0/mac")
.map_err(|e| format!("read mac: {}", e))?;
let mut bytes = [0u8; 6];
for (i, part) in s.trim().split(&[':', '-'][..]).take(6).enumerate() {
bytes[i] = u8::from_str_radix(part, 16).map_err(|_| "bad mac".to_string())?;
}
Ok(bytes)
}
fn build_duid(mac: [u8; 6]) -> Vec<u8> {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let mut duid = vec![0u8; 14];
duid[0..2].copy_from_slice(&[0x00, 0x01]);
duid[2..4].copy_from_slice(&[0x00, 0x01]);
duid[4..8].copy_from_slice(&time.to_be_bytes());
duid[8..14].copy_from_slice(&mac);
duid
}
fn put_u16(buf: &mut Vec<u8>, val: u16) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn put_u32(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn put_option(buf: &mut Vec<u8>, code: u16, data: &[u8]) {
put_u16(buf, code);
put_u16(buf, data.len() as u16);
buf.extend_from_slice(data);
}
struct Dhcp6Packet {
data: Vec<u8>,
}
impl Dhcp6Packet {
fn new(msg_type: u8, tid: [u8; 3], duid: &[u8]) -> Self {
let mut data = vec![msg_type];
data.extend_from_slice(&tid);
put_option(&mut data, OPT_CLIENTID, duid);
put_option(&mut data, OPT_ORO, &[0, OPT_DNS as u8, 0, OPT_DOMAIN as u8]);
Dhcp6Packet { data }
}
fn add_ia_na(&mut self, iaid: u32, t1: u32, t2: u32) {
let mut ia = Vec::new();
put_u32(&mut ia, iaid);
put_u32(&mut ia, t1);
put_u32(&mut ia, t2);
put_option(&mut self.data, OPT_IA_NA, &ia);
}
}
struct Dhcp6Reply {
msg_type: u8,
server_id: Option<Vec<u8>>,
addresses: Vec<([u8; 16], u32, u32)>,
dns_servers: Vec<[u8; 16]>,
domains: Vec<String>,
}
fn parse_reply(data: &[u8]) -> Result<Dhcp6Reply, String> {
if data.len() < 4 {
return Err("packet too short".into());
}
let msg_type = data[0];
let mut reply = Dhcp6Reply {
msg_type,
server_id: None,
addresses: Vec::new(),
dns_servers: Vec::new(),
domains: Vec::new(),
};
let mut pos = 4;
while pos + 4 <= data.len() {
let code = u16::from_be_bytes([data[pos], data[pos + 1]]);
let len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if pos + len > data.len() {
break;
}
let opt_data = &data[pos..pos + len];
match code {
OPT_SERVERID => reply.server_id = Some(opt_data.to_vec()),
OPT_DNS => {
for chunk in opt_data.chunks(16) {
if chunk.len() == 16 {
let mut addr = [0u8; 16];
addr.copy_from_slice(chunk);
reply.dns_servers.push(addr);
}
}
}
OPT_IA_NA => {
if opt_data.len() >= 12 {
let t1 = u32::from_be_bytes([opt_data[4], opt_data[5], opt_data[6], opt_data[7]]);
let t2 = u32::from_be_bytes([opt_data[8], opt_data[9], opt_data[10], opt_data[11]]);
let mut inner = &opt_data[12..];
while inner.len() >= 4 {
let inner_code = u16::from_be_bytes([inner[0], inner[1]]);
let inner_len = u16::from_be_bytes([inner[2], inner[3]]) as usize;
inner = &inner[4..];
if inner.len() < inner_len { break; }
if inner_code == OPT_IAADDR && inner_len >= 24 {
let mut addr = [0u8; 16];
addr.copy_from_slice(&inner[..16]);
let pref = u32::from_be_bytes([inner[16], inner[17], inner[18], inner[19]]);
let valid = u32::from_be_bytes([inner[20], inner[21], inner[22], inner[23]]);
reply.addresses.push((addr, pref, valid));
}
inner = &inner[inner_len..];
}
}
}
_ => {}
}
pos += len;
}
Ok(reply)
}
fn main() {
let mut verbose = false;
for arg in env::args().skip(1) {
if arg == "-v" || arg == "--verbose" { verbose = true; }
}
if let Err(e) = run(verbose) {
eprintln!("dhcpv6d: {}", e);
process::exit(1);
}
}
fn run(verbose: bool) -> Result<(), String> {
let mac = get_mac()?;
let duid = build_duid(mac);
if verbose {
println!("dhcpv6d: DUID {:02x?}", duid);
}
let socket = try_fmt!(
UdpSocket::bind((MULTICAST_ADDR, DHCPV6_CLIENT_PORT)),
"bind"
);
try_fmt!(
socket.connect(SocketAddr::new(
MULTICAST_ADDR.parse().map_err(|_| "bad addr")?,
DHCPV6_SERVER_PORT,
)),
"connect"
);
try_fmt!(socket.set_read_timeout(Some(Duration::from_secs(5))), "timeout");
let tid = [
(mac[0] ^ mac[1]) as u8,
(mac[2] ^ mac[3]) as u8,
(mac[4] ^ mac[5]) as u8,
];
// SOLICIT
let mut solicit = Dhcp6Packet::new(SOLICIT, tid, &duid);
solicit.add_ia_na(1, 0, 0);
try_fmt!(socket.send(&solicit.data), "send solicit");
if verbose { println!("dhcpv6d: sent SOLICIT"); }
// Recv ADVERTISE
let mut buf = [0u8; 65536];
let n = try_fmt!(socket.recv(&mut buf), "recv advertise");
let adv = parse_reply(&buf[..n])?;
if verbose {
println!("dhcpv6d: received ADVERTISE, {} addresses", adv.addresses.len());
}
// REQUEST
let mut request = Dhcp6Packet::new(REQUEST, tid, &duid);
if let Some(ref sid) = adv.server_id {
put_option(&mut request.data, OPT_SERVERID, sid);
}
request.add_ia_na(1, 0, 0);
try_fmt!(socket.send(&request.data), "send request");
if verbose { println!("dhcpv6d: sent REQUEST"); }
// Recv REPLY
let n = try_fmt!(socket.recv(&mut buf), "recv reply");
let reply = parse_reply(&buf[..n])?;
if verbose {
println!("dhcpv6d: received REPLY, {} addresses", reply.addresses.len());
}
for (addr, pref, valid) in &reply.addresses {
let addr_str = format!(
"{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}",
u16::from_be_bytes([addr[0], addr[1]]),
u16::from_be_bytes([addr[2], addr[3]]),
u16::from_be_bytes([addr[4], addr[5]]),
u16::from_be_bytes([addr[6], addr[7]]),
u16::from_be_bytes([addr[8], addr[9]]),
u16::from_be_bytes([addr[10], addr[11]]),
u16::from_be_bytes([addr[12], addr[13]]),
u16::from_be_bytes([addr[14], addr[15]]),
);
let cidr = format!("{}/128\n", addr_str);
try_fmt!(set_cfg("ifaces/eth0/addr/set", &cidr), "set addr");
if verbose {
println!("dhcpv6d: configured {} (pref={}s valid={}s)", addr_str, pref, valid);
}
}
for dns in &reply.dns_servers {
let dns_str = format!(
"{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}",
u16::from_be_bytes([dns[0], dns[1]]),
u16::from_be_bytes([dns[2], dns[3]]),
u16::from_be_bytes([dns[4], dns[5]]),
u16::from_be_bytes([dns[6], dns[7]]),
u16::from_be_bytes([dns[8], dns[9]]),
u16::from_be_bytes([dns[10], dns[11]]),
u16::from_be_bytes([dns[12], dns[13]]),
u16::from_be_bytes([dns[14], dns[15]]),
);
try_fmt!(set_cfg("resolv/nameserver6", &dns_str), "set DNS");
if verbose { println!("dhcpv6d: DNS6 {}", dns_str); }
}
Ok(())
}
-63
View File
@@ -1,63 +0,0 @@
# Community Hardware
This document tracks the devices from developers or community that need a driver.
This document was created because unfortunately we can't know the most sold device models of the world to measure our device porting priority, thus we will use our community data to measure our device priorities, if you find a "device model users" survey (similar to [Debian Popularity Contest](https://popcon.debian.org/) and [Steam Hardware/Software Survey](https://store.steampowered.com/hwsurvey/Steam-Hardware-Software-Survey-Welcome-to-Steam)), please comment.
If you want to contribute to this table, install [pciutils](https://mj.ucw.cz/sw/pciutils/) on your Linux or Unix-like distribution (it may have a package on your distribution), run the `lspci -v` command to see your hardware devices, their kernel drivers and give the results of these items on each device:
- The first field (each device has an unique name for this item)
- Kernel driver
- Kernel module
If you are unsure of what to do, you can talk with us on the [chat](https://doc.redox-os.org/book/chat.html).
## Template
You will use this template to insert your devices on the table.
```
| | | | No |
```
- Remove the `#` characters in the port numbers to avoid GitLab issues to be wrongly mentioned
## Devices
| **Device model** | **Kernel driver?** | **Kernel module?** | **There's a Redox driver?** |
|------------------|--------------------|--------------------|-----------------------------|
| Realtek RTL8821CE 802.11ac (Wi-Fi) | rtw_8821ce | rtw88_8821ce | No |
| Intel Ice Lake-LP SPI Controller | intel-spi | spi_intel_pci | No |
| Intel Ice Lake-LP SMBus Controller | i801_smbus | i2c_i801 | No |
| Intel Ice Lake-LP Smart Sound Technology Audio Controller | snd_hda_intel | snd_hda_intel, snd_sof_pci_intel_icl | No |
| Intel Ice Lake-LP Serial IO SPI Controller | intel-lpss | No | No |
| Intel Ice Lake-LP Serial IO UART Controller | intel-lpss | No | No |
| Intel Ice Lake-LP Serial IO I2C Controller | intel-lpss | No | No |
| Ice Lake-LP USB 3.1 xHCI Host Controller | xhci_hcd | No | No |
| Intel Processor Power and Thermal Controller | proc_thermal | processor_thermal_device_pci_legacy | No |
| Intel Device 8a02 | icl_uncore | No | No |
| Iris Plus Graphics G1 (Ice Lake) | i915 | i915 | No |
| Intel Corporation Raptor Lake-P 6p+8e cores Host Bridge/DRAM Controller | No | No | No |
| Intel Corporation Raptor Lake PCI Express 5.0 Graphics Port (PEG010) (prog-if 00 [Normal decode]) | pcieport | No | No |
| Intel Corporation Raptor Lake-P [UHD Graphics] (rev 04) (prog-if 00 [VGA controller]) | i915 | i915 | No |
| Intel Corporation Raptor Lake Dynamic Platform and Thermal Framework Processor Participant | proc_thermal_pci | processor_thermal_device_pci | No |
| Intel Corporation Raptor Lake PCIe 4.0 Graphics Port (prog-if 00 [Normal decode]) | pcieport | No | No |
| Intel Corporation Raptor Lake-P Thunderbolt 4 PCI Express Root Port 0 (prog-if 00 [Normal decode]) | pcieport | No | No |
| Intel Corporation GNA Scoring Accelerator module | No | No | No |
| Intel Corporation Raptor Lake-P Thunderbolt 4 USB Controller (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No |
| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI 0 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No |
| Intel Corporation Raptor Lake-P Thunderbolt 4 NHI 1 (prog-if 40 [USB4 Host Interface]) | thunderbolt | thunderbolt | No |
| Intel Corporation Alder Lake PCH USB 3.2 xHCI Host Controller (rev 01) (prog-if 30 [XHCI]) | xhci_hcd | xhci_pci | No |
| Intel Corporation Alder Lake PCH Shared SRAM (rev 01) | No | No | No |
| Intel Corporation Raptor Lake PCH CNVi WiFi (rev 01) | iwlwifi | iwlwifi | No |
| Intel Corporation Alder Lake PCH Serial IO I2C Controller #0 (rev 01) | intel-lpss | intel_lpss_pci | No |
| Intel Corporation Alder Lake PCH HECI Controller (rev 01) | mei_me | mei_me | No |
| Intel Corporation Device 51b8 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No |
| Intel Corporation Alder Lake-P PCH PCIe Root Port 6 (rev 01) (prog-if 00 [Normal decode]) | pcieport | No | No |
| Intel Corporation Raptor Lake LPC/eSPI Controller (rev 01) | No | No | No |
| Intel Corporation Raptor Lake-P/U/H cAVS (rev 01) (prog-if 80) | sof-audio-pci-intel-tgl | snd_hda_intel, snd_sof_pci_intel_tgl | No |
| Intel Corporation Alder Lake PCH-P SMBus Host Controller | i801_smbus | i2c_i801 | No |
| Intel Corporation Alder Lake-P PCH SPI Controller (rev 01) | intel-spi | spi_intel_pci | No |
| NVIDIA Corporation GA107GLM [RTX A1000 6GB Laptop GPU] (rev a1) | nvidia | nouveau, nvidia_drm, nvidia | No |
| SK hynix Platinum P41/PC801 NVMe Solid State Drive (prog-if 02 [NVM Express]) | nvme | nvme | No |
| Realtek Semiconductor Co., Ltd. RTS5261 PCI Express Card Reader (rev 01) | rtsx_pci | rtsx_pci | No |
-160
View File
@@ -1,160 +0,0 @@
# Drivers
- [Libraries](#libraries)
- [Services](#services)
- [Hardware Interfaces](#hardware-interfaces)
- [Devices](#devices)
- [CPU](#cpu)
- [Controllers](#controllers)
- [Storage](#storage)
- [Graphics](#graphics)
- [Input](#input)
- [Sound](#sound)
- [Networking](#networking)
- [Virtualization](#virtualization)
- [System Interfaces](#system-interfaces)
- [System Calls](#system-calls)
- [Schemes](#schemes)
- [Contribution Details](#contribution-details)
## Libraries
- amlserde - Library to provide serialization/deserialization of the AML symbol table from ACPI
- common - Library with shared driver code
- executor - Library to run Rust futures and integrate the executor in an interrupt+queue model without a separated reactor thread
- [graphics/console-draw](graphics/console-draw/) - Library with shared terminal drawing code
- [graphics/driver-graphics](graphics/driver-graphics/) - Library with shared graphics code
- [graphics/graphics-ipc](graphics/graphics-ipc/) - Library with graphics IPC shared code
- [net/driver-network](net/driver-network/) - Library with shared networking code
- [storage/partitionlib](storage/partitionlib/) - Library with MBR and GPT code
- [storage/driver-block](storage/driver-block/) - Library with shared storage code
- virtio-core - VirtIO driver library
## Services
- [graphics/fbbootlogd](graphics/fbbootlogd/) - Daemon for boot log drawing
- [graphics/fbcond](graphics/fbcond/) - Terminal daemon
- hwd - Daemon that handle the ACPI and DeviceTree booting
- inputd - Multiplexes input from multiple input drivers and provides that to Orbital
- pcid-spawner - Daemon for PCI-based device driver spawn
- [storage/lived](storage/lived/) - Daemon for live disk
- redoxerd - Daemon that send/receive terminal text between the host system and QEMU
## Hardware Interfaces
- acpid - ACPI interface driver
- pcid - PCI and PCI Express driver
## Devices
### CPU
- rtcd - x86 Real Time Clock driver
### Controllers
- [usb/xhcid](usb/xhcid/) - xHCI USB controller driver
### Storage
- [storage/ahcid](storage/ahcid/) - AHCI (SATA) driver
- [storage/bcm2835-sdhcid](storage/bcm2835-sdhcid/) - BCM2835 storage driver
- [storage/ided](storage/ided/) - PATA (IDE) driver
- [storage/nvmed](storage/nvmed/) - NVMe driver
- [storage/virtio-blkd](storage/virtio-blkd/) - VirtIO block device driver
- [storage/usbscsid](storage/usbscsid/) - USB SCSI driver
### Graphics
- [graphics/ihdgd](graphics/ihdgd/) - Intel graphics driver
- [graphics/vesad](graphics/vesad/) - VESA video driver
- [graphics/virtio-gpud](graphics/virtio-gpud/) - VirtIO-GPU device driver
### Input
- [input/ps2d](input/ps2d/) - PS/2 interface driver
- [input/usbhidd](input/usbhidd/) - USB HID driver
- [usb/usbhubd](usb/usbhubd/) - USB Hub driver
- [usb/usbctl](usb/usbctl/) - TODO
### Sound
- [audio/ac97d](audio/ac97d/) - AC'97 codec driver
- [audio/ihdad](audio/ihdad/) - Intel HD Audio chipset driver
- [audio/sb16d](audio/sb16d/) - Sound Blaster sound card driver
### Networking
- [net/e1000d](net/e1000d/) - Intel Gigabit ethernet driver
- [net/ixgbed](net/ixgbed/) - Intel 10 Gigabit ethernet driver
- [net/rtl8139d](net/rtl8139d/), [net/rtl8168d](net/rtl8168d/) - Realtek ethernet drivers
- [net/virtio-netd](net/virtio-netd/) - VirtIO network device driver
### Virtualization
- vboxd - VirtualBox driver
Some drivers are work-in-progress and incomplete, read [this](https://gitlab.redox-os.org/redox-os/base/-/issues/56) tracking issue to verify.
## System Interfaces
This section explain the system interfaces used by drivers.
### System Calls
- `iopl` : system call that sets the I/O privilege level. x86 has four privilege rings (0/1/2/3), of which the kernel runs in ring 0 and userspace in ring 3. IOPL can only be changed by the kernel, for obvious security reasons, and therefore the Redox kernel needs root to set it. It is unique for each process. Processes with IOPL=3 can access I/O ports, and the kernel can access them as well.
### Schemes
- `/scheme/memory/physical` : Allows mapping physical memory frames to driver-accessible virtual memory pages, with various available memory types:
- `/scheme/memory/physical` : Default memory type (currently writeback)
- `/scheme/memory/physical@wb` Writeback cached memory
- `/scheme/memory/physical@uc` : Uncacheable memory
- `/scheme/memory/physical@wc` : Write-combining memory
- `/scheme/irq` : Allows getting events from interrupts. It is used primarily by listening for its file descriptors using the `/scheme/event` scheme.
## Contribution Details
### Driver Design
A device driver on Redox is an user-space daemon that use system calls and schemes to work, while operating systems with monolithic kernels drivers use internal kernel APIs instead of common program APIs.
If you want to port a driver from a monolithic operating system to Redox you will need to rewrite the driver with reverse enginnering of the code logic, because the logic is adapted to internal kernel APIs (it's a hard task if the device is complex, datasheets are much more easy).
### Write a Driver
Datasheets are preferable (much more easy depending on device complexity), when they are freely available. Be aware that datasheets are often provided under a [Non-Disclosure Agreement](https://en.wikipedia.org/wiki/Non-disclosure_agreement) from hardware vendors, which can affect the ability to create an MIT-licensed driver.
If datasheets aren't available you need to do reverse-engineering of BSD or Linux drivers (if you want use a Linux driver as reference for your Redox driver please ask in the [Chat](https://doc.redox-os.org/book/chat.html) before the implementation to know/satisfy the license requirements and not waste your time, also if you use a BSD driver not licensed as BSD as reference).
### Libraries
You should use the [redox-scheme](https://crates.io/crates/redox-scheme) and [redox_event](https://crates.io/crates/redox_event) libraries to create your drivers, you can also read the [example driver](https://gitlab.redox-os.org/redox-os/exampled) or read the code of other drivers with the same type of your device.
Before testing your changes be aware of [this](https://doc.redox-os.org/book/coding-and-building.html#how-to-update-initfs).
### References
If you want to reverse enginner the existing drivers, you can access the BSD code using these links:
- [FreeBSD drivers](https://github.com/freebsd/freebsd-src/tree/main/sys/dev)
- [NetBSD drivers](https://github.com/NetBSD/src/tree/trunk/sys/dev)
- [OpenBSD drivers](https://github.com/openbsd/src/tree/master/sys/dev)
## How To Contribute
To learn how to contribute to this system component you need to read the following document:
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
## Development
To learn how to do development with this system component inside the Redox build system you need to read the [Build System](https://doc.redox-os.org/book/build-system-reference.html) and [Coding and Building](https://doc.redox-os.org/book/coding-and-building.html) pages.
### How To Build
To build this system component you need to download the Redox build system, you can learn how to do it on the [Building Redox](https://doc.redox-os.org/book/podman-build.html) page.
This is necessary because they only work with cross-compilation to a Redox virtual machine or real hardware, but you can do some testing from Linux.
[Back to top](#drivers)
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "acpi-resource"
description = "Shared ACPI resource template decoder"
version = "0.0.1"
authors = ["Red Bear OS"]
repository = "https://gitlab.redox-os.org/redox-os/drivers"
categories = ["hardware-support"]
license = "MIT/Apache-2.0"
edition = "2021"
[dependencies]
serde.workspace = true
thiserror.workspace = true
-688
View File
@@ -1,688 +0,0 @@
use serde::{Deserialize, Serialize};
use thiserror::Error;
const SMALL_IRQ: u8 = 0x20;
const SMALL_END_TAG: u8 = 0x78;
const LARGE_MEMORY32: u8 = 0x85;
const LARGE_FIXED_MEMORY32: u8 = 0x86;
const LARGE_ADDRESS32: u8 = 0x87;
const LARGE_EXTENDED_IRQ: u8 = 0x89;
const LARGE_ADDRESS64: u8 = 0x8A;
const LARGE_GPIO: u8 = 0x8C;
const LARGE_SERIAL_BUS: u8 = 0x8E;
const SERIAL_BUS_I2C: u8 = 1;
const I2C_TYPE_DATA_LEN: usize = 6;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptTrigger {
Edge,
Level,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptPolarity {
ActiveHigh,
ActiveLow,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum AddressResourceType {
MemoryRange,
IoRange,
BusNumberRange,
Unknown(u8),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResourceSource {
pub index: u8,
pub source: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct IrqDescriptor {
pub interrupts: Vec<u8>,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub shareable: bool,
pub wake_capable: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExtendedIrqDescriptor {
pub producer_consumer: bool,
pub interrupts: Vec<u32>,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub shareable: bool,
pub wake_capable: bool,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GpioDescriptor {
pub revision_id: u8,
pub producer_consumer: bool,
pub pin_config: u8,
pub shareable: bool,
pub wake_capable: bool,
pub io_restriction: u8,
pub triggering: InterruptTrigger,
pub polarity: InterruptPolarity,
pub drive_strength: u16,
pub debounce_timeout: u16,
pub pins: Vec<u16>,
pub resource_source: Option<ResourceSource>,
pub vendor_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cSerialBusDescriptor {
pub revision_id: u8,
pub producer_consumer: bool,
pub slave_mode: bool,
pub connection_sharing: bool,
pub type_revision_id: u8,
pub access_mode_10bit: bool,
pub slave_address: u16,
pub connection_speed: u32,
pub resource_source: Option<ResourceSource>,
pub vendor_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Memory32RangeDescriptor {
pub write_protect: bool,
pub minimum: u32,
pub maximum: u32,
pub alignment: u32,
pub address_length: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FixedMemory32Descriptor {
pub write_protect: bool,
pub address: u32,
pub address_length: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Address32Descriptor {
pub resource_type: AddressResourceType,
pub producer_consumer: bool,
pub decode: bool,
pub min_address_fixed: bool,
pub max_address_fixed: bool,
pub specific_flags: u8,
pub granularity: u32,
pub minimum: u32,
pub maximum: u32,
pub translation_offset: u32,
pub address_length: u32,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Address64Descriptor {
pub resource_type: AddressResourceType,
pub producer_consumer: bool,
pub decode: bool,
pub min_address_fixed: bool,
pub max_address_fixed: bool,
pub specific_flags: u8,
pub granularity: u64,
pub minimum: u64,
pub maximum: u64,
pub translation_offset: u64,
pub address_length: u64,
pub resource_source: Option<ResourceSource>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ResourceDescriptor {
Irq(IrqDescriptor),
ExtendedIrq(ExtendedIrqDescriptor),
GpioInt(GpioDescriptor),
GpioIo(GpioDescriptor),
I2cSerialBus(I2cSerialBusDescriptor),
Memory32Range(Memory32RangeDescriptor),
FixedMemory32(FixedMemory32Descriptor),
Address32(Address32Descriptor),
Address64(Address64Descriptor),
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum ResourceDecodeError {
#[error("descriptor at offset {offset} overruns the resource template")]
TruncatedDescriptor { offset: usize },
#[error("unsupported small descriptor length {length} for tag {tag:#04x} at offset {offset}")]
InvalidSmallLength {
offset: usize,
tag: u8,
length: usize,
},
#[error("descriptor {descriptor} at offset {offset} is shorter than {minimum} bytes")]
InvalidLargeLength {
offset: usize,
descriptor: &'static str,
minimum: usize,
},
#[error("descriptor {descriptor} at offset {offset} has an invalid internal offset")]
InvalidInternalOffset {
offset: usize,
descriptor: &'static str,
},
}
pub fn decode_resource_template(
bytes: &[u8],
) -> Result<Vec<ResourceDescriptor>, ResourceDecodeError> {
let mut resources = Vec::new();
let mut offset = 0usize;
while offset < bytes.len() {
let descriptor = *bytes
.get(offset)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
if descriptor & 0x80 == 0 {
let length = usize::from(descriptor & 0x07);
let end = offset + 1 + length;
let desc = bytes
.get(offset..end)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
let body = &desc[1..];
match descriptor & 0x78 {
SMALL_IRQ => resources.push(ResourceDescriptor::Irq(parse_irq(body, offset)?)),
SMALL_END_TAG => break,
_ => {}
}
offset = end;
continue;
}
let length = usize::from(read_u16(bytes, offset + 1)?);
let end = offset + 3 + length;
let desc = bytes
.get(offset..end)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
let body = &desc[3..];
match descriptor {
LARGE_MEMORY32 => resources.push(ResourceDescriptor::Memory32Range(parse_memory32(
body, offset,
)?)),
LARGE_FIXED_MEMORY32 => resources.push(ResourceDescriptor::FixedMemory32(
parse_fixed_memory32(body, offset)?,
)),
LARGE_ADDRESS32 => {
resources.push(ResourceDescriptor::Address32(parse_address32(
desc, body, offset,
)?));
}
LARGE_ADDRESS64 => {
resources.push(ResourceDescriptor::Address64(parse_address64(
desc, body, offset,
)?));
}
LARGE_EXTENDED_IRQ => resources.push(ResourceDescriptor::ExtendedIrq(
parse_extended_irq(desc, body, offset)?,
)),
LARGE_GPIO => {
let (is_interrupt, descriptor) = parse_gpio(desc, body, offset)?;
resources.push(if is_interrupt {
ResourceDescriptor::GpioInt(descriptor)
} else {
ResourceDescriptor::GpioIo(descriptor)
});
}
LARGE_SERIAL_BUS => {
if let Some(descriptor) = parse_i2c_serial_bus(desc, body, offset)? {
resources.push(ResourceDescriptor::I2cSerialBus(descriptor));
}
}
_ => {}
}
offset = end;
}
Ok(resources)
}
fn parse_irq(body: &[u8], offset: usize) -> Result<IrqDescriptor, ResourceDecodeError> {
if body.len() != 2 && body.len() != 3 {
return Err(ResourceDecodeError::InvalidSmallLength {
offset,
tag: SMALL_IRQ,
length: body.len(),
});
}
let mask = u16::from_le_bytes([body[0], body[1]]);
let flags = body.get(2).copied().unwrap_or(0);
let interrupts = (0..16)
.filter(|irq| mask & (1 << irq) != 0)
.map(|irq| irq as u8)
.collect();
Ok(IrqDescriptor {
interrupts,
triggering: if flags & 0x01 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if flags & 0x08 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
shareable: flags & 0x10 != 0,
wake_capable: flags & 0x20 != 0,
})
}
fn parse_extended_irq(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<ExtendedIrqDescriptor, ResourceDecodeError> {
ensure_length(body, 2, offset, "ExtendedIrq")?;
let flags = body[0];
let count = usize::from(body[1]);
let ints_len = count * 4;
ensure_length(body, 2 + ints_len, offset, "ExtendedIrq")?;
let interrupts = (0..count)
.map(|index| read_u32(body, 2 + index * 4))
.collect::<Result<Vec<_>, _>>()?;
let resource_source = if body.len() > 2 + ints_len {
Some(parse_source_inline(&body[2 + ints_len..]))
} else {
None
};
let _ = desc;
Ok(ExtendedIrqDescriptor {
producer_consumer: flags & 0x01 != 0,
triggering: if flags & 0x02 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if flags & 0x04 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
shareable: flags & 0x08 != 0,
wake_capable: flags & 0x10 != 0,
interrupts,
resource_source,
})
}
fn parse_gpio(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<(bool, GpioDescriptor), ResourceDecodeError> {
ensure_length(body, 20, offset, "Gpio")?;
let connection_type = body[1];
let flags = read_u16(body, 2)?;
let int_flags = read_u16(body, 4)?;
let pin_table_offset = usize::from(read_u16(body, 11)?);
let resource_source_index = body[13];
let resource_source_offset = usize::from(read_u16(body, 14)?);
let vendor_offset = usize::from(read_u16(body, 16)?);
let vendor_length = usize::from(read_u16(body, 18)?);
let pins_end = min_nonzero([resource_source_offset, vendor_offset, desc.len()]);
let pins = parse_u16_list(desc, pin_table_offset, pins_end, offset, "Gpio")?;
let resource_source = parse_source_absolute(
desc,
resource_source_offset,
min_nonzero([vendor_offset, desc.len()]),
resource_source_index,
offset,
"Gpio",
)?;
let vendor_data = parse_blob_absolute(desc, vendor_offset, vendor_length, offset, "Gpio")?;
Ok((
connection_type == 0,
GpioDescriptor {
revision_id: body[0],
producer_consumer: flags & 0x0001 != 0,
pin_config: body[6],
shareable: int_flags & 0x0008 != 0,
wake_capable: int_flags & 0x0010 != 0,
io_restriction: (int_flags & 0x0003) as u8,
triggering: if int_flags & 0x0001 != 0 {
InterruptTrigger::Level
} else {
InterruptTrigger::Edge
},
polarity: if int_flags & 0x0002 != 0 {
InterruptPolarity::ActiveLow
} else {
InterruptPolarity::ActiveHigh
},
drive_strength: read_u16(body, 7)?,
debounce_timeout: read_u16(body, 9)?,
pins,
resource_source,
vendor_data,
},
))
}
fn parse_i2c_serial_bus(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Option<I2cSerialBusDescriptor>, ResourceDecodeError> {
ensure_length(body, 15, offset, "SerialBus")?;
if body[2] != SERIAL_BUS_I2C {
return Ok(None);
}
let type_data_length = usize::from(read_u16(body, 7)?);
if type_data_length < I2C_TYPE_DATA_LEN {
return Err(ResourceDecodeError::InvalidLargeLength {
offset,
descriptor: "I2cSerialBus",
minimum: 15,
});
}
let vendor_length = type_data_length - I2C_TYPE_DATA_LEN;
let vendor_data = parse_blob_absolute(desc, 18, vendor_length, offset, "I2cSerialBus")?;
let resource_source = parse_source_absolute(
desc,
12 + type_data_length,
desc.len(),
body[1],
offset,
"I2cSerialBus",
)?;
Ok(Some(I2cSerialBusDescriptor {
revision_id: body[0],
producer_consumer: body[3] & 0x02 != 0,
slave_mode: body[3] & 0x01 != 0,
connection_sharing: body[3] & 0x04 != 0,
type_revision_id: body[6],
access_mode_10bit: read_u16(body, 4)? & 0x0001 != 0,
connection_speed: read_u32(body, 9)?,
slave_address: read_u16(body, 13)?,
resource_source,
vendor_data,
}))
}
fn parse_memory32(
body: &[u8],
offset: usize,
) -> Result<Memory32RangeDescriptor, ResourceDecodeError> {
ensure_length(body, 17, offset, "Memory32Range")?;
Ok(Memory32RangeDescriptor {
write_protect: body[0] & 0x01 != 0,
minimum: read_u32(body, 1)?,
maximum: read_u32(body, 5)?,
alignment: read_u32(body, 9)?,
address_length: read_u32(body, 13)?,
})
}
fn parse_fixed_memory32(
body: &[u8],
offset: usize,
) -> Result<FixedMemory32Descriptor, ResourceDecodeError> {
ensure_length(body, 9, offset, "FixedMemory32")?;
Ok(FixedMemory32Descriptor {
write_protect: body[0] & 0x01 != 0,
address: read_u32(body, 1)?,
address_length: read_u32(body, 5)?,
})
}
fn parse_address32(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Address32Descriptor, ResourceDecodeError> {
ensure_length(body, 23, offset, "Address32")?;
Ok(Address32Descriptor {
resource_type: parse_address_type(body[0]),
producer_consumer: body[1] & 0x01 != 0,
decode: body[1] & 0x02 != 0,
min_address_fixed: body[1] & 0x04 != 0,
max_address_fixed: body[1] & 0x08 != 0,
specific_flags: body[2],
granularity: read_u32(body, 3)?,
minimum: read_u32(body, 7)?,
maximum: read_u32(body, 11)?,
translation_offset: read_u32(body, 15)?,
address_length: read_u32(body, 19)?,
resource_source: if desc.len() > 26 {
parse_source_absolute(desc, 26, desc.len(), desc[26], offset, "Address32")?
} else {
None
},
})
}
fn parse_address64(
desc: &[u8],
body: &[u8],
offset: usize,
) -> Result<Address64Descriptor, ResourceDecodeError> {
ensure_length(body, 43, offset, "Address64")?;
Ok(Address64Descriptor {
resource_type: parse_address_type(body[0]),
producer_consumer: body[1] & 0x01 != 0,
decode: body[1] & 0x02 != 0,
min_address_fixed: body[1] & 0x04 != 0,
max_address_fixed: body[1] & 0x08 != 0,
specific_flags: body[2],
granularity: read_u64(body, 3)?,
minimum: read_u64(body, 11)?,
maximum: read_u64(body, 19)?,
translation_offset: read_u64(body, 27)?,
address_length: read_u64(body, 35)?,
resource_source: if desc.len() > 46 {
parse_source_absolute(desc, 46, desc.len(), desc[46], offset, "Address64")?
} else {
None
},
})
}
fn ensure_length(
body: &[u8],
minimum: usize,
offset: usize,
descriptor: &'static str,
) -> Result<(), ResourceDecodeError> {
if body.len() < minimum {
return Err(ResourceDecodeError::InvalidLargeLength {
offset,
descriptor,
minimum,
});
}
Ok(())
}
fn parse_source_inline(bytes: &[u8]) -> ResourceSource {
let index = bytes.first().copied().unwrap_or(0);
let source = bytes.get(1..).map(parse_nul_string).unwrap_or_default();
ResourceSource { index, source }
}
fn parse_source_absolute(
desc: &[u8],
start: usize,
end: usize,
index: u8,
offset: usize,
descriptor: &'static str,
) -> Result<Option<ResourceSource>, ResourceDecodeError> {
if start == 0 || start >= end || start > desc.len() {
return Ok(None);
}
let slice = desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
Ok(Some(ResourceSource {
index,
source: parse_nul_string(slice),
}))
}
fn parse_blob_absolute(
desc: &[u8],
start: usize,
length: usize,
offset: usize,
descriptor: &'static str,
) -> Result<Vec<u8>, ResourceDecodeError> {
if start == 0 || length == 0 {
return Ok(Vec::new());
}
let end = start + length;
Ok(desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?
.to_vec())
}
fn parse_u16_list(
desc: &[u8],
start: usize,
end: usize,
offset: usize,
descriptor: &'static str,
) -> Result<Vec<u16>, ResourceDecodeError> {
if start == 0 || start >= end || start > desc.len() {
return Ok(Vec::new());
}
let slice = desc
.get(start..end)
.ok_or(ResourceDecodeError::InvalidInternalOffset { offset, descriptor })?;
if slice.len() % 2 != 0 {
return Err(ResourceDecodeError::InvalidInternalOffset { offset, descriptor });
}
slice
.chunks_exact(2)
.map(|chunk| Ok(u16::from_le_bytes([chunk[0], chunk[1]])))
.collect()
}
fn parse_nul_string(bytes: &[u8]) -> String {
let end = bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(bytes.len());
String::from_utf8_lossy(&bytes[..end]).to_string()
}
fn parse_address_type(value: u8) -> AddressResourceType {
match value {
0 => AddressResourceType::MemoryRange,
1 => AddressResourceType::IoRange,
2 => AddressResourceType::BusNumberRange,
other => AddressResourceType::Unknown(other),
}
}
fn read_u16(bytes: &[u8], offset: usize) -> Result<u16, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 2)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 4)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
}
fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, ResourceDecodeError> {
let slice = bytes
.get(offset..offset + 8)
.ok_or(ResourceDecodeError::TruncatedDescriptor { offset })?;
Ok(u64::from_le_bytes([
slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
]))
}
fn min_nonzero<const N: usize>(values: [usize; N]) -> usize {
values
.into_iter()
.filter(|value| *value != 0)
.min()
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::{decode_resource_template, ResourceDescriptor};
#[test]
fn decodes_small_irq_descriptor() {
let resources = decode_resource_template(&[0x23, 0x0A, 0x00, 0x19, 0x79, 0x00]).unwrap();
assert!(matches!(
&resources[0],
ResourceDescriptor::Irq(descriptor)
if descriptor.interrupts == vec![1, 3]
&& descriptor.shareable
&& descriptor.wake_capable == false
));
}
#[test]
fn decodes_i2c_serial_bus_descriptor() {
let template = [
0x8E, 0x14, 0x00, 0x01, 0x02, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0x80, 0x1A,
0x06, 0x00, 0x15, 0x00, b'I', b'2', b'C', b'0', 0x00, 0x79, 0x00,
];
let resources = decode_resource_template(&template).unwrap();
assert!(matches!(
&resources[0],
ResourceDescriptor::I2cSerialBus(descriptor)
if descriptor.connection_speed == 400_000
&& descriptor.slave_address == 0x15
&& descriptor.resource_source.as_ref().map(|source| source.source.as_str())
== Some("I2C0")
));
}
#[test]
fn decodes_gpio_interrupt_descriptor() {
let template = [
0x8C, 0x1B, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17,
0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x12, b'\\', b'_', b'S', b'B',
0x00, 0x79, 0x00,
];
let resources = decode_resource_template(&template).unwrap();
assert!(matches!(&resources[0], ResourceDescriptor::GpioInt(_)));
}
}
-24
View File
@@ -1,24 +0,0 @@
[package]
name = "acpi"
version = "6.1.1"
authors = ["Isaac Woods"]
repository = "https://github.com/rust-osdev/acpi"
description = "A pure-Rust library for interacting with ACPI"
categories = ["hardware-support", "no-std"]
license = "MIT/Apache-2.0"
edition = "2024"
[dependencies]
bit_field = "0.10.2"
bitflags = "2.5.0"
log = "0.4.20"
spinning_top = "0.3.0"
pci_types = { version = "0.10.0", public = true }
byteorder = { version = "1.5.0", default-features = false }
[dev-dependencies]
[features]
default = ["alloc", "aml"]
alloc = []
aml = ["alloc"]
-201
View File
@@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018 Isaac Woods
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-32
View File
@@ -1,32 +0,0 @@
# Acpi
![Build Status](https://github.com/rust-osdev/acpi/actions/workflows/build.yml/badge.svg)
[![Version](https://img.shields.io/crates/v/acpi.svg?style=rounded-square)](https://crates.io/crates/acpi/)
### [Documentation](https://docs.rs/acpi)
`acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a
complex framework for power management and device discovery and configuration. ACPI is used on
modern x64, as well as some ARM and RISC-V platforms. An operating system needs to interact with
ACPI to correctly set up a platform's interrupt controllers, perform power management, and fully
support many other platform capabilities.
This crate provides a limited API that can be used without an allocator, for example for use
from a bootloader. This API will allow you to search for the RSDP, enumerate over the available
tables, and interact with the tables using their raw structures. All other functionality is
behind an `alloc` feature (enabled by default) and requires an allocator.
With an allocator, this crate provides a richer higher-level interfaces to the static tables, as
well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT tables.
See the library documentation for example usage. You will almost certainly need to read portions
of the [ACPI Specification](https://uefi.org/specifications) too (however, be aware that firmware often
ships with ACPI tables that are not spec-compliant).
## Licence
This project is dual-licenced under:
- Apache Licence, Version 2.0 ([LICENCE-APACHE](LICENCE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENCE-MIT](LICENCE-MIT) or http://opensource.org/licenses/MIT)
Unless you explicitly state otherwise, any contribution submitted for inclusion in this work by you,
as defined in the Apache-2.0 licence, shall be dual licenced as above, without additional terms or
conditions.
-263
View File
@@ -1,263 +0,0 @@
//! ACPI defines a Generic Address Structure (GAS), which provides a versatile way to describe register locations
//! in a wide range of address spaces.
use crate::{AcpiError, Handler, PhysicalMapping};
use core::ptr;
use log::warn;
/// This is the raw form of a Generic Address Structure, and follows the layout found in the ACPI tables.
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct RawGenericAddress {
pub address_space: u8,
pub bit_width: u8,
pub bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
impl RawGenericAddress {
pub(crate) const fn is_empty(&self) -> bool {
self.address_space == 0
&& self.bit_width == 0
&& self.bit_offset == 0
&& self.access_size == 0
&& self.address == 0
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum AddressSpace {
SystemMemory,
SystemIo,
/// Describes a register in the configuration space of a PCI device in segment `0`, on bus `0`.
/// The `address` field is of the format:
/// ```ignore
/// 64 48 32 16 0
/// +---------------+---------------+---------------+---------------+
/// | reserved (0) | device | function | offset |
/// +---------------+---------------+---------------+---------------+
/// ```
PciConfigSpace,
EmbeddedController,
SMBus,
SystemCmos,
PciBarTarget,
Ipmi,
GeneralIo,
GenericSerialBus,
PlatformCommunicationsChannel,
FunctionalFixedHardware,
OemDefined(u8),
}
/// Specifies a standard access size. The access size of a GAS can be non-standard, and is defined
/// by the Address Space ID in such cases.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum StandardAccessSize {
Undefined,
ByteAccess,
WordAccess,
DWordAccess,
QWordAccess,
}
impl TryFrom<u8> for StandardAccessSize {
type Error = AcpiError;
fn try_from(size: u8) -> Result<Self, Self::Error> {
match size {
0 => Ok(StandardAccessSize::Undefined),
1 => Ok(StandardAccessSize::ByteAccess),
2 => Ok(StandardAccessSize::WordAccess),
3 => Ok(StandardAccessSize::DWordAccess),
4 => Ok(StandardAccessSize::QWordAccess),
_ => Err(AcpiError::InvalidGenericAddress),
}
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct GenericAddress {
pub address_space: AddressSpace,
pub bit_width: u8,
pub bit_offset: u8,
pub access_size: u8,
pub address: u64,
}
impl GenericAddress {
pub fn from_raw(raw: RawGenericAddress) -> Result<GenericAddress, AcpiError> {
let address_space = match raw.address_space {
0x00 => AddressSpace::SystemMemory,
0x01 => AddressSpace::SystemIo,
0x02 => AddressSpace::PciConfigSpace,
0x03 => AddressSpace::EmbeddedController,
0x04 => AddressSpace::SMBus,
0x05 => AddressSpace::SystemCmos,
0x06 => AddressSpace::PciBarTarget,
0x07 => AddressSpace::Ipmi,
0x08 => AddressSpace::GeneralIo,
0x09 => AddressSpace::GenericSerialBus,
0x0a => AddressSpace::PlatformCommunicationsChannel,
0x0b..=0x7e => return Err(AcpiError::InvalidGenericAddress),
0x7f => AddressSpace::FunctionalFixedHardware,
0x80..=0xbf => return Err(AcpiError::InvalidGenericAddress),
0xc0..=0xff => AddressSpace::OemDefined(raw.address_space),
};
Ok(GenericAddress {
address_space,
bit_width: raw.bit_width,
bit_offset: raw.bit_offset,
access_size: raw.access_size,
address: raw.address,
})
}
pub fn standard_access_size(&self) -> Result<StandardAccessSize, AcpiError> {
StandardAccessSize::try_from(self.access_size)
}
}
pub struct MappedGas<H: Handler> {
gas: GenericAddress,
handler: H,
mapping: Option<PhysicalMapping<H, u8>>,
}
impl<H> MappedGas<H>
where
H: Handler,
{
/// Map the given `GenericAddress`, giving a `MappedGas` that can be read from and written to.
///
/// ### Safety
/// The supplied `GenericAddress` must be a valid GAS and all subsequent reads and writes must
/// be valid.
pub unsafe fn map_gas(gas: GenericAddress, handler: &H) -> Result<MappedGas<H>, AcpiError> {
match gas.address_space {
AddressSpace::SystemMemory => {
// TODO: how to know total size needed?
let mapping = unsafe { handler.map_physical_region(gas.address as usize, 0x1000) };
Ok(MappedGas { gas, handler: handler.clone(), mapping: Some(mapping) })
}
AddressSpace::SystemIo => Ok(MappedGas { gas, handler: handler.clone(), mapping: None }),
other => {
warn!("Mapping a GAS in address space {:?} is not supported!", other);
Err(AcpiError::LibUnimplemented)
}
}
}
pub fn read(&self) -> Result<u64, AcpiError> {
/*
* TODO: this is only correct for basic GASs that require a single access. Extend it to
* support bit offsets and multiple reads etc.
*/
let access_size_bits = gas_decode_access_bit_width(self.gas)?;
match self.gas.address_space {
AddressSpace::SystemMemory => {
let mapping = self.mapping.as_ref().unwrap();
match access_size_bits {
8 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u8) as u64 }),
16 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u16) as u64 }),
32 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u32) as u64 }),
64 => Ok(unsafe { ptr::read_volatile(mapping.virtual_start.as_ptr() as *const u64) }),
_ => Err(AcpiError::InvalidGenericAddress),
}
}
AddressSpace::SystemIo => match access_size_bits {
8 => Ok(self.handler.read_io_u8(self.gas.address as u16) as u64),
16 => Ok(self.handler.read_io_u16(self.gas.address as u16) as u64),
32 => Ok(self.handler.read_io_u32(self.gas.address as u16) as u64),
_ => Err(AcpiError::InvalidGenericAddress),
},
_ => {
warn!("Read from GAS with address space {:?} is not supported. Ignored.", self.gas.address_space);
Err(AcpiError::LibUnimplemented)
}
}
}
pub fn write(&self, value: u64) -> Result<(), AcpiError> {
// TODO: see above
let access_size_bits = gas_decode_access_bit_width(self.gas)?;
match self.gas.address_space {
AddressSpace::SystemMemory => {
let mapping = self.mapping.as_ref().unwrap();
match access_size_bits {
8 => unsafe {
ptr::write_volatile(mapping.virtual_start.as_ptr(), value as u8);
},
16 => unsafe {
ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u16, value as u16);
},
32 => unsafe {
ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u32, value as u32);
},
64 => unsafe { ptr::write_volatile(mapping.virtual_start.as_ptr() as *mut u64, value) },
_ => return Err(AcpiError::InvalidGenericAddress),
}
Ok(())
}
AddressSpace::SystemIo => {
match access_size_bits {
8 => self.handler.write_io_u8(self.gas.address as u16, value as u8),
16 => self.handler.write_io_u16(self.gas.address as u16, value as u16),
32 => self.handler.write_io_u32(self.gas.address as u16, value as u32),
_ => return Err(AcpiError::InvalidGenericAddress),
}
Ok(())
}
_ => {
warn!("Write to GAS with address space {:?} is not supported. Ignored.", self.gas.address_space);
Err(AcpiError::LibUnimplemented)
}
}
}
}
/// Returns the access size that should be made for a given `GenericAddress`, in bits.
fn gas_decode_access_bit_width(gas: GenericAddress) -> Result<u8, AcpiError> {
/*
* This is more complex than it should be - we follow ACPICA to try and work with quirky
* firmwares.
*
* We should actually ignore the access sizes for normal registers (they tend to be unspecified
* in my experience anyway) and base our accesses on the width of the register. Only if a
* register has a bit width that cannot be accessed as a single native access do we look at the
* access size.
*
* We use a third method, based on the alignment of the address, for registers that have
* non-zero bit offsets. These are not typically encountered in normal registers - they very
* often mean the GAS has come from APEI (ACPI Platform Error Interface), and so needs special
* handling.
*/
if gas.bit_offset == 0 && [8, 16, 32, 64].contains(&gas.bit_width) {
Ok(gas.bit_width)
} else if gas.access_size != 0 {
match gas.access_size {
1 => Ok(8),
2 => Ok(16),
3 => Ok(32),
4 => Ok(64),
_ => Err(AcpiError::InvalidGenericAddress),
}
} else {
/*
* Work out the access size based on the alignment of the address. We round up the total
* width (`bit_offset + bit_width`) to the next power-of-two, up to 64.
*/
let total_width = gas.bit_offset + gas.bit_width;
Ok(if total_width <= 8 {
8
} else if total_width <= 16 {
16
} else if total_width <= 32 {
32
} else {
64
})
}
}
File diff suppressed because it is too large Load Diff
-680
View File
@@ -1,680 +0,0 @@
use super::{
AmlError,
Handle,
object::{Object, ObjectType, WrappedObject},
};
use alloc::{
collections::btree_map::BTreeMap,
string::{String, ToString},
vec,
vec::Vec,
};
use bit_field::BitField;
use core::{
fmt,
str::{self, FromStr},
};
use log::{trace, warn};
#[derive(Clone)]
pub struct Namespace {
root: NamespaceLevel,
}
impl Namespace {
/// Create a new AML namespace, with the expected pre-defined objects.
pub fn new(global_lock_mutex: Handle) -> Namespace {
let mut namespace = Namespace { root: NamespaceLevel::new(NamespaceLevelKind::Scope) };
namespace.add_level(AmlName::from_str("\\_GPE").unwrap(), NamespaceLevelKind::Scope).unwrap();
namespace.add_level(AmlName::from_str("\\_SB").unwrap(), NamespaceLevelKind::Scope).unwrap();
namespace.add_level(AmlName::from_str("\\_SI").unwrap(), NamespaceLevelKind::Scope).unwrap();
namespace.add_level(AmlName::from_str("\\_PR").unwrap(), NamespaceLevelKind::Scope).unwrap();
namespace.add_level(AmlName::from_str("\\_TZ").unwrap(), NamespaceLevelKind::Scope).unwrap();
namespace
.insert(
AmlName::from_str("\\_GL").unwrap(),
Object::Mutex { mutex: global_lock_mutex, sync_level: 0 }.wrap(),
)
.unwrap();
/*
* In the dark ages of ACPI 1.0, before `\_OSI`, `\_OS` was used to communicate to the firmware which OS
* was running. This was predictably not very good, and so was replaced in ACPI 3.0 with `_OSI`, which
* allows support for individual capabilities to be queried. `_OS` should not be used by modern firmwares;
* we follow the NT interpreter and ACPICA by calling ourselves `Microsoft Windows NT`.
*
* See https://www.kernel.org/doc/html/latest/firmware-guide/acpi/osi.html for more information.
*/
namespace
.insert(AmlName::from_str("\\_OS").unwrap(), Object::String("Microsoft Windows NT".to_string()).wrap())
.unwrap();
/*
* `\_OSI` was introduced by ACPI 3.0 to improve the situation created by `\_OS`. Unfortunately, exactly
* the same problem was immediately repeated by introducing capabilities reflecting that an ACPI
* implementation is exactly the same as a particular version of Windows' (e.g. firmwares will call
* `\_OSI("Windows 2001")`).
*
* We basically follow suit with whatever Linux does, as this will hopefully minimise breakage:
* - We always claim `Windows *` compatability
* - We answer 'yes' to `_OSI("Darwin")
* - We answer 'no' to `_OSI("Linux")`, and report that the tables are doing the wrong thing
*/
namespace
.insert(
AmlName::from_str("\\_OSI").unwrap(),
Object::native_method(1, |args| {
if args.len() != 1 {
return Err(AmlError::MethodArgCountIncorrect);
}
let Object::String(ref feature) = *args[0] else {
return Err(AmlError::ObjectNotOfExpectedType {
expected: ObjectType::String,
got: args[0].typ(),
});
};
let is_supported = match feature.as_str() {
"Windows 2000" => true, // 2000
"Windows 2001" => true, // XP
"Windows 2001 SP1" => true, // XP SP1
"Windows 2001 SP2" => true, // XP SP2
"Windows 2001.1" => true, // Server 2003
"Windows 2001.1 SP1" => true, // Server 2003 SP1
"Windows 2006" => true, // Vista
"Windows 2006 SP1" => true, // Vista SP1
"Windows 2006 SP2" => true, // Vista SP2
"Windows 2006.1" => true, // Server 2008
"Windows 2009" => true, // 7 and Server 2008 R2
"Windows 2012" => true, // 8 and Server 2012
"Windows 2013" => true, // 8.1 and Server 2012 R2
"Windows 2015" => true, // 10
"Windows 2016" => true, // 10 version 1607
"Windows 2017" => true, // 10 version 1703
"Windows 2017.2" => true, // 10 version 1709
"Windows 2018" => true, // 10 version 1803
"Windows 2018.2" => true, // 10 version 1809
"Windows 2019" => true, // 10 version 1903
"Windows 2020" => true, // 10 version 20H1
"Windows 2021" => true, // 11
"Windows 2022" => true, // 11 version 22H2
// TODO: Linux answers yes to this, NT answers no. Maybe make configurable
"Darwin" => false,
"Linux" => {
// TODO: should we allow users to specify that this should be true? Linux has a
// command line option for this.
warn!("ACPI evaluated `_OSI(\"Linux\")`. This is a bug. Reporting no support.");
false
}
"Extended Address Space Descriptor" => true,
"Module Device" => true,
"3.0 Thermal Model" => true,
"3.0 _SCP Extensions" => true,
"Processor Aggregator Device" => true,
_ => false,
};
Ok(Object::Integer(if is_supported { u64::MAX } else { 0 }).wrap())
})
.wrap(),
)
.unwrap();
/*
* `\_REV` evaluates to the version of the ACPI specification supported by this interpreter. Linux did this
* correctly until 2015, but firmwares misused this to detect Linux (as even modern versions of Windows
* return `2`), and so they switched to just returning `2` (as we'll also do). `_REV` should be considered
* useless and deprecated (this is mirrored in newer specs, which claim `2` means "ACPI 2 or greater").
*/
namespace.insert(AmlName::from_str("\\_REV").unwrap(), Object::Integer(2).wrap()).unwrap();
namespace
}
pub fn add_level(&mut self, path: AmlName, kind: NamespaceLevelKind) -> Result<(), AmlError> {
assert!(path.is_absolute());
let path = path.normalize()?;
// Don't try to recreate the root scope
if path != AmlName::root() {
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
/*
* If the level has already been added, we don't need to add it again. The parser can try to add it
* multiple times if the ASL contains multiple blocks that add to the same scope/device.
*/
level.children.entry(last_seg).or_insert_with(|| NamespaceLevel::new(kind));
}
Ok(())
}
pub fn remove_level(&mut self, path: AmlName) -> Result<(), AmlError> {
assert!(path.is_absolute());
let path = path.normalize()?;
// Don't try to remove the root scope
// TODO: we probably shouldn't be able to remove the pre-defined scopes either?
if path != AmlName::root() {
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
level.children.remove(&last_seg);
}
Ok(())
}
pub fn insert(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> {
assert!(path.is_absolute());
let path = path.normalize()?;
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
match level.values.insert(last_seg, (ObjectFlags::new(false), object)) {
None => Ok(()),
Some(_) => {
/*
* Real AML often has name collisions, and so we can't afford to be too strict
* about it. We do warn the user as it does have the potential to break stuff.
*/
trace!("AML name collision: {}. Replacing object.", path);
Ok(())
}
}
}
pub fn create_alias(&mut self, path: AmlName, object: WrappedObject) -> Result<(), AmlError> {
assert!(path.is_absolute());
let path = path.normalize()?;
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
match level.values.insert(last_seg, (ObjectFlags::new(true), object)) {
None => Ok(()),
Some(_) => Err(AmlError::NameCollision(path)),
}
}
pub fn get(&mut self, path: AmlName) -> Result<WrappedObject, AmlError> {
assert!(path.is_absolute());
let path = path.normalize()?;
let (level, last_seg) = self.get_level_for_path_mut(&path)?;
match level.values.get(&last_seg) {
Some((_, object)) => Ok(object.clone()),
None => Err(AmlError::ObjectDoesNotExist(path.clone())),
}
}
/// Search for an object at the given path of the namespace, applying the search rules described in §5.3 of the
/// ACPI specification, if they are applicable. Returns the resolved name, and the handle of the first valid
/// object, if found.
pub fn search(&self, path: &AmlName, starting_scope: &AmlName) -> Result<(AmlName, WrappedObject), AmlError> {
if path.search_rules_apply() {
/*
* If search rules apply, we need to recursively look through the namespace. If the
* given name does not occur in the current scope, we look at the parent scope, until
* we either find the name, or reach the root of the namespace.
*/
let mut scope = starting_scope.clone();
assert!(scope.is_absolute());
loop {
// Search for the name at this namespace level. If we find it, we're done.
let name = path.resolve(&scope)?;
match self.get_level_for_path(&name) {
Ok((level, last_seg)) => {
if let Some((_, object)) = level.values.get(&last_seg) {
return Ok((name, object.clone()));
}
}
Err(err) => return Err(err),
}
// If we don't find it, go up a level in the namespace and search for it there recursively
match scope.parent() {
Ok(parent) => scope = parent,
Err(AmlError::RootHasNoParent) => return Err(AmlError::ObjectDoesNotExist(path.clone())),
Err(err) => return Err(err),
}
}
} else {
// If search rules don't apply, simply resolve it against the starting scope
let name = path.resolve(starting_scope)?;
let (level, last_seg) = self.get_level_for_path(&path.resolve(starting_scope)?)?;
if let Some((_, object)) = level.values.get(&last_seg) {
Ok((name, object.clone()))
} else {
Err(AmlError::ObjectDoesNotExist(path.clone()))
}
}
}
pub fn search_for_level(&self, level_name: &AmlName, starting_scope: &AmlName) -> Result<AmlName, AmlError> {
if level_name.search_rules_apply() {
let mut scope = starting_scope.clone().normalize()?;
assert!(scope.is_absolute());
loop {
let name = level_name.resolve(&scope)?;
if let Ok((level, last_seg)) = self.get_level_for_path(&name)
&& level.children.contains_key(&last_seg)
{
return Ok(name);
}
// If we don't find it, move the scope up a level and search for it there recursively
match scope.parent() {
Ok(parent) => scope = parent,
Err(AmlError::RootHasNoParent) => return Err(AmlError::LevelDoesNotExist(level_name.clone())),
Err(err) => return Err(err),
}
}
} else {
Ok(level_name.clone())
}
}
/// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a
/// last segment to index into that level. This must not be called on `\\`.
fn get_level_for_path(&self, path: &AmlName) -> Result<(&NamespaceLevel, NameSeg), AmlError> {
assert_ne!(*path, AmlName::root());
let (last_seg, levels) = path.0[1..].split_last().unwrap();
let NameComponent::Segment(last_seg) = last_seg else {
panic!();
};
// TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error.
let mut traversed_path = AmlName::root();
let mut current_level = &self.root;
for level in levels {
traversed_path.0.push(*level);
let NameComponent::Segment(segment) = level else {
panic!();
};
current_level =
current_level.children.get(segment).ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?;
}
Ok((current_level, *last_seg))
}
/// Split an absolute path into a bunch of level segments (used to traverse the level data structure), and a
/// last segment to index into that level. This must not be called on `\\`.
fn get_level_for_path_mut(&mut self, path: &AmlName) -> Result<(&mut NamespaceLevel, NameSeg), AmlError> {
assert_ne!(*path, AmlName::root());
let (last_seg, levels) = path.0[1..].split_last().unwrap();
let NameComponent::Segment(last_seg) = last_seg else {
panic!();
};
// TODO: this helps with diagnostics, but requires a heap allocation just in case we need to error. We can
// improve this by changing the `levels` interation into an `enumerate()`, and then using the index to
// create the correct path on the error path
let mut traversed_path = AmlName::root();
let mut current_level = &mut self.root;
for level in levels {
traversed_path.0.push(*level);
let NameComponent::Segment(segment) = level else {
panic!();
};
current_level = current_level
.children
.get_mut(segment)
.ok_or(AmlError::LevelDoesNotExist(traversed_path.clone()))?;
}
Ok((current_level, *last_seg))
}
/// Traverse the namespace, calling `f` on each namespace level. `f` returns a `Result<bool, AmlError>` -
/// errors terminate the traversal and are propagated, and the `bool` on the successful path marks whether the
/// children of the level should also be traversed.
pub fn traverse<F>(&mut self, mut f: F) -> Result<(), AmlError>
where
F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
{
fn traverse_level<F>(level: &NamespaceLevel, scope: &AmlName, f: &mut F) -> Result<(), AmlError>
where
F: FnMut(&AmlName, &NamespaceLevel) -> Result<bool, AmlError>,
{
for (name, child) in level.children.iter() {
let name = AmlName::from_name_seg(*name).resolve(scope)?;
if f(&name, child)? {
traverse_level(child, &name, f)?;
}
}
Ok(())
}
if f(&AmlName::root(), &self.root)? {
traverse_level(&self.root, &AmlName::root(), &mut f)?;
}
Ok(())
}
}
impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
const STEM: &str = "";
const BRANCH: &str = "├── ";
const END: &str = "└── ";
fn print_level(f: &mut fmt::Formatter<'_>, level: &NamespaceLevel, indent_stack: String) -> fmt::Result {
for (i, (name, (flags, object))) in level.values.iter().enumerate() {
let end = (i == level.values.len() - 1)
&& level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).count() == 0;
writeln!(
f,
"{}{}{}: {}{}",
&indent_stack,
if end { END } else { BRANCH },
name.as_str(),
if flags.is_alias() { "[A] " } else { "" },
**object
)?;
// If the object has a corresponding scope, print it here
if let Some(child_level) = level.children.get(name) {
print_level(
f,
child_level,
if end { indent_stack.clone() + " " } else { indent_stack.clone() + STEM },
)?;
}
}
let remaining_scopes: Vec<_> =
level.children.iter().filter(|(_, l)| l.kind == NamespaceLevelKind::Scope).collect();
for (i, (name, sub_level)) in remaining_scopes.iter().enumerate() {
let end = i == remaining_scopes.len() - 1;
writeln!(f, "{}{}{}:", &indent_stack, if end { END } else { BRANCH }, name.as_str())?;
print_level(f, sub_level, indent_stack.clone() + STEM)?;
}
Ok(())
}
writeln!(f, "\n \\:")?;
print_level(f, &self.root, String::from(" "))
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum NamespaceLevelKind {
Scope,
Device,
Processor,
PowerResource,
ThermalZone,
MethodLocals,
}
#[derive(Clone)]
pub struct NamespaceLevel {
pub kind: NamespaceLevelKind,
pub values: BTreeMap<NameSeg, (ObjectFlags, WrappedObject)>,
pub children: BTreeMap<NameSeg, NamespaceLevel>,
}
#[derive(Clone, Copy, Debug)]
pub struct ObjectFlags(u8);
impl ObjectFlags {
pub fn new(is_alias: bool) -> ObjectFlags {
let mut flags = 0;
flags.set_bit(0, is_alias);
ObjectFlags(flags)
}
pub fn is_alias(&self) -> bool {
self.0.get_bit(0)
}
}
impl NamespaceLevel {
pub fn new(kind: NamespaceLevelKind) -> NamespaceLevel {
NamespaceLevel { kind, values: BTreeMap::new(), children: BTreeMap::new() }
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct AmlName(Vec<NameComponent>);
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum NameComponent {
Root,
Prefix,
Segment(NameSeg),
}
impl AmlName {
pub fn root() -> AmlName {
AmlName(vec![NameComponent::Root])
}
pub fn from_name_seg(seg: NameSeg) -> AmlName {
AmlName(vec![NameComponent::Segment(seg)])
}
pub fn from_components(components: Vec<NameComponent>) -> AmlName {
AmlName(components)
}
pub fn as_string(&self) -> String {
self.0
.iter()
.fold(String::new(), |name, component| match component {
NameComponent::Root => name + "\\",
NameComponent::Prefix => name + "^",
NameComponent::Segment(seg) => name + seg.as_str() + ".",
})
.trim_end_matches('.')
.to_string()
}
/// An AML path is normal if it does not contain any prefix elements ("^" characters, when
/// expressed as a string).
pub fn is_normal(&self) -> bool {
!self.0.contains(&NameComponent::Prefix)
}
pub fn is_absolute(&self) -> bool {
self.0.first() == Some(&NameComponent::Root)
}
/// Special rules apply when searching for certain paths (specifically, those that are made up
/// of a single name segment). Returns `true` if those rules apply.
pub fn search_rules_apply(&self) -> bool {
if self.0.len() != 1 {
return false;
}
matches!(self.0[0], NameComponent::Segment(_))
}
/// Normalize an AML path, resolving prefix chars. Returns `AmlError::InvalidNormalizedName` if the path
/// normalizes to an invalid path (e.g. `\^_FOO`)
pub fn normalize(self) -> Result<AmlName, AmlError> {
/*
* If the path is already normal, just return it as-is. This avoids an unneccessary heap allocation and
* free.
*/
if self.is_normal() {
return Ok(self);
}
Ok(AmlName(self.0.iter().try_fold(Vec::new(), |mut name, &component| match component {
seg @ NameComponent::Segment(_) => {
name.push(seg);
Ok(name)
}
NameComponent::Root => {
name.push(NameComponent::Root);
Ok(name)
}
NameComponent::Prefix => {
if let Some(NameComponent::Segment(_)) = name.iter().last() {
name.pop().unwrap();
Ok(name)
} else {
Err(AmlError::InvalidNormalizedName(self.clone()))
}
}
})?))
}
/// Get the parent of this `AmlName`. For example, the parent of `\_SB.PCI0._PRT` is `\_SB.PCI0`. The root
/// path has no parent, and so returns `None`.
pub fn parent(&self) -> Result<AmlName, AmlError> {
// Firstly, normalize the path so we don't have to deal with prefix chars
let mut normalized_self = self.clone().normalize()?;
match normalized_self.0.last() {
None | Some(NameComponent::Root) => Err(AmlError::RootHasNoParent),
Some(NameComponent::Segment(_)) => {
normalized_self.0.pop();
Ok(normalized_self)
}
Some(NameComponent::Prefix) => unreachable!(), // Prefix chars are removed by normalization
}
}
/// Resolve this path against a given scope, making it absolute. If the path is absolute, it is
/// returned directly. The path is also normalized.
pub fn resolve(&self, scope: &AmlName) -> Result<AmlName, AmlError> {
assert!(scope.is_absolute());
if self.is_absolute() {
return Ok(self.clone());
}
let mut resolved_path = scope.clone();
resolved_path.0.extend_from_slice(&(self.0));
resolved_path.normalize()
}
}
impl FromStr for AmlName {
type Err = AmlError;
fn from_str(mut string: &str) -> Result<Self, Self::Err> {
if string.is_empty() {
return Err(AmlError::EmptyNamesAreInvalid);
}
let mut components = Vec::new();
// If it starts with a \, make it an absolute name
if string.starts_with('\\') {
components.push(NameComponent::Root);
string = &string[1..];
}
if !string.is_empty() {
// Divide the rest of it into segments, and parse those
for mut part in string.split('.') {
// Handle prefix chars
while part.starts_with('^') {
components.push(NameComponent::Prefix);
part = &part[1..];
}
components.push(NameComponent::Segment(NameSeg::from_str(part)?));
}
}
Ok(Self(components))
}
}
impl fmt::Display for AmlName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.as_string())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NameSeg(pub(crate) [u8; 4]);
impl NameSeg {
pub fn from_bytes(bytes: [u8; 4]) -> Result<NameSeg, AmlError> {
if !is_lead_name_char(bytes[0]) {
return Err(AmlError::InvalidNameSeg(bytes));
}
if !is_name_char(bytes[1]) {
return Err(AmlError::InvalidNameSeg(bytes));
}
if !is_name_char(bytes[2]) {
return Err(AmlError::InvalidNameSeg(bytes));
}
if !is_name_char(bytes[3]) {
return Err(AmlError::InvalidNameSeg(bytes));
}
Ok(NameSeg(bytes))
}
pub fn as_str(&self) -> &str {
// We should only construct valid ASCII name segments
unsafe { str::from_utf8_unchecked(&self.0) }
}
}
impl FromStr for NameSeg {
type Err = AmlError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Each NameSeg can only have four chars, and must have at least one
if s.is_empty() || s.len() > 4 {
return Err(AmlError::InvalidNameSeg([0xff, 0xff, 0xff, 0xff]));
}
// We pre-fill the array with '_', so it will already be correct if the length is < 4
let mut seg = [b'_'; 4];
let bytes = s.as_bytes();
// Manually do the first one, because we have to check it's a LeadNameChar
if !is_lead_name_char(bytes[0]) {
return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]]));
}
seg[0] = bytes[0];
// Copy the rest of the chars, checking that they're NameChars
for i in 1..bytes.len() {
if !is_name_char(bytes[i]) {
return Err(AmlError::InvalidNameSeg([bytes[0], bytes[1], bytes[2], bytes[3]]));
}
seg[i] = bytes[i];
}
Ok(NameSeg(seg))
}
}
pub fn is_lead_name_char(c: u8) -> bool {
c.is_ascii_uppercase() || c == b'_'
}
pub fn is_name_char(c: u8) -> bool {
is_lead_name_char(c) || c.is_ascii_digit()
}
impl fmt::Debug for NameSeg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.as_str())
}
}
-558
View File
@@ -1,558 +0,0 @@
use crate::aml::{AmlError, Handle, Operation, op_region::OpRegion};
use alloc::{
borrow::Cow,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use bit_field::BitField;
use core::{cell::UnsafeCell, fmt, ops, sync::atomic::AtomicU64};
type NativeMethod = dyn Fn(&[WrappedObject]) -> Result<WrappedObject, AmlError>;
#[derive(Clone)]
pub enum Object {
Uninitialized,
Buffer(Vec<u8>),
BufferField { buffer: WrappedObject, offset: usize, length: usize },
Device,
Event(Arc<AtomicU64>),
FieldUnit(FieldUnit),
Integer(u64),
Method { code: Vec<u8>, flags: MethodFlags },
NativeMethod { f: Arc<NativeMethod>, flags: MethodFlags },
Mutex { mutex: Handle, sync_level: u8 },
Reference { kind: ReferenceKind, inner: WrappedObject },
OpRegion(OpRegion),
Package(Vec<WrappedObject>),
PowerResource { system_level: u8, resource_order: u16 },
Processor { proc_id: u8, pblk_address: u32, pblk_length: u8 },
RawDataBuffer,
String(String),
ThermalZone,
Debug,
}
impl Object {
pub fn native_method<F>(num_args: u8, f: F) -> Object
where
F: Fn(&[WrappedObject]) -> Result<WrappedObject, AmlError> + 'static,
{
let mut flags = 0;
flags.set_bits(0..3, num_args);
Object::NativeMethod { f: Arc::new(f), flags: MethodFlags(flags) }
}
}
impl fmt::Display for Object {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Object::Uninitialized => write!(f, "[Uninitialized]"),
Object::Buffer(bytes) => write!(f, "Buffer({bytes:x?})"),
Object::BufferField { offset, length, .. } => {
write!(f, "BufferField {{ offset: {offset}, length: {length} }}")
}
Object::Device => write!(f, "Device"),
Object::Event(counter) => write!(f, "Event({counter:?})"),
// TODO: include fields
Object::FieldUnit(_) => write!(f, "FieldUnit"),
Object::Integer(value) => write!(f, "Integer({value})"),
// TODO: decode flags here
Object::Method { .. } => write!(f, "Method"),
Object::NativeMethod { .. } => write!(f, "NativeMethod"),
Object::Mutex { .. } => write!(f, "Mutex"),
Object::Reference { kind, inner } => write!(f, "Reference({:?} -> {})", kind, **inner),
Object::OpRegion(region) => write!(f, "{region:?}"),
Object::Package(elements) => {
write!(f, "Package {{ ")?;
for (i, element) in elements.iter().enumerate() {
if i == elements.len() - 1 {
write!(f, "{}", **element)?;
} else {
write!(f, "{}, ", **element)?;
}
}
write!(f, " }}")?;
Ok(())
}
// TODO: include fields
Object::PowerResource { .. } => write!(f, "PowerResource"),
// TODO: include fields
Object::Processor { .. } => write!(f, "Processor"),
Object::RawDataBuffer => write!(f, "RawDataBuffer"),
Object::String(value) => write!(f, "String({value:?})"),
Object::ThermalZone => write!(f, "ThermalZone"),
Object::Debug => write!(f, "Debug"),
}
}
}
/// `ObjectToken` is used to mediate mutable access to objects from a [`WrappedObject`]. It must be
/// acquired by locking the single token provided by [`super::Interpreter`].
#[non_exhaustive]
pub struct ObjectToken {
_dont_construct_me: (),
}
impl ObjectToken {
/// Create an [`ObjectToken`]. This should **only** be done **once** by the main interpreter,
/// as contructing your own token allows invalid mutable access to objects.
pub(super) unsafe fn create_interpreter_token() -> ObjectToken {
ObjectToken { _dont_construct_me: () }
}
}
#[derive(Clone, Debug)]
pub struct WrappedObject(Arc<UnsafeCell<Object>>);
impl WrappedObject {
pub fn new(object: Object) -> WrappedObject {
#[allow(clippy::arc_with_non_send_sync)]
WrappedObject(Arc::new(UnsafeCell::new(object)))
}
/// Gain a mutable reference to an [`Object`] from this [`WrappedObject`].
///
/// # Safety
/// This requires an [`ObjectToken`] which is protected by a lock on [`super::Interpreter`],
/// which prevents mutable access to objects from multiple contexts. It does not, however,
/// prevent the same object, referenced from multiple [`WrappedObject`]s, having multiple
/// mutable (and therefore aliasing) references being made to it, and therefore care must be
/// taken in the interpreter to prevent this.
pub unsafe fn gain_mut<'r, 'a, 't>(&'a self, _token: &'t ObjectToken) -> &'r mut Object
where
't: 'r,
'a: 'r,
{
unsafe { &mut *(self.0.get()) }
}
pub fn unwrap_reference(self) -> WrappedObject {
let mut object = self;
loop {
if let Object::Reference { ref inner, .. } = *object {
object = inner.clone();
} else {
return object.clone();
}
}
}
/// Unwraps 'transparent' references (e.g. locals, arguments, and internal usage of reference-type objects), but maintain 'real'
/// references deliberately created by AML.
pub fn unwrap_transparent_reference(self) -> WrappedObject {
let mut object = self;
loop {
if let Object::Reference { kind, ref inner } = *object
&& (kind == ReferenceKind::Local || kind == ReferenceKind::Arg || kind == ReferenceKind::Named)
{
object = inner.clone();
} else {
return object.clone();
}
}
}
}
impl ops::Deref for WrappedObject {
type Target = Object;
fn deref(&self) -> &Self::Target {
/*
* SAFETY: elided lifetime ensures reference cannot outlive at least one reference-counted
* instance of the object. `WrappedObject::gain_mut` is unsafe, and so it is the user's
* responsibility to ensure shared references from `Deref` do not co-exist with an
* exclusive reference.
*/
unsafe { &*self.0.get() }
}
}
impl fmt::Display for WrappedObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Wrapped({})", unsafe { &*self.0.get() })
}
}
impl Object {
pub fn wrap(self) -> WrappedObject {
WrappedObject::new(self)
}
pub fn as_integer(&self) -> Result<u64, AmlError> {
if let Object::Integer(value) = self {
Ok(*value)
} else {
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Integer, got: self.typ() })
}
}
pub fn as_string(&self) -> Result<Cow<'_, str>, AmlError> {
if let Object::String(value) = self {
Ok(Cow::from(value))
} else {
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::String, got: self.typ() })
}
}
pub fn as_buffer(&self) -> Result<&[u8], AmlError> {
if let Object::Buffer(bytes) = self {
Ok(bytes)
} else {
Err(AmlError::ObjectNotOfExpectedType { expected: ObjectType::Buffer, got: self.typ() })
}
}
pub fn to_integer(&self, allowed_bytes: usize) -> Result<u64, AmlError> {
match self {
Object::Integer(value) => Ok(*value),
Object::Buffer(value) => {
let length = usize::min(value.len(), allowed_bytes);
let mut bytes = [0u8; 8];
bytes[0..length].copy_from_slice(&value[0..length]);
Ok(u64::from_le_bytes(bytes))
}
// TODO: how should we handle invalid inputs? What does NT do here?
Object::String(value) => Ok(value.parse::<u64>().unwrap_or(0)),
_ => Ok(0),
}
}
pub fn to_buffer(&self, allowed_bytes: usize) -> Result<Vec<u8>, AmlError> {
match self {
Object::Buffer(bytes) => Ok(bytes.clone()),
Object::Integer(value) => match allowed_bytes {
4 => Ok((*value as u32).to_le_bytes().to_vec()),
8 => Ok(value.to_le_bytes().to_vec()),
_ => panic!(),
},
Object::String(value) => Ok(value.as_bytes().to_vec()),
_ => Err(AmlError::InvalidOperationOnObject { op: Operation::ConvertToBuffer, typ: self.typ() }),
}
}
pub fn read_buffer_field(&self, integer_size: usize) -> Result<Object, AmlError> {
if let Self::BufferField { buffer, offset, length } = self {
let buffer = match **buffer {
Object::Buffer(ref buffer) => buffer.as_slice(),
Object::String(ref string) => string.as_bytes(),
_ => panic!(),
};
if *length <= integer_size {
let mut dst = [0u8; 8];
copy_bits(buffer, *offset, &mut dst, 0, *length);
Ok(Object::Integer(u64::from_le_bytes(dst)))
} else {
let mut dst = alloc::vec![0u8; length.div_ceil(8)];
copy_bits(buffer, *offset, &mut dst, 0, *length);
Ok(Object::Buffer(dst))
}
} else {
Err(AmlError::InvalidOperationOnObject { op: Operation::ReadBufferField, typ: self.typ() })
}
}
pub fn write_buffer_field(&mut self, value: &[u8], token: &ObjectToken) -> Result<(), AmlError> {
// TODO: bounds check the buffer first to avoid panicking
if let Self::BufferField { buffer, offset, length } = self {
let buffer = match unsafe { buffer.gain_mut(token) } {
Object::Buffer(buffer) => buffer.as_mut_slice(),
// XXX: this unfortunately requires us to trust AML to keep the string as valid
// UTF8... maybe there is a better way?
Object::String(string) => unsafe { string.as_bytes_mut() },
_ => panic!(),
};
copy_bits(value, 0, buffer, *offset, *length);
Ok(())
} else {
Err(AmlError::InvalidOperationOnObject { op: Operation::WriteBufferField, typ: self.typ() })
}
}
/// Replace this object's contents with that of a `new` object, applying implicit casting rules
/// as needed. This follows the NT interpreter's creative interpretation of implicit casts, which is
/// effectively a byte-wise transmutation.
pub fn replace_with_implicit_casting(&mut self, new: Object) -> Result<(), AmlError> {
let new_bytes = match new {
Object::Integer(value) => &value.to_le_bytes(),
Object::String(ref value) => value.as_bytes(),
Object::Buffer(ref value) => &value.clone(),
_ => return Err(AmlError::InvalidImplicitCast { from: self.typ(), to: new.typ() }),
};
match self {
Object::Integer(value) => {
let bytes_to_copy = core::cmp::min(new_bytes.len(), 8);
let mut bytes = [0u8; 8];
bytes[0..bytes_to_copy].copy_from_slice(&new_bytes[0..bytes_to_copy]);
*value = u64::from_le_bytes(bytes);
}
Object::String(value) => {
*value = String::from_utf8_lossy(&new_bytes).to_string();
}
Object::Buffer(value) => {
*value = new_bytes.to_vec();
}
_ => return Err(AmlError::InvalidImplicitCast { from: self.typ(), to: new.typ() }),
}
Ok(())
}
/// Returns the `ObjectType` of this object. Returns the type of the referenced object in the
/// case of `Object::Reference`.
pub fn typ(&self) -> ObjectType {
match self {
Object::Uninitialized => ObjectType::Uninitialized,
Object::Buffer(_) => ObjectType::Buffer,
Object::BufferField { .. } => ObjectType::BufferField,
Object::Device => ObjectType::Device,
Object::Event(_) => ObjectType::Event,
Object::FieldUnit(_) => ObjectType::FieldUnit,
Object::Integer(_) => ObjectType::Integer,
Object::Method { .. } => ObjectType::Method,
Object::NativeMethod { .. } => ObjectType::Method,
Object::Mutex { .. } => ObjectType::Mutex,
// Object::Reference { inner, .. } => inner.typ(),
Object::Reference { .. } => ObjectType::Reference, // TODO: maybe this should
// differentiate internal/real
// references?
Object::OpRegion(_) => ObjectType::OpRegion,
Object::Package(_) => ObjectType::Package,
Object::PowerResource { .. } => ObjectType::PowerResource,
Object::Processor { .. } => ObjectType::Processor,
Object::RawDataBuffer => ObjectType::RawDataBuffer,
Object::String(_) => ObjectType::String,
Object::ThermalZone => ObjectType::ThermalZone,
Object::Debug => ObjectType::Debug,
}
}
}
#[derive(Clone, Debug)]
pub struct FieldUnit {
pub kind: FieldUnitKind,
pub flags: FieldFlags,
pub bit_index: usize,
pub bit_length: usize,
}
#[derive(Clone, Debug)]
pub enum FieldUnitKind {
Normal { region: WrappedObject },
Bank { region: WrappedObject, bank: WrappedObject, bank_value: u64 },
Index { index: WrappedObject, data: WrappedObject },
}
#[derive(Clone, Copy, Debug)]
pub struct FieldFlags(pub u8);
#[derive(Clone, Copy, Debug)]
pub enum FieldAccessType {
Any,
Byte,
Word,
DWord,
QWord,
Buffer,
}
#[derive(Clone, Copy, Debug)]
pub enum FieldUpdateRule {
Preserve,
WriteAsOnes,
WriteAsZeros,
}
impl FieldFlags {
pub fn access_type(&self) -> Result<FieldAccessType, AmlError> {
match self.0.get_bits(0..4) {
0 => Ok(FieldAccessType::Any),
1 => Ok(FieldAccessType::Byte),
2 => Ok(FieldAccessType::Word),
3 => Ok(FieldAccessType::DWord),
4 => Ok(FieldAccessType::QWord),
5 => Ok(FieldAccessType::Buffer),
_ => Err(AmlError::InvalidFieldFlags),
}
}
pub fn access_type_bytes(&self) -> Result<usize, AmlError> {
match self.access_type()? {
FieldAccessType::Any => {
// TODO: given more info about the field, we might be able to make a more efficient
// read, since all are valid in this case
Ok(1)
}
FieldAccessType::Byte | FieldAccessType::Buffer => Ok(1),
FieldAccessType::Word => Ok(2),
FieldAccessType::DWord => Ok(4),
FieldAccessType::QWord => Ok(8),
}
}
pub fn lock_rule(&self) -> bool {
self.0.get_bit(4)
}
pub fn update_rule(&self) -> FieldUpdateRule {
match self.0.get_bits(5..7) {
0 => FieldUpdateRule::Preserve,
1 => FieldUpdateRule::WriteAsOnes,
2 => FieldUpdateRule::WriteAsZeros,
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct MethodFlags(pub u8);
impl MethodFlags {
pub fn arg_count(&self) -> usize {
self.0.get_bits(0..3) as usize
}
pub fn serialize(&self) -> bool {
self.0.get_bit(3)
}
pub fn sync_level(&self) -> u8 {
self.0.get_bits(4..8)
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ReferenceKind {
Named,
RefOf,
Local,
Arg,
Index,
Unresolved,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ObjectType {
Uninitialized,
Buffer,
BufferField,
Device,
Event,
FieldUnit,
Integer,
Method,
Mutex,
Reference,
OpRegion,
Package,
PowerResource,
Processor,
RawDataBuffer,
String,
ThermalZone,
Debug,
}
/// Helper type for decoding the result of `_STA` objects.
pub struct DeviceStatus(pub u64);
impl DeviceStatus {
pub fn present(&self) -> bool {
self.0.get_bit(0)
}
pub fn enabled(&self) -> bool {
self.0.get_bit(1)
}
pub fn show_in_ui(&self) -> bool {
self.0.get_bit(2)
}
pub fn functioning(&self) -> bool {
self.0.get_bit(3)
}
/// This flag is only used for Battery devices (PNP0C0A), and indicates if the battery is
/// present.
pub fn battery_present(&self) -> bool {
self.0.get_bit(4)
}
}
/// Copy an arbitrary bit range of `src` to an arbitrary bit range of `dst`. This is used for
/// buffer fields. Data is zero-extended if `src` does not cover `length` bits, matching the
/// expected behaviour for buffer fields.
pub(crate) fn copy_bits(
src: &[u8],
mut src_index: usize,
dst: &mut [u8],
mut dst_index: usize,
mut length: usize,
) {
while length > 0 {
let src_shift = src_index & 7;
let mut src_bits = src.get(src_index / 8).unwrap_or(&0x00) >> src_shift;
if src_shift > 0 && length > (8 - src_shift) {
src_bits |= src.get(src_index / 8 + 1).unwrap_or(&0x00) << (8 - src_shift);
}
if length < 8 {
src_bits &= (1 << length) - 1;
}
let dst_shift = dst_index & 7;
let mut dst_mask: u16 = if length < 8 { ((1 << length) - 1) as u16 } else { 0xff_u16 } << dst_shift;
dst[dst_index / 8] =
(dst[dst_index / 8] & !(dst_mask as u8)) | ((src_bits << dst_shift) & (dst_mask as u8));
if dst_shift > 0 && length > (8 - dst_shift) {
dst_mask >>= 8;
dst[dst_index / 8 + 1] &= !(dst_mask as u8);
dst[dst_index / 8 + 1] |= (src_bits >> (8 - dst_shift)) & (dst_mask as u8);
}
if length < 8 {
length = 0;
} else {
length -= 8;
src_index += 8;
dst_index += 8;
}
}
}
#[inline]
pub(crate) fn align_down(value: usize, align: usize) -> usize {
assert!(align == 0 || align.is_power_of_two());
if align == 0 {
value
} else {
/*
* Alignment must be a power of two.
*
* E.g.
* align = 0b00001000
* align-1 = 0b00000111
* !(align-1) = 0b11111000
* ^^^ Masks the value to the one below it with the correct align
*/
value & !(align - 1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_copy_bits() {
let src = [0b1011_1111, 0b1111_0111, 0b1111_1111, 0b1111_1111, 0b1111_1111];
let mut dst = [0b1110_0001, 0, 0, 0, 0];
copy_bits(&src, 0, &mut dst, 2, 15);
assert_eq!(dst, [0b1111_1101, 0b1101_1110, 0b0000_0001, 0b0000_0000, 0b0000_0000]);
}
}
-75
View File
@@ -1,75 +0,0 @@
use crate::aml::{AmlError, namespace::AmlName};
#[derive(Clone, Debug)]
pub struct OpRegion {
pub space: RegionSpace,
pub base: u64,
pub length: u64,
pub parent_device_path: AmlName,
}
pub trait RegionHandler {
fn read_u8(&self, region: &OpRegion, offset: usize) -> Result<u8, AmlError>;
fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, AmlError>;
fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, AmlError>;
fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, AmlError>;
fn write_u8(&self, region: &OpRegion, offset: usize, value: u8) -> Result<(), AmlError>;
fn write_u16(&self, region: &OpRegion, offset: usize, value: u16) -> Result<(), AmlError>;
fn write_u32(&self, region: &OpRegion, offset: usize, value: u32) -> Result<(), AmlError>;
fn write_u64(&self, region: &OpRegion, offset: usize, value: u64) -> Result<(), AmlError>;
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum RegionSpace {
SystemMemory,
SystemIO,
PciConfig,
EmbeddedControl,
SmBus,
SystemCmos,
PciBarTarget,
Ipmi,
GeneralPurposeIo,
GenericSerialBus,
Pcc,
Oem(u8),
}
impl From<u8> for RegionSpace {
fn from(value: u8) -> Self {
match value {
0 => RegionSpace::SystemMemory,
1 => RegionSpace::SystemIO,
2 => RegionSpace::PciConfig,
3 => RegionSpace::EmbeddedControl,
4 => RegionSpace::SmBus,
5 => RegionSpace::SystemCmos,
6 => RegionSpace::PciBarTarget,
7 => RegionSpace::Ipmi,
8 => RegionSpace::GeneralPurposeIo,
9 => RegionSpace::GenericSerialBus,
10 => RegionSpace::Pcc,
_ => RegionSpace::Oem(value),
}
}
}
impl From<RegionSpace> for u8 {
fn from(space: RegionSpace) -> u8 {
match space {
RegionSpace::SystemMemory => 0,
RegionSpace::SystemIO => 1,
RegionSpace::PciConfig => 2,
RegionSpace::EmbeddedControl => 3,
RegionSpace::SmBus => 4,
RegionSpace::SystemCmos => 5,
RegionSpace::PciBarTarget => 6,
RegionSpace::Ipmi => 7,
RegionSpace::GeneralPurposeIo => 8,
RegionSpace::GenericSerialBus => 9,
RegionSpace::Pcc => 10,
RegionSpace::Oem(value) => value,
}
}
}
-189
View File
@@ -1,189 +0,0 @@
use crate::aml::{
AmlError,
Handler,
Interpreter,
Operation,
namespace::AmlName,
object::Object,
resource::{self, InterruptPolarity, InterruptTrigger, Resource},
};
use alloc::{vec, vec::Vec};
use bit_field::BitField;
use core::str::FromStr;
pub use crate::aml::resource::IrqDescriptor;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Pin {
IntA,
IntB,
IntC,
IntD,
}
#[derive(Debug)]
pub enum PciRouteType {
/// The interrupt is hard-coded to a specific GSI
Gsi(u32),
/// The interrupt is linked to a link object. This object will have `_PRS`, `_CRS` fields and a `_SRS` method
/// that can be used to allocate the interrupt. Note that some platforms (e.g. QEMU's q35 chipset) use link
/// objects but do not support changing the interrupt that it's linked to (i.e. `_SRS` doesn't do anything).
/*
* The actual object itself will just be a `Device`, and we need paths to its children objects to do
* anything useful, so we just store the resolved name here.
*/
LinkObject(AmlName),
}
#[derive(Debug)]
pub struct PciRoute {
device: u16,
function: u16,
pin: Pin,
route_type: PciRouteType,
}
/// A `PciRoutingTable` is used to interpret the data in a `_PRT` object, which provides a mapping
/// from PCI interrupt pins to the inputs of the interrupt controller. One of these objects must be
/// present under each PCI root bridge, and consists of a package of packages, each of which describes the
/// mapping of a single PCI interrupt pin.
#[derive(Debug)]
pub struct PciRoutingTable {
entries: Vec<PciRoute>,
}
impl PciRoutingTable {
/// Construct a `PciRoutingTable` from a path to a `_PRT` object. Returns
/// `AmlError::InvalidOperationOnObject` if the value passed is not a package, or if any of the
/// values within it are not packages. Returns the various `AmlError::Prt*` errors if the
/// internal structure of the entries is invalid.
pub fn from_prt_path(
prt_path: AmlName,
interpreter: &Interpreter<impl Handler>,
) -> Result<PciRoutingTable, AmlError> {
let mut entries = Vec::new();
let prt = interpreter.evaluate(prt_path.clone(), vec![])?;
if let Object::Package(ref inner_values) = *prt {
for value in inner_values {
if let Object::Package(ref pin_package) = **value {
/*
* Each inner package has the following structure:
* | Field | Type | Description |
* | -----------|-----------|-----------------------------------------------------------|
* | Address | Dword | Address of the device. Same format as _ADR objects (high |
* | | | word = #device, low word = #function) |
* | -----------|-----------|-----------------------------------------------------------|
* | Pin | Byte | The PCI pin (0 = INTA, 1 = INTB, 2 = INTC, 3 = INTD) |
* | -----------|-----------|-----------------------------------------------------------|
* | Source | Byte or | Name of the device that allocates the interrupt to which |
* | | NamePath | the above pin is connected. Can be fully qualified, |
* | | | relative, or a simple NameSeg that utilizes namespace |
* | | | search rules. Instead, if this is a byte value of 0, the |
* | | | interrupt is allocated out of the GSI pool, and Source |
* | | | Index should be utilised. |
* | -----------|-----------|-----------------------------------------------------------|
* | Source | Dword | Index that indicates which resource descriptor in the |
* | Index | | resource template of the device pointed to in the Source |
* | | | field this interrupt is allocated from. If the Source |
* | | | is zero, then this field is the GSI number to which the |
* | | | pin is connected. |
* | -----------|-----------|-----------------------------------------------------------|
*/
let Object::Integer(address) = *pin_package[0] else {
return Err(AmlError::PrtInvalidAddress);
};
let device = address.get_bits(16..32).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
let function = address.get_bits(0..16).try_into().map_err(|_| AmlError::PrtInvalidAddress)?;
let pin = match *pin_package[1] {
Object::Integer(0) => Pin::IntA,
Object::Integer(1) => Pin::IntB,
Object::Integer(2) => Pin::IntC,
Object::Integer(3) => Pin::IntD,
_ => return Err(AmlError::PrtInvalidPin),
};
match *pin_package[2] {
Object::Integer(0) => {
/*
* The Source Index field contains the GSI number that this interrupt is attached
* to.
*/
let Object::Integer(gsi) = *pin_package[3] else {
return Err(AmlError::PrtInvalidGsi);
};
entries.push(PciRoute {
device,
function,
pin,
route_type: PciRouteType::Gsi(gsi as u32),
});
}
Object::String(ref name) => {
let link_object_name = interpreter
.namespace
.lock()
.search_for_level(&AmlName::from_str(name)?, &prt_path)?;
entries.push(PciRoute {
device,
function,
pin,
route_type: PciRouteType::LinkObject(link_object_name),
});
}
_ => return Err(AmlError::PrtInvalidSource),
}
} else {
return Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: value.typ() });
}
}
Ok(PciRoutingTable { entries })
} else {
Err(AmlError::InvalidOperationOnObject { op: Operation::DecodePrt, typ: prt.typ() })
}
}
/// Get the interrupt input that a given PCI interrupt pin is wired to. Returns `AmlError::PrtNoEntry` if the
/// PRT doesn't contain an entry for the given address + pin.
pub fn route(
&self,
device: u16,
function: u16,
pin: Pin,
interpreter: &Interpreter<impl Handler>,
) -> Result<IrqDescriptor, AmlError> {
let entry = self
.entries
.iter()
.find(|entry| {
entry.device == device
&& (entry.function == 0xffff || entry.function == function)
&& entry.pin == pin
})
.ok_or(AmlError::PrtNoEntry)?;
match entry.route_type {
PciRouteType::Gsi(gsi) => Ok(IrqDescriptor {
is_consumer: true,
trigger: InterruptTrigger::Level,
polarity: InterruptPolarity::ActiveLow,
is_shared: true,
is_wake_capable: false,
irq: gsi,
}),
PciRouteType::LinkObject(ref name) => {
let path = AmlName::from_str("_CRS").unwrap().resolve(name)?;
let link_crs = interpreter.evaluate(path, vec![])?;
let resources = resource::resource_descriptor_list(link_crs)?;
match resources.as_slice() {
[Resource::Irq(descriptor)] => Ok(descriptor.clone()),
_ => Err(AmlError::UnexpectedResourceType),
}
}
}
}
}
File diff suppressed because it is too large Load Diff
-512
View File
@@ -1,512 +0,0 @@
//! `acpi` is a Rust library for interacting with the Advanced Configuration and Power Interface, a
//! complex framework for power management and device discovery and configuration. ACPI is used on
//! modern x64, as well as some ARM and RISC-V platforms. An operating system needs to interact with
//! ACPI to correctly set up a platform's interrupt controllers, perform power management, and fully
//! support many other platform capabilities.
//!
//! This crate provides a limited API that can be used without an allocator, for example for use
//! from a bootloader. This API will allow you to search for the RSDP, enumerate over the available
//! tables, and interact with the tables using their raw structures. All other functionality is
//! behind an `alloc` feature (enabled by default) and requires an allocator.
//!
//! With an allocator, this crate also provides higher-level interfaces to the static tables, as
//! well as a dynamic interpreter for AML - the bytecode format encoded in the DSDT and SSDT
//! tables.
//!
//! ### Usage
//! To use the library, you will need to provide an implementation of the [`Handler`] trait,
//! which allows the library to make requests such as mapping a particular region of physical
//! memory into the virtual address space.
//!
//! Next, you'll need to get the physical address of either the RSDP, or the RSDT/XSDT. The method
//! for doing this depends on the platform you're running on and how you were booted. If you know
//! the system was booted via the BIOS, you can use [`Rsdp::search_for_on_bios`]. UEFI provides a
//! separate mechanism for getting the address of the RSDP.
//!
//! You then need to construct an instance of [`AcpiTables`], which can be done in a few ways
//! depending on how much information you have:
//! * Use [`AcpiTables::from_rsdp`] if you have the physical address of the RSDP
//! * Use [`AcpiTables::from_rsdt`] if you have the physical address of the RSDT/XSDT
//!
//! Once you have an [`AcpiTables`], you can search for relevant tables, or use the higher-level
//! interfaces, such as [`PowerProfile`], or [`HpetInfo`].
//!
//! If you have the `aml` feature enabled then you can construct an
//! [AML interpreter](`crate::aml::Interpreter`) by first constructing an
//! [`AcpiPlatform`](platform::AcpiPlatform) and then the interpreter:
//!
//! ```ignore,rust
//! let mut acpi_handler = YourHandler::new(/*args*/);
//! let acpi_tables = unsafe { AcpiTables::from_rsdp(acpi_handler.clone(), rsdp_addr) }.unwrap();
//! let platform = AcpiPlatform::new(acpi_tables, acpi_handler).unwrap();
//! let interpreter = Interpreter::new_from_platform(&platform).unwrap();
//! ```
#![no_std]
#![feature(allocator_api)]
#![feature(let_chains)]
#![feature(vec_pop_if)]
#[cfg_attr(test, macro_use)]
#[cfg(test)]
extern crate std;
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod address;
#[cfg(feature = "aml")]
pub mod aml;
#[cfg(feature = "alloc")]
pub mod platform;
pub mod registers;
pub mod rsdp;
pub mod sdt;
pub use pci_types::PciAddress;
pub use sdt::{fadt::PowerProfile, hpet::HpetInfo, madt::MadtError};
use crate::sdt::{SdtHeader, Signature};
use core::{
fmt,
mem,
ops::{Deref, DerefMut},
pin::Pin,
ptr::NonNull,
};
use log::{error, warn};
use rsdp::Rsdp;
/// `AcpiTables` should be constructed after finding the RSDP or RSDT/XSDT and allows enumeration
/// of the system's ACPI tables.
pub struct AcpiTables<H: Handler> {
rsdt_mapping: PhysicalMapping<H, SdtHeader>,
pub rsdp_revision: u8,
handler: H,
}
unsafe impl<H> Send for AcpiTables<H> where H: Handler + Send {}
unsafe impl<H> Sync for AcpiTables<H> where H: Handler + Send {}
impl<H> AcpiTables<H>
where
H: Handler,
{
/// Construct an `AcpiTables` from the **physical** address of the RSDP.
///
/// # Safety
/// The address of the RSDP must be valid.
pub unsafe fn from_rsdp(handler: H, rsdp_address: usize) -> Result<AcpiTables<H>, AcpiError> {
let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(rsdp_address, mem::size_of::<Rsdp>()) };
/*
* If the address given does not have a correct RSDP signature, the user has probably given
* us an invalid address, and we should not continue. We're more lenient with other errors
* as it's probably a real RSDP and the firmware developers are just lazy.
*/
match rsdp_mapping.validate() {
Ok(()) => (),
Err(AcpiError::RsdpIncorrectSignature) => return Err(AcpiError::RsdpIncorrectSignature),
Err(AcpiError::RsdpInvalidOemId) | Err(AcpiError::RsdpInvalidChecksum) => {
warn!("RSDP has invalid checksum or OEM ID. Continuing.");
}
Err(_) => (),
}
let rsdp_revision = rsdp_mapping.revision();
let rsdt_address = if rsdp_revision == 0 {
// We're running on ACPI Version 1.0. We should use the 32-bit RSDT address.
rsdp_mapping.rsdt_address() as usize
} else {
/*
* We're running on ACPI Version 2.0+. We should use the 64-bit XSDT address, truncated
* to 32 bits on x86.
*/
rsdp_mapping.xsdt_address() as usize
};
unsafe { Self::from_rsdt(handler, rsdp_revision, rsdt_address) }
}
/// Construct an `AcpiTables` from the **physical** address of the RSDT/XSDT, and the revision
/// found in the RSDP.
///
/// # Safety
/// The address of the RSDT must be valid.
pub unsafe fn from_rsdt(
handler: H,
rsdp_revision: u8,
rsdt_address: usize,
) -> Result<AcpiTables<H>, AcpiError> {
let rsdt_mapping =
unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, mem::size_of::<SdtHeader>()) };
let rsdt_length = rsdt_mapping.length;
let rsdt_mapping = unsafe { handler.map_physical_region::<SdtHeader>(rsdt_address, rsdt_length as usize) };
Ok(Self { rsdt_mapping, rsdp_revision, handler })
}
/// Iterate over the **physical** addresses of the SDTs.
pub fn table_entries(&self) -> impl Iterator<Item = usize> {
let entry_size = if self.rsdp_revision == 0 { 4 } else { 8 };
let mut table_entries_ptr =
unsafe { self.rsdt_mapping.virtual_start.as_ptr().byte_add(mem::size_of::<SdtHeader>()) }.cast::<u8>();
let mut num_entries = (self.rsdt_mapping.region_length - mem::size_of::<SdtHeader>()) / entry_size;
core::iter::from_fn(move || {
if num_entries > 0 {
unsafe {
let entry = if entry_size == 4 {
*table_entries_ptr.cast::<u32>() as usize
} else {
*table_entries_ptr.cast::<u64>() as usize
};
table_entries_ptr = table_entries_ptr.byte_add(entry_size);
num_entries -= 1;
Some(entry)
}
} else {
None
}
})
}
/// Iterate over the headers of each SDT, along with their **physical** addresses.
pub fn table_headers(&self) -> impl Iterator<Item = (usize, SdtHeader)> {
self.table_entries().map(|table_phys_address| {
let mapping = unsafe {
self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
};
(table_phys_address, *mapping)
})
}
/// Find all tables with the signature `T::SIGNATURE`.
pub fn find_tables<T>(&self) -> impl Iterator<Item = PhysicalMapping<H, T>>
where
T: AcpiTable,
{
self.table_entries().filter_map(|table_phys_address| {
let header_mapping = unsafe {
self.handler.map_physical_region::<SdtHeader>(table_phys_address, mem::size_of::<SdtHeader>())
};
if header_mapping.signature == T::SIGNATURE {
// Extend the mapping to the entire table
let length = header_mapping.length;
drop(header_mapping);
Some(unsafe { self.handler.map_physical_region::<T>(table_phys_address, length as usize) })
} else {
None
}
})
}
/// Find the first table with the signature `T::SIGNATURE`.
pub fn find_table<T>(&self) -> Option<PhysicalMapping<H, T>>
where
T: AcpiTable,
{
self.find_tables().next()
}
pub fn dsdt(&self) -> Result<AmlTable, AcpiError> {
let Some(fadt) = self.find_table::<sdt::fadt::Fadt>() else {
Err(AcpiError::TableNotFound(Signature::FADT))?
};
let phys_address = fadt.dsdt_address()?;
let header =
unsafe { self.handler.map_physical_region::<SdtHeader>(phys_address, mem::size_of::<SdtHeader>()) };
Ok(AmlTable { phys_address, length: header.length, revision: header.revision })
}
pub fn ssdts(&self) -> impl Iterator<Item = AmlTable> {
self.table_headers().filter_map(|(phys_address, header)| {
if header.signature == Signature::SSDT {
Some(AmlTable { phys_address, length: header.length, revision: header.revision })
} else {
None
}
})
}
}
#[derive(Clone, Copy, Debug)]
pub struct AmlTable {
/// The physical address of the start of the table. Add `mem::size_of::<SdtHeader>()` to this
/// to get the physical address of the start of the AML stream.
pub phys_address: usize,
/// The length of the table, including the header.
pub length: u32,
pub revision: u8,
}
/// All types representing ACPI tables should implement this trait.
///
/// ### Safety
/// The table's memory is naively interpreted, so you must be careful in providing a type that
/// correctly represents the table's structure. Regardless of the provided type's size, the region mapped will
/// be the size specified in the SDT's header. If a table's definition may be larger than a valid
/// SDT's size, [`ExtendedField`](sdt::ExtendedField) should be used to define fields that may or
/// may not exist.
pub unsafe trait AcpiTable {
const SIGNATURE: Signature;
fn header(&self) -> &SdtHeader;
fn validate(&self) -> Result<(), AcpiError> {
unsafe { self.header().validate(Self::SIGNATURE) }
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum AcpiError {
NoValidRsdp,
RsdpIncorrectSignature,
RsdpInvalidOemId,
RsdpInvalidChecksum,
SdtInvalidSignature(Signature),
SdtInvalidOemId(Signature),
SdtInvalidTableId(Signature),
SdtInvalidChecksum(Signature),
SdtInvalidCreatorId(Signature),
TableNotFound(Signature),
InvalidFacsAddress,
InvalidDsdtAddress,
InvalidMadt(MadtError),
InvalidGenericAddress,
Timeout,
#[cfg(feature = "alloc")]
Aml(aml::AmlError),
/// This is emitted to signal that the library does not support the requested behaviour. This
/// should eventually never be emitted.
LibUnimplemented,
/// This can be returned by the host (user of the library) to signal that required behaviour
/// has not been implemented. This will cause the error to be propagated back to the host if an
/// operation that requires that behaviour is performed.
HostUnimplemented,
}
/// Describes a physical mapping created by [`Handler::map_physical_region`] and unmapped by
/// [`Handler::unmap_physical_region`]. The region mapped must be at least `size_of::<T>()`
/// bytes, but may be bigger.
pub struct PhysicalMapping<H, T>
where
H: Handler,
{
/// The physical address of the mapped structure. The actual mapping may start at a lower address
/// if the requested physical address is not well-aligned.
pub physical_start: usize,
/// The virtual address of the mapped structure. It must be a valid, non-null pointer to the
/// start of the requested structure. The actual virtual mapping may start at a lower address
/// if the requested address is not well-aligned.
pub virtual_start: NonNull<T>,
/// The size of the requested region, in bytes. Can be equal or larger to `size_of::<T>()`. If a
/// larger region has been mapped, this should still be the requested size.
pub region_length: usize,
/// The total size of the produced mapping. This may be the same as `region_length`, or larger to
/// meet requirements of the mapping implementation.
pub mapped_length: usize,
/// The [`Handler`] that was used to produce the mapping. When this mapping is dropped, this
/// handler will be used to unmap the region.
pub handler: H,
}
impl<H, T> PhysicalMapping<H, T>
where
H: Handler,
{
/// Get a pinned reference to the inner `T`. This is generally only useful if `T` is `!Unpin`,
/// otherwise the mapping can simply be dereferenced to access the inner type.
pub fn get(&self) -> Pin<&T> {
unsafe { Pin::new_unchecked(self.virtual_start.as_ref()) }
}
}
impl<H, T> fmt::Debug for PhysicalMapping<H, T>
where
H: Handler,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PhysicalMapping")
.field("physical_start", &self.physical_start)
.field("virtual_start", &self.virtual_start)
.field("region_length", &self.region_length)
.field("mapped_length", &self.mapped_length)
.field("handler", &())
.finish()
}
}
unsafe impl<H: Handler + Send, T: Send> Send for PhysicalMapping<H, T> {}
impl<H, T> Deref for PhysicalMapping<H, T>
where
T: Unpin,
H: Handler,
{
type Target = T;
fn deref(&self) -> &T {
unsafe { self.virtual_start.as_ref() }
}
}
impl<H, T> DerefMut for PhysicalMapping<H, T>
where
T: Unpin,
H: Handler,
{
fn deref_mut(&mut self) -> &mut T {
unsafe { self.virtual_start.as_mut() }
}
}
impl<H, T> Drop for PhysicalMapping<H, T>
where
H: Handler,
{
fn drop(&mut self) {
H::unmap_physical_region(self)
}
}
/// A `Handle` is an opaque reference to an object that is managed by the host on behalf of this
/// library.
///
/// The library will treat the value of a handle as entirely opaque. You may manage handles
/// however you wish, and the same value can be used to refer to objects of different types, if
/// desired.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Handle(pub u32);
/// An implementation of this trait must be provided to allow `acpi` to perform operations that
/// interface with the underlying hardware and other systems in your host implementation. This
/// interface is designed to be flexible to allow usage of the library from a variety of settings.
///
/// Depending on your usage of this library, not all functionality may be required. If you do not
/// provide certain functionality, you should return [`AcpiError::HostUnimplemented`]. The library
/// will attempt to propagate this error back to the host if an operation cannot be performed
/// without that functionality.
///
/// The `Handler` must be cheaply clonable (e.g. a reference, `Arc`, marker struct, etc.) as a copy
/// of the handler is stored in various structures, such as in each [`PhysicalMapping`] to
/// facilitate unmapping.
pub trait Handler: Clone {
/// Given a physical address and a size, map a region of physical memory that contains `T` (note: the passed
/// size may be larger than `size_of::<T>()`). The address is not neccessarily page-aligned, so the
/// implementation may need to map more than `size` bytes. The virtual address the region is mapped to does not
/// matter, as long as it is accessible to `acpi`. Refer to the fields on [`PhysicalMapping`] to understand how
/// to produce one properly.
///
/// ## Safety
///
/// - `physical_address` must point to a valid `T` in physical memory.
/// - `size` must be at least `size_of::<T>()`.
unsafe fn map_physical_region<T>(&self, physical_address: usize, size: usize) -> PhysicalMapping<Self, T>;
/// Unmap the given physical mapping. This is called when a [`PhysicalMapping`] is dropped, you should **not** manually call this.
///
/// Note: A reference to the [`Handler`] used to construct `region` can be acquired from [`PhysicalMapping::handler`].
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>);
// TODO: maybe we should map stuff ourselves in the AML interpreter and do this internally?
// Maybe provide a hook for tracing the IO / emit trace events ourselves if we do do that?
fn read_u8(&self, address: usize) -> u8;
fn read_u16(&self, address: usize) -> u16;
fn read_u32(&self, address: usize) -> u32;
fn read_u64(&self, address: usize) -> u64;
fn write_u8(&self, address: usize, value: u8);
fn write_u16(&self, address: usize, value: u16);
fn write_u32(&self, address: usize, value: u32);
fn write_u64(&self, address: usize, value: u64);
// TODO: would be nice to provide defaults that just do the actual port IO on x86?
fn read_io_u8(&self, port: u16) -> u8;
fn read_io_u16(&self, port: u16) -> u16;
fn read_io_u32(&self, port: u16) -> u32;
fn write_io_u8(&self, port: u16, value: u8);
fn write_io_u16(&self, port: u16, value: u16);
fn write_io_u32(&self, port: u16, value: u32);
fn read_pci_u8(&self, address: PciAddress, offset: u16) -> u8;
fn read_pci_u16(&self, address: PciAddress, offset: u16) -> u16;
fn read_pci_u32(&self, address: PciAddress, offset: u16) -> u32;
fn write_pci_u8(&self, address: PciAddress, offset: u16, value: u8);
fn write_pci_u16(&self, address: PciAddress, offset: u16, value: u16);
fn write_pci_u32(&self, address: PciAddress, offset: u16, value: u32);
/// Returns a monotonically-increasing value of nanoseconds.
fn nanos_since_boot(&self) -> u64;
/// Stall for at least the given number of **microseconds**. An implementation should not relinquish control of
/// the processor during the stall, and for this reason, firmwares should not stall for periods of more than
/// 100 microseconds.
fn stall(&self, microseconds: u64);
/// Sleep for at least the given number of **milliseconds**. An implementation may round to the closest sleep
/// time supported, and should relinquish the processor.
fn sleep(&self, milliseconds: u64);
#[cfg(feature = "aml")]
fn create_mutex(&self) -> Handle;
/// Acquire the mutex referred to by the given handle. `timeout` is a millisecond timeout value
/// with the following meaning:
/// - `0` - try to acquire the mutex once, in a non-blocking manner. If the mutex cannot be
/// acquired immediately, return `Err(AmlError::MutexAcquireTimeout)`
/// - `1-0xfffe` - try to acquire the mutex for at least `timeout` milliseconds.
/// - `0xffff` - try to acquire the mutex indefinitely. Should not return `MutexAcquireTimeout`.
///
/// AML mutexes are **reentrant** - that is, a thread may acquire the same mutex more than once
/// without causing a deadlock.
#[cfg(feature = "aml")]
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), aml::AmlError>;
#[cfg(feature = "aml")]
fn release(&self, mutex: Handle);
#[cfg(feature = "aml")]
fn breakpoint(&self) {}
#[cfg(feature = "aml")]
fn handle_debug(&self, _object: &aml::object::Object) {}
/// Called when AML executes `Notify (device, value)`. The default
/// implementation drops the notification; hosts that consume ACPI
/// device notifications (battery, AC, lid, thermal) should override it.
/// Mirrors the ACPICA `EvNotify` / Linux `acpi_ev_notify_dispatch`
/// delivery point.
#[cfg(feature = "aml")]
fn handle_notify(&self, _device: &str, _value: u64) {}
#[cfg(feature = "aml")]
fn handle_fatal_error(&self, fatal_type: u8, fatal_code: u32, fatal_arg: u64) {
error!(
"Fatal error while executing AML (encountered DefFatalOp). fatal_type = {}, fatal_code = {}, fatal_arg = {}",
fatal_type, fatal_code, fatal_arg
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(dead_code)]
fn test_physical_mapping_send_sync() {
fn test_send_sync<T: Send>() {}
fn caller<H: Handler + Send, T: Send>() {
test_send_sync::<PhysicalMapping<H, T>>();
}
}
}
-334
View File
@@ -1,334 +0,0 @@
use super::{Processor, ProcessorInfo, ProcessorState};
use crate::{
AcpiError,
AcpiTables,
Handler,
MadtError,
sdt::{
Signature,
madt::{Madt, MadtEntry, parse_mps_inti_flags},
},
};
use alloc::{alloc::Global, vec::Vec};
use bit_field::BitField;
use core::{alloc::Allocator, pin::Pin};
pub use crate::sdt::madt::{Polarity, TriggerMode};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum InterruptModel<A: Allocator = Global> {
/// This model is only chosen when the MADT does not describe another interrupt model. On `x86_64` platforms,
/// this probably means only the legacy i8259 PIC is present.
Unknown,
/// Describes an interrupt controller based around the Advanced Programmable Interrupt Controller (any of APIC,
/// XAPIC, or X2APIC). These are likely to be found on x86 and x86_64 systems and are made up of a Local APIC
/// for each core and one or more I/O APICs to handle external interrupts.
Apic(Apic<A>),
}
impl InterruptModel<Global> {
pub fn new<H: Handler>(
tables: &AcpiTables<H>,
) -> Result<(InterruptModel<Global>, Option<ProcessorInfo<Global>>), AcpiError> {
Self::new_in(tables, Global)
}
}
impl<A: Allocator + Clone> InterruptModel<A> {
pub fn new_in<H: Handler>(
tables: &AcpiTables<H>,
allocator: A,
) -> Result<(InterruptModel<A>, Option<ProcessorInfo<A>>), AcpiError> {
let Some(madt) = tables.find_table::<Madt>() else { Err(AcpiError::TableNotFound(Signature::MADT))? };
/*
* We first do a pass through the MADT to determine which interrupt model is being used.
*/
for entry in madt.get().entries() {
match entry {
MadtEntry::LocalApic(_)
| MadtEntry::LocalX2Apic(_)
| MadtEntry::IoApic(_)
| MadtEntry::InterruptSourceOverride(_)
| MadtEntry::LocalApicNmi(_)
| MadtEntry::X2ApicNmi(_)
| MadtEntry::LocalApicAddressOverride(_) => {
return Self::from_apic_model_in(madt.get(), allocator);
}
MadtEntry::IoSapic(_) | MadtEntry::LocalSapic(_) | MadtEntry::PlatformInterruptSource(_) => {}
MadtEntry::Gicc(_)
| MadtEntry::Gicd(_)
| MadtEntry::GicMsiFrame(_)
| MadtEntry::GicRedistributor(_)
| MadtEntry::GicInterruptTranslationService(_) => {}
MadtEntry::NmiSource(_) => (),
MadtEntry::MultiprocessorWakeup(_) => (),
}
}
Ok((InterruptModel::Unknown, None))
}
fn from_apic_model_in(
madt: Pin<&Madt>,
allocator: A,
) -> Result<(InterruptModel<A>, Option<ProcessorInfo<A>>), AcpiError> {
let mut local_apic_address = madt.local_apic_address as u64;
let mut io_apic_count = 0;
let mut iso_count = 0;
let mut nmi_source_count = 0;
let mut local_nmi_line_count = 0;
let mut processor_count = 0usize;
// Do a pass over the entries so we know how much space we should reserve in the vectors
for entry in madt.entries() {
match entry {
MadtEntry::IoApic(_) => io_apic_count += 1,
MadtEntry::InterruptSourceOverride(_) => iso_count += 1,
MadtEntry::NmiSource(_) => nmi_source_count += 1,
MadtEntry::LocalApicNmi(_) => local_nmi_line_count += 1,
MadtEntry::X2ApicNmi(_) => local_nmi_line_count += 1,
MadtEntry::LocalApic(_) => processor_count += 1,
MadtEntry::LocalX2Apic(_) => processor_count += 1,
_ => (),
}
}
let mut io_apics = Vec::with_capacity_in(io_apic_count, allocator.clone());
let mut interrupt_source_overrides = Vec::with_capacity_in(iso_count, allocator.clone());
let mut nmi_sources = Vec::with_capacity_in(nmi_source_count, allocator.clone());
let mut local_apic_nmi_lines = Vec::with_capacity_in(local_nmi_line_count, allocator.clone());
let mut application_processors = Vec::with_capacity_in(processor_count.saturating_sub(1), allocator); // Subtract one for the BSP
let mut boot_processor = None;
for entry in madt.entries() {
match entry {
MadtEntry::LocalApic(entry) => {
/*
* The first processor is the BSP. Subsequent ones are APs. If we haven't found
* the BSP yet, this must be it.
*/
let is_ap = boot_processor.is_some();
let is_disabled = !{ entry.flags }.get_bit(0);
let state = match (is_ap, is_disabled) {
(_, true) => ProcessorState::Disabled,
(true, false) => ProcessorState::WaitingForSipi,
(false, false) => ProcessorState::Running,
};
let processor = Processor {
processor_uid: entry.processor_id as u32,
local_apic_id: entry.apic_id as u32,
state,
is_ap,
};
if is_ap {
application_processors.push(processor);
} else {
boot_processor = Some(processor);
}
}
MadtEntry::LocalX2Apic(entry) => {
let is_ap = boot_processor.is_some();
let is_disabled = !{ entry.flags }.get_bit(0);
let state = match (is_ap, is_disabled) {
(_, true) => ProcessorState::Disabled,
(true, false) => ProcessorState::WaitingForSipi,
(false, false) => ProcessorState::Running,
};
let processor = Processor {
processor_uid: entry.processor_uid,
local_apic_id: entry.x2apic_id,
state,
is_ap,
};
if is_ap {
application_processors.push(processor);
} else {
boot_processor = Some(processor);
}
}
MadtEntry::IoApic(entry) => {
io_apics.push(IoApic {
id: entry.io_apic_id,
address: entry.io_apic_address,
global_system_interrupt_base: entry.global_system_interrupt_base,
});
}
MadtEntry::InterruptSourceOverride(entry) => {
if entry.bus != 0 {
return Err(AcpiError::InvalidMadt(MadtError::InterruptOverrideEntryHasInvalidBus));
}
let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
interrupt_source_overrides.push(InterruptSourceOverride {
isa_source: entry.irq,
global_system_interrupt: entry.global_system_interrupt,
polarity,
trigger_mode,
});
}
MadtEntry::NmiSource(entry) => {
let (polarity, trigger_mode) = parse_mps_inti_flags(entry.flags)?;
nmi_sources.push(NmiSource {
global_system_interrupt: entry.global_system_interrupt,
polarity,
trigger_mode,
});
}
MadtEntry::LocalApicNmi(entry) => {
local_apic_nmi_lines.push(NmiLine {
processor: if entry.processor_id == 0xff {
NmiProcessor::All
} else {
NmiProcessor::ProcessorUid(entry.processor_id as u32)
},
line: match entry.nmi_line {
0 => LocalInterruptLine::Lint0,
1 => LocalInterruptLine::Lint1,
_ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
},
});
}
MadtEntry::X2ApicNmi(entry) => {
local_apic_nmi_lines.push(NmiLine {
processor: if entry.processor_uid == 0xffffffff {
NmiProcessor::All
} else {
NmiProcessor::ProcessorUid(entry.processor_uid)
},
line: match entry.nmi_line {
0 => LocalInterruptLine::Lint0,
1 => LocalInterruptLine::Lint1,
_ => return Err(AcpiError::InvalidMadt(MadtError::InvalidLocalNmiLine)),
},
});
}
MadtEntry::LocalApicAddressOverride(entry) => {
local_apic_address = entry.local_apic_address;
}
MadtEntry::MultiprocessorWakeup(_) => {}
_ => {
return Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry));
}
}
}
Ok((
InterruptModel::Apic(Apic::new(
local_apic_address,
io_apics,
local_apic_nmi_lines,
interrupt_source_overrides,
nmi_sources,
madt.supports_8259(),
)),
Some(ProcessorInfo::new_in(boot_processor.unwrap(), application_processors)),
))
}
}
#[derive(Debug, Clone, Copy)]
pub struct IoApic {
pub id: u8,
/// The physical address at which to access this I/O APIC.
pub address: u32,
/// The global system interrupt number where this I/O APIC's inputs start.
pub global_system_interrupt_base: u32,
}
#[derive(Debug, Clone, Copy)]
pub struct NmiLine {
pub processor: NmiProcessor,
pub line: LocalInterruptLine,
}
/// Indicates which local interrupt line will be utilized by an external interrupt. Specifically,
/// these lines directly correspond to their requisite LVT entries in a processor's APIC.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LocalInterruptLine {
Lint0,
Lint1,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NmiProcessor {
All,
ProcessorUid(u32),
}
/// Describes a difference in the mapping of an ISA interrupt to how it's mapped in other interrupt
/// models. For example, if a device is connected to ISA IRQ 0 and IOAPIC input 2, an override will
/// appear mapping source 0 to GSI 2. Currently these will only be created for ISA interrupt
/// sources.
#[derive(Debug, Clone, Copy)]
pub struct InterruptSourceOverride {
pub isa_source: u8,
pub global_system_interrupt: u32,
pub polarity: Polarity,
pub trigger_mode: TriggerMode,
}
/// Describes a Global System Interrupt that should be enabled as non-maskable. Any source that is
/// non-maskable can not be used by devices.
#[derive(Debug, Clone, Copy)]
pub struct NmiSource {
pub global_system_interrupt: u32,
pub polarity: Polarity,
pub trigger_mode: TriggerMode,
}
#[derive(Debug, Clone)]
pub struct Apic<A: Allocator = Global> {
pub local_apic_address: u64,
pub io_apics: Vec<IoApic, A>,
pub local_apic_nmi_lines: Vec<NmiLine, A>,
pub interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
pub nmi_sources: Vec<NmiSource, A>,
/// If this field is set, you must remap and mask all the lines of the legacy PIC, even if
/// you choose to use the APIC. It's recommended that you do this even if ACPI does not
/// require you to.
pub also_has_legacy_pics: bool,
}
impl<A: Allocator> Apic<A> {
pub(crate) fn new(
local_apic_address: u64,
io_apics: Vec<IoApic, A>,
local_apic_nmi_lines: Vec<NmiLine, A>,
interrupt_source_overrides: Vec<InterruptSourceOverride, A>,
nmi_sources: Vec<NmiSource, A>,
also_has_legacy_pics: bool,
) -> Self {
Self {
local_apic_address,
io_apics,
local_apic_nmi_lines,
interrupt_source_overrides,
nmi_sources,
also_has_legacy_pics,
}
}
}
-264
View File
@@ -1,264 +0,0 @@
pub mod interrupt;
pub mod numa;
pub mod pci;
pub use interrupt::InterruptModel;
pub use pci::PciConfigRegions;
use crate::{
AcpiError,
AcpiTables,
Handler,
PowerProfile,
address::GenericAddress,
registers::{FixedRegisters, Pm1ControlBit, Pm1Event},
sdt::{
Signature,
fadt::Fadt,
madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox},
},
};
use alloc::{alloc::Global, sync::Arc, vec::Vec};
use core::{alloc::Allocator, mem, ptr};
/// `AcpiPlatform` is a higher-level view of the ACPI tables that makes it easier to perform common
/// tasks with ACPI. It requires allocator support.
pub struct AcpiPlatform<H: Handler, A: Allocator = Global> {
pub handler: H,
pub tables: AcpiTables<H>,
pub power_profile: PowerProfile,
pub interrupt_model: InterruptModel<A>,
/// The interrupt vector that the System Control Interrupt (SCI) is wired to. On x86 systems with
/// an 8259, this is the interrupt vector. On other systems, this is the GSI of the SCI
/// interrupt. The interrupt should be treated as a shareable, level, active-low interrupt.
pub sci_interrupt: u16,
/// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
/// interrupt model. That information is stored here, if present.
pub processor_info: Option<ProcessorInfo<A>>,
pub pm_timer: Option<PmTimer>,
pub registers: Arc<FixedRegisters<H>>,
}
unsafe impl<H, A> Send for AcpiPlatform<H, A>
where
H: Handler,
A: Allocator,
{
}
unsafe impl<H, A> Sync for AcpiPlatform<H, A>
where
H: Handler,
A: Allocator,
{
}
impl<H: Handler> AcpiPlatform<H, Global> {
pub fn new(tables: AcpiTables<H>, handler: H) -> Result<Self, AcpiError> {
Self::new_in(tables, handler, alloc::alloc::Global)
}
}
impl<H: Handler, A: Allocator + Clone> AcpiPlatform<H, A> {
pub fn new_in(tables: AcpiTables<H>, handler: H, allocator: A) -> Result<Self, AcpiError> {
let Some(fadt) = tables.find_table::<Fadt>() else { Err(AcpiError::TableNotFound(Signature::FADT))? };
let power_profile = fadt.power_profile();
let (interrupt_model, processor_info) = InterruptModel::new_in(&tables, allocator)?;
let pm_timer = PmTimer::new(&fadt)?;
let registers = Arc::new(FixedRegisters::new(&fadt, handler.clone())?);
Ok(AcpiPlatform {
handler: handler.clone(),
tables,
power_profile,
interrupt_model,
sci_interrupt: fadt.sci_interrupt,
processor_info,
pm_timer,
registers,
})
}
/// Initializes the event registers, masking all events to start.
pub fn initialize_events(&self) -> Result<(), AcpiError> {
/*
* Disable all fixed events to start.
*/
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Timer, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::GlobalLock, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::PowerButton, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::SleepButton, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Rtc, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::PciEWake, false)?;
self.registers.pm1_event_registers.set_event_enabled(Pm1Event::Wake, false)?;
// TODO: deal with GPEs
Ok(())
}
pub fn read_mode(&self) -> Result<AcpiMode, AcpiError> {
if self.registers.pm1_control_registers.read_bit(Pm1ControlBit::SciEnable)? {
Ok(AcpiMode::Acpi)
} else {
Ok(AcpiMode::Legacy)
}
}
/// Move the platform into ACPI mode, if it is not already in it. This means platform power
/// management events will be routed to the kernel via the SCI interrupt, instead of to the
/// firmware's SMI handler.
///
/// ### Warning
/// This can be a bad idea on real hardware if you are not able to handle platform events
/// properly. Entering ACPI mode means you are responsible for dealing with events like the
/// power button and thermal events instead of the firmware - if you do not handle these, it
/// may be difficult to recover the platform. Hardware damage is unlikely as firmware usually
/// has safeguards for critical events, but like with all things concerning firmware, you may
/// not wish to rely on these.
pub fn enter_acpi_mode(&self) -> Result<(), AcpiError> {
if self.read_mode()? == AcpiMode::Acpi {
return Ok(());
}
let Some(fadt) = self.tables.find_table::<Fadt>() else { Err(AcpiError::TableNotFound(Signature::FADT))? };
self.handler.write_io_u8(fadt.smi_cmd_port as u16, fadt.acpi_enable);
/*
* We now have to spin and wait for the firmware to yield control. We'll wait up to 3
* seconds.
*/
let mut spinning = 3 * 1000 * 1000; // Microseconds
while spinning > 0 {
if self.read_mode()? == AcpiMode::Acpi {
return Ok(());
}
spinning -= 100;
self.handler.stall(100);
}
Err(AcpiError::Timeout)
}
/// Wake up all Application Processors (APs) using the Multiprocessor Wakeup Mailbox Mechanism.
/// This may not be available on the platform you're running on.
///
/// On Intel processors, this will start the AP in long-mode, with interrupts disabled and a
/// single page with the supplied waking vector identity-mapped (it is therefore advisable to
/// align your waking vector to start at a page boundary and fit within one page).
///
/// # Safety
/// An appropriate environment must exist for the AP to boot into at the given address, or the
/// AP could fault or cause unexpected behaviour.
pub unsafe fn wake_aps(&self, apic_id: u32, wakeup_vector: u64, timeout_loops: u64) -> Result<(), AcpiError> {
let Some(madt) = self.tables.find_table::<Madt>() else { Err(AcpiError::TableNotFound(Signature::MADT))? };
let mailbox_addr = madt.get().get_mpwk_mailbox_addr()?;
let mut mpwk_mapping = unsafe {
self.handler.map_physical_region::<MultiprocessorWakeupMailbox>(
mailbox_addr as usize,
mem::size_of::<MultiprocessorWakeupMailbox>(),
)
};
// Reset command
unsafe {
ptr::write_volatile(&mut mpwk_mapping.command, MpProtectedModeWakeupCommand::Noop as u16);
}
// Fill the mailbox
mpwk_mapping.apic_id = apic_id;
mpwk_mapping.wakeup_vector = wakeup_vector;
unsafe {
ptr::write_volatile(&mut mpwk_mapping.command, MpProtectedModeWakeupCommand::Wakeup as u16);
}
// Wait to join
// TODO: if we merge the handlers into one, we could use `stall` here.
let mut loops = 0;
let mut command = MpProtectedModeWakeupCommand::Wakeup;
while command != MpProtectedModeWakeupCommand::Noop {
if loops >= timeout_loops {
return Err(AcpiError::InvalidMadt(MadtError::WakeupApsTimeout));
}
// SAFETY: The caller must ensure that the provided `handler` correctly handles these
// operations and that the specified `mailbox_addr` is valid.
unsafe {
command = ptr::read_volatile(&mpwk_mapping.command).into();
}
core::hint::spin_loop();
loops += 1;
}
drop(mpwk_mapping);
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProcessorState {
/// A processor in this state is unusable, and you must not attempt to bring it up.
Disabled,
/// A processor waiting for a SIPI (Startup Inter-processor Interrupt) is currently not active,
/// but may be brought up.
WaitingForSipi,
/// A Running processor is currently brought up and running code.
Running,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Processor {
/// Corresponds to the `_UID` object of the processor's `Device`, or the `ProcessorId` field of the `Processor`
/// object, in AML.
pub processor_uid: u32,
/// The ID of the local APIC of the processor. Will be less than `256` if the APIC is being used, but can be
/// greater than this if the X2APIC is being used.
pub local_apic_id: u32,
/// The state of this processor. Check that the processor is not `Disabled` before attempting to bring it up!
pub state: ProcessorState,
/// Whether this processor is the Bootstrap Processor (BSP), or an Application Processor (AP).
/// When the bootloader is entered, the BSP is the only processor running code. To run code on
/// more than one processor, you need to "bring up" the APs.
pub is_ap: bool,
}
#[derive(Debug, Clone)]
pub struct ProcessorInfo<A: Allocator = Global> {
pub boot_processor: Processor,
/// Application processors should be brought up in the order they're defined in this list.
pub application_processors: Vec<Processor, A>,
}
impl<A: Allocator> ProcessorInfo<A> {
pub(crate) fn new_in(boot_processor: Processor, application_processors: Vec<Processor, A>) -> Self {
Self { boot_processor, application_processors }
}
}
/// Information about the ACPI Power Management Timer (ACPI PM Timer).
#[derive(Debug, Clone)]
pub struct PmTimer {
/// A generic address to the register block of ACPI PM Timer.
pub base: GenericAddress,
/// This field is `true` if the hardware supports 32-bit timer, and `false` if the hardware supports 24-bit timer.
pub supports_32bit: bool,
}
impl PmTimer {
pub fn new(fadt: &Fadt) -> Result<Option<PmTimer>, AcpiError> {
match fadt.pm_timer_block()? {
Some(base) => Ok(Some(PmTimer { base, supports_32bit: { fadt.flags }.pm_timer_is_32_bit() })),
None => Ok(None),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum AcpiMode {
Legacy,
Acpi,
}
-88
View File
@@ -1,88 +0,0 @@
use crate::{
AcpiTables,
Handler,
sdt::{
slit::{DistanceMatrix, Slit},
srat::{LocalApicAffinityFlags, MemoryAffinityFlags, Srat, SratEntry},
},
};
use alloc::{alloc::Global, vec::Vec};
use core::alloc::Allocator;
/// Information about the setup of NUMA (Non-Uniform Memory Architecture) resources within the
/// sytem.
#[derive(Clone, Debug)]
pub struct NumaInfo<A: Allocator = Global> {
pub processor_affinity: Vec<ProcessorAffinity, A>,
pub memory_affinity: Vec<MemoryAffinity, A>,
pub num_proximity_domains: usize,
pub distance_matrix: Vec<u8, A>,
}
impl NumaInfo<Global> {
pub fn new(tables: AcpiTables<impl Handler>) -> NumaInfo<Global> {
Self::new_in(tables, Global)
}
}
impl<A: Allocator + Clone> NumaInfo<A> {
pub fn new_in(tables: AcpiTables<impl Handler>, allocator: A) -> NumaInfo<A> {
let mut processor_affinity = Vec::new_in(allocator.clone());
let mut memory_affinity = Vec::new_in(allocator.clone());
if let Some(srat) = tables.find_table::<Srat>() {
for entry in srat.get().entries() {
match entry {
SratEntry::LocalApicAffinity(entry) => processor_affinity.push(ProcessorAffinity {
local_apic_id: entry.apic_id as u32,
proximity_domain: entry.proximity_domain(),
is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED),
}),
SratEntry::LocalApicX2Affinity(entry) => processor_affinity.push(ProcessorAffinity {
local_apic_id: entry.x2apic_id,
proximity_domain: entry.proximity_domain,
is_enabled: { entry.flags }.contains(LocalApicAffinityFlags::ENABLED),
}),
SratEntry::MemoryAffinity(entry) => memory_affinity.push(MemoryAffinity {
base_address: entry.base_address(),
length: entry.length(),
proximity_domain: entry.proximity_domain,
is_enabled: { entry.flags }.contains(MemoryAffinityFlags::ENABLED),
is_hot_pluggable: { entry.flags }.contains(MemoryAffinityFlags::HOT_PLUGGABLE),
is_non_volatile: { entry.flags }.contains(MemoryAffinityFlags::NON_VOLATILE),
}),
_ => (),
}
}
}
let (num_proximity_domains, distance_matrix) = if let Some(slit) = tables.find_table::<Slit>() {
(slit.get().num_proximity_domains as usize, slit.get().matrix_raw().to_vec_in(allocator.clone()))
} else {
(0, Vec::new_in(allocator.clone()))
};
NumaInfo { processor_affinity, memory_affinity, num_proximity_domains, distance_matrix }
}
pub fn distance_matrix(&self) -> DistanceMatrix<'_> {
DistanceMatrix { num_proximity_domains: self.num_proximity_domains as u64, matrix: &self.distance_matrix }
}
}
#[derive(Clone, Debug)]
pub struct ProcessorAffinity {
pub local_apic_id: u32,
pub proximity_domain: u32,
pub is_enabled: bool,
}
#[derive(Clone, Debug)]
pub struct MemoryAffinity {
pub base_address: u64,
pub length: u64,
pub proximity_domain: u32,
pub is_enabled: bool,
pub is_hot_pluggable: bool,
pub is_non_volatile: bool,
}
-65
View File
@@ -1,65 +0,0 @@
use crate::{
AcpiError,
AcpiTables,
Handler,
sdt::{
Signature,
mcfg::{Mcfg, McfgEntry},
},
};
use alloc::{
alloc::{Allocator, Global},
vec::Vec,
};
/// Describes a set of regions of physical memory used to access the PCIe configuration space. A
/// region is created for each entry in the MCFG. Given the segment group, bus, device number, and
/// function of a PCIe device, [`PciConfigRegions::physical_address`] will give you the physical
/// address of the start of that device function's configuration space (each function has 4096
/// bytes of configuration space in PCIe).
#[derive(Clone, Debug)]
pub struct PciConfigRegions<A: Allocator = Global> {
pub regions: Vec<McfgEntry, A>,
}
impl PciConfigRegions<Global> {
pub fn new<H>(tables: &AcpiTables<H>) -> Result<PciConfigRegions<Global>, AcpiError>
where
H: Handler,
{
Self::new_in(tables, Global)
}
}
impl<A: Allocator> PciConfigRegions<A> {
pub fn new_in<H>(tables: &AcpiTables<H>, allocator: A) -> Result<PciConfigRegions<A>, AcpiError>
where
H: Handler,
{
let Some(mcfg) = tables.find_table::<Mcfg>() else { Err(AcpiError::TableNotFound(Signature::MCFG))? };
let regions = mcfg.entries().to_vec_in(allocator);
Ok(Self { regions })
}
/// Get the **physical** address of the start of the configuration space for a given PCIe device
/// function. Returns `None` if there isn't an entry in the MCFG that manages that device.
pub fn physical_address(&self, segment_group_no: u16, bus: u8, device: u8, function: u8) -> Option<u64> {
/*
* First, find the memory region that handles this segment and bus. This method is fine
* because there should only be one region that handles each segment group + bus
* combination.
*/
let region = self.regions.iter().find(|region| {
region.pci_segment_group == segment_group_no
&& (region.bus_number_start..=region.bus_number_end).contains(&bus)
})?;
Some(
region.base_address
+ ((u64::from(bus - region.bus_number_start) << 20)
| (u64::from(device) << 15)
| (u64::from(function) << 12)),
)
}
}
-175
View File
@@ -1,175 +0,0 @@
use crate::{AcpiError, Handler, address::MappedGas, sdt::fadt::Fadt};
use bit_field::BitField;
pub struct FixedRegisters<H: Handler> {
pub pm1_event_registers: Pm1EventRegisterBlock<H>,
pub pm1_control_registers: Pm1ControlRegisterBlock<H>,
}
impl<H> FixedRegisters<H>
where
H: Handler,
{
pub fn new(fadt: &Fadt, handler: H) -> Result<FixedRegisters<H>, AcpiError> {
let pm1_event_registers = {
let pm1a = unsafe { MappedGas::map_gas(fadt.pm1a_event_block()?, &handler)? };
let pm1b = match fadt.pm1b_event_block()? {
Some(gas) => Some(unsafe { MappedGas::map_gas(gas, &handler)? }),
None => None,
};
Pm1EventRegisterBlock { pm1_event_length: fadt.pm1_event_length as usize, pm1a, pm1b }
};
let pm1_control_registers = {
let pm1a = unsafe { MappedGas::map_gas(fadt.pm1a_control_block()?, &handler)? };
let pm1b = match fadt.pm1b_control_block()? {
Some(gas) => Some(unsafe { MappedGas::map_gas(gas, &handler)? }),
None => None,
};
Pm1ControlRegisterBlock { pm1a, pm1b }
};
Ok(FixedRegisters { pm1_event_registers, pm1_control_registers })
}
}
/// The PM1 register grouping contains two register blocks that control fixed events. It is split
/// into two to allow its functionality to be split between two hardware components. `PM1a` and
/// `PM1b` are effectively mirrors of each other - reads are made from both of them and logically
/// ORed, and writes are made to both of them.
///
/// The register grouping contains two registers - a `STS` status register that can be read to
/// determine if an event has fired (and written to clear), and an `EN` enabling register to
/// control whether an event should fire.
pub struct Pm1EventRegisterBlock<H: Handler> {
pub pm1_event_length: usize,
pub pm1a: MappedGas<H>,
pub pm1b: Option<MappedGas<H>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Pm1Event {
Timer = 0,
GlobalLock = 5,
PowerButton = 8,
SleepButton = 9,
Rtc = 10,
PciEWake = 14,
Wake = 15,
}
impl<H> Pm1EventRegisterBlock<H>
where
H: Handler,
{
pub fn set_event_enabled(&self, event: Pm1Event, enabled: bool) -> Result<(), AcpiError> {
let enable_offset = self.pm1_event_length * 8 / 2;
let event_bit = match event {
Pm1Event::Timer => 0,
Pm1Event::GlobalLock => 5,
Pm1Event::PowerButton => 8,
Pm1Event::SleepButton => 9,
Pm1Event::Rtc => 10,
Pm1Event::PciEWake => 14,
Pm1Event::Wake => 15,
};
let mut pm1a = self.pm1a.read()?;
pm1a.set_bit(enable_offset + event_bit, enabled);
self.pm1a.write(pm1a)?;
if let Some(pm1b) = &self.pm1b {
let mut value = pm1b.read()?;
value.set_bit(enable_offset + event_bit, enabled);
pm1b.write(value)?;
}
Ok(())
}
pub fn read(&self) -> Result<u64, AcpiError> {
let pm1_len = self.pm1_event_length * 8;
let pm1a = self.pm1a.read()?.get_bits(0..pm1_len);
let pm1b = if let Some(pm1b) = &self.pm1b { pm1b.read()?.get_bits(0..pm1_len) } else { 0 };
Ok(pm1a | pm1b)
}
}
pub struct Pm1ControlRegisterBlock<H: Handler> {
pub pm1a: MappedGas<H>,
pub pm1b: Option<MappedGas<H>>,
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Pm1ControlBit {
/// Determines whether the power management event produces an SCI or SMI interrupt. This is
/// controlled by the firmware - OSPM should always preserve this bit.
SciEnable = 0,
/// When this bit is set, bus master requests can cause any processor in the C3 state to
/// transistion to C0.
BusMasterWake = 1,
/// A write to this bit generates an SMI, passing control to the platform runtime firmware. It
/// should be written when the global lock is released and the pending bit in the FACS is set.
GlobalLockRelease = 2,
/*
* Bits 3..10 are reserved. Bits 10..13 are SLP_TYPx - this field is set separately and
* contains the desired hardware sleep state the system enters when `SleepEnable` is set.
*/
SleepEnable = 13,
}
impl<H> Pm1ControlRegisterBlock<H>
where
H: Handler,
{
pub fn read_bit(&self, bit: Pm1ControlBit) -> Result<bool, AcpiError> {
let control_bit = match bit {
Pm1ControlBit::SciEnable => 0,
Pm1ControlBit::BusMasterWake => 1,
Pm1ControlBit::GlobalLockRelease => 2,
Pm1ControlBit::SleepEnable => 13,
};
let pm1a = self.pm1a.read()?;
let pm1b = if let Some(ref pm1b) = self.pm1b { pm1b.read()? } else { 0 };
let pm1 = pm1a | pm1b;
Ok(pm1.get_bit(control_bit))
}
pub fn set_bit(&self, bit: Pm1ControlBit, set: bool) -> Result<(), AcpiError> {
let control_bit = match bit {
Pm1ControlBit::SciEnable => 0,
Pm1ControlBit::BusMasterWake => 1,
Pm1ControlBit::GlobalLockRelease => 2,
Pm1ControlBit::SleepEnable => 13,
};
let mut pm1a = self.pm1a.read()?;
pm1a.set_bit(control_bit, set);
self.pm1a.write(pm1a)?;
if let Some(pm1b) = &self.pm1b {
let mut value = pm1b.read()?;
value.set_bit(control_bit, set);
pm1b.write(value)?;
}
Ok(())
}
pub fn set_sleep_typ(&self, value: u8) -> Result<(), AcpiError> {
let mut pm1a = self.pm1a.read()?;
pm1a.set_bits(10..13, value as u64);
self.pm1a.write(pm1a)?;
if let Some(pm1b) = &self.pm1b {
let mut pm1b_value = pm1b.read()?;
pm1b_value.set_bits(10..13, value as u64);
pm1b.write(pm1b_value)?;
}
Ok(())
}
}
-213
View File
@@ -1,213 +0,0 @@
use crate::{AcpiError, Handler, PhysicalMapping};
use core::{mem, ops::Range, slice, str};
/// The size in bytes of the ACPI 1.0 RSDP.
const RSDP_V1_LENGTH: usize = 20;
/// The total size in bytes of the RSDP fields introduced in ACPI 2.0.
const RSDP_V2_EXT_LENGTH: usize = mem::size_of::<Rsdp>() - RSDP_V1_LENGTH;
/// The first structure found in ACPI. It just tells us where the RSDT is.
///
/// On BIOS systems, it is either found in the first 1KiB of the Extended Bios Data Area, or between `0x000e0000`
/// and `0x000fffff`. The signature is always on a 16 byte boundary. On (U)EFI, it may not be located in these
/// locations, and so an address should be found in the EFI configuration table instead.
///
/// The recommended way of locating the RSDP is to let the bootloader do it - Multiboot2 can pass a
/// tag with the physical address of it. If this is not possible, a manual scan can be done.
///
/// If `revision > 0`, (the hardware ACPI version is Version 2.0 or greater), the RSDP contains
/// some new fields. For ACPI Version 1.0, these fields are not valid and should not be accessed.
/// For ACPI Version 2.0+, `xsdt_address` should be used (truncated to `u32` on x86) instead of
/// `rsdt_address`.
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Rsdp {
pub signature: [u8; 8],
pub checksum: u8,
pub oem_id: [u8; 6],
pub revision: u8,
pub rsdt_address: u32,
/*
* These fields are only valid for ACPI Version 2.0 and greater
*/
pub length: u32,
pub xsdt_address: u64,
pub ext_checksum: u8,
_reserved: [u8; 3],
}
impl Rsdp {
/// This searches for a RSDP on BIOS systems.
///
/// ### Safety
/// This function probes memory in three locations:
/// - It reads a word from `40:0e` to locate the EBDA.
/// - The first 1KiB of the EBDA (Extended BIOS Data Area).
/// - The BIOS memory area at `0xe0000..=0xfffff`.
///
/// This should be fine on all BIOS systems. However, UEFI platforms are free to put the RSDP wherever they
/// please, so this won't always find the RSDP. Further, prodding these memory locations may have unintended
/// side-effects. On UEFI systems, the RSDP should be found in the Configuration Table, using two GUIDs:
/// - ACPI v1.0 structures use `eb9d2d30-2d88-11d3-9a16-0090273fc14d`.
/// - ACPI v2.0 or later structures use `8868e871-e4f1-11d3-bc22-0080c73c8881`.
/// You should search the entire table for the v2.0 GUID before searching for the v1.0 one.
pub unsafe fn search_for_on_bios<H>(handler: H) -> Result<PhysicalMapping<H, Rsdp>, AcpiError>
where
H: Handler,
{
let rsdp_address = find_search_areas(handler.clone()).iter().find_map(|area| {
// Map the search area for the RSDP followed by `RSDP_V2_EXT_LENGTH` bytes so an ACPI 1.0 RSDP at the
// end of the area can be read as an `Rsdp` (which always has the size of an ACPI 2.0 RSDP)
let mapping = unsafe {
handler.map_physical_region::<u8>(area.start, area.end - area.start + RSDP_V2_EXT_LENGTH)
};
let extended_area_bytes =
unsafe { slice::from_raw_parts(mapping.virtual_start.as_ptr(), mapping.region_length) };
// Search `Rsdp`-sized windows at 16-byte boundaries relative to the base of the area (which is also
// aligned to 16 bytes due to the implementation of `find_search_areas`)
extended_area_bytes.windows(mem::size_of::<Rsdp>()).step_by(16).find_map(|maybe_rsdp_bytes_slice| {
let maybe_rsdp_virt_ptr = maybe_rsdp_bytes_slice.as_ptr().cast::<Rsdp>();
let maybe_rsdp_phys_start = maybe_rsdp_virt_ptr as usize - mapping.virtual_start.as_ptr() as usize
+ mapping.physical_start;
// SAFETY: `maybe_rsdp_virt_ptr` points to an aligned, readable `Rsdp`-sized value, and the `Rsdp`
// struct's fields are always initialized.
let maybe_rsdp = unsafe { &*maybe_rsdp_virt_ptr };
match maybe_rsdp.validate() {
Ok(()) => Some(maybe_rsdp_phys_start),
Err(AcpiError::RsdpIncorrectSignature) => None,
Err(err) => {
log::warn!("Invalid RSDP found at {:#x}: {:?}", maybe_rsdp_phys_start, err);
None
}
}
})
});
match rsdp_address {
Some(address) => {
let rsdp_mapping = unsafe { handler.map_physical_region::<Rsdp>(address, mem::size_of::<Rsdp>()) };
Ok(rsdp_mapping)
}
None => Err(AcpiError::NoValidRsdp),
}
}
/// Checks that:
/// 1) The signature is correct
/// 2) The checksum is correct
/// 3) For Version 2.0+, that the extension checksum is correct
pub fn validate(&self) -> Result<(), AcpiError> {
// Check the signature
if self.signature != RSDP_SIGNATURE {
return Err(AcpiError::RsdpIncorrectSignature);
}
// Check the OEM id is valid UTF-8
if str::from_utf8(&self.oem_id).is_err() {
return Err(AcpiError::RsdpInvalidOemId);
}
/*
* `self.length` doesn't exist on ACPI version 1.0, so we mustn't rely on it. Instead,
* check for version 1.0 and use a hard-coded length instead.
*/
let length = if self.revision > 0 {
// For Version 2.0+, include the number of bytes specified by `length`
self.length as usize
} else {
RSDP_V1_LENGTH
};
let bytes = unsafe { slice::from_raw_parts(self as *const Rsdp as *const u8, length) };
let sum = bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
if sum != 0 {
return Err(AcpiError::RsdpInvalidChecksum);
}
Ok(())
}
pub fn signature(&self) -> [u8; 8] {
self.signature
}
pub fn checksum(&self) -> u8 {
self.checksum
}
pub fn oem_id(&self) -> Result<&str, AcpiError> {
str::from_utf8(&self.oem_id).map_err(|_| AcpiError::RsdpInvalidOemId)
}
pub fn revision(&self) -> u8 {
self.revision
}
pub fn rsdt_address(&self) -> u32 {
self.rsdt_address
}
pub fn length(&self) -> u32 {
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
self.length
}
pub fn xsdt_address(&self) -> u64 {
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
self.xsdt_address
}
pub fn ext_checksum(&self) -> u8 {
assert!(self.revision > 0, "Tried to read extended RSDP field with ACPI Version 1.0");
self.ext_checksum
}
}
/// Find the areas we should search for the RSDP in.
fn find_search_areas<H>(handler: H) -> [Range<usize>; 2]
where
H: Handler,
{
/*
* Read the base address of the EBDA from its location in the BDA (BIOS Data Area). Not all BIOSs fill this out
* unfortunately, so we might not get a sensible result. We shift it left 4, as it's a segment address.
*/
let ebda_start_mapping =
unsafe { handler.map_physical_region::<u16>(EBDA_START_SEGMENT_PTR, mem::size_of::<u16>()) };
let ebda_start = (*ebda_start_mapping as usize) << 4;
[
/*
* The main BIOS area below 1MiB. In practice, from my [Restioson's] testing, the RSDP is more often here
* than the EBDA. We also don't want to search the entire possibele EBDA range, if we've failed to find it
* from the BDA.
*/
RSDP_BIOS_AREA_START..(RSDP_BIOS_AREA_END + 1),
// Check if base segment ptr is in valid range for EBDA base
if (EBDA_EARLIEST_START..EBDA_END).contains(&ebda_start) {
// First KiB of EBDA
ebda_start..ebda_start + 1024
} else {
// We don't know where the EBDA starts, so just search the largest possible EBDA
EBDA_EARLIEST_START..(EBDA_END + 1)
},
]
}
/// This (usually!) contains the base address of the EBDA (Extended Bios Data Area), shifted right by 4
const EBDA_START_SEGMENT_PTR: usize = 0x40e;
/// The earliest (lowest) memory address an EBDA (Extended Bios Data Area) can start
const EBDA_EARLIEST_START: usize = 0x80000;
/// The end of the EBDA (Extended Bios Data Area)
const EBDA_END: usize = 0x9ffff;
/// The start of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
const RSDP_BIOS_AREA_START: usize = 0xe0000;
/// The end of the main BIOS area below 1MiB in which to search for the RSDP (Root System Description Pointer)
const RSDP_BIOS_AREA_END: usize = 0xfffff;
/// The RSDP (Root System Description Pointer)'s signature, "RSD PTR " (note trailing space)
const RSDP_SIGNATURE: [u8; 8] = *b"RSD PTR ";
-68
View File
@@ -1,68 +0,0 @@
use crate::{
AcpiTable,
sdt::{SdtHeader, Signature},
};
use bit_field::BitField;
/// The BGRT table contains information about a boot graphic that was displayed
/// by firmware.
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct Bgrt {
pub header: SdtHeader,
pub version: u16,
pub status: u8,
pub image_type: u8,
pub image_address: u64,
pub image_offset_x: u32,
pub image_offset_y: u32,
}
unsafe impl AcpiTable for Bgrt {
const SIGNATURE: Signature = Signature::BGRT;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Bgrt {
pub fn image_type(&self) -> ImageType {
let img_type = self.image_type;
match img_type {
0 => ImageType::Bitmap,
_ => ImageType::Reserved,
}
}
/// Gets the orientation offset of the image.
/// Degrees are clockwise from the image's default orientation.
pub fn orientation_offset(&self) -> u16 {
let status = self.status;
match status.get_bits(1..3) {
0 => 0,
1 => 90,
2 => 180,
3 => 270,
_ => unreachable!(),
}
}
pub fn was_displayed(&self) -> bool {
let status = self.status;
status.get_bit(0)
}
pub fn image_offset(&self) -> (u32, u32) {
let x = self.image_offset_x;
let y = self.image_offset_y;
(x, y)
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ImageType {
Bitmap,
Reserved,
}
-17
View File
@@ -1,17 +0,0 @@
use crate::sdt::Signature;
use core::sync::atomic::AtomicU32;
#[repr(C)]
pub struct Facs {
pub signature: Signature,
pub length: u32,
pub hardware_signature: u32,
pub firmware_waking_vector: u32,
pub global_lock: AtomicU32,
pub flags: u32,
pub x_firmware_waking_vector: u64,
pub version: u8,
pub _reserved0: [u8; 3],
pub ospm_flags: u32,
pub reserved1: [u8; 24],
}
-522
View File
@@ -1,522 +0,0 @@
use crate::{
AcpiError,
AcpiTable,
address::{AddressSpace, GenericAddress, RawGenericAddress},
sdt::{ExtendedField, SdtHeader, Signature},
};
use bit_field::BitField;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PowerProfile {
Unspecified,
Desktop,
Mobile,
Workstation,
EnterpriseServer,
SohoServer,
AppliancePc,
PerformanceServer,
Tablet,
Reserved(u8),
}
/// Represents the Fixed ACPI Description Table (FADT). This table contains various fixed hardware
/// details, such as the addresses of the hardware register blocks. It also contains a pointer to
/// the Differentiated Definition Block (DSDT).
///
/// In cases where the FADT contains both a 32-bit and 64-bit field for the same address, we should
/// always prefer the 64-bit one. Only if it's zero or the CPU will not allow us to access that
/// address should the 32-bit one be used.
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct Fadt {
pub header: SdtHeader,
pub firmware_ctrl: u32,
pub dsdt_address: u32,
// Used in acpi 1.0; compatibility only, should be zero
_reserved: u8,
pub preferred_pm_profile: u8,
/// On systems with an i8259 PIC, this is the vector the System Control Interrupt (SCI) is wired to. On other systems, this is
/// the Global System Interrupt (GSI) number of the SCI.
///
/// The SCI should be treated as a sharable, level, active-low interrupt.
pub sci_interrupt: u16,
/// The system port address of the SMI Command Port. This port should only be accessed from the boot processor.
/// A value of `0` indicates that System Management Mode is not supported.
///
/// - Writing the value in `acpi_enable` to this port will transfer control of the ACPI hardware registers
/// from the firmware to the OS. You must synchronously wait for the transfer to complete, indicated by the
/// setting of `SCI_EN`.
/// - Writing the value in `acpi_disable` will relinquish ownership of the hardware registers to the
/// firmware. This should only be done if you've previously acquired ownership. Before writing this value,
/// the OS should mask all SCI interrupts and clear the `SCI_EN` bit.
/// - Writing the value in `s4bios_req` requests that the firmware enter the S4 state through the S4BIOS
/// feature. This is only supported if the `S4BIOS_F` flag in the FACS is set.
/// - Writing the value in `pstate_control` yields control of the processor performance state to the OS.
/// If this field is `0`, this feature is not supported.
/// - Writing the value in `c_state_control` tells the firmware that the OS supports `_CST` AML objects and
/// notifications of C State changes.
pub smi_cmd_port: u32,
pub acpi_enable: u8,
pub acpi_disable: u8,
pub s4bios_req: u8,
pub pstate_control: u8,
pub pm1a_event_block: u32,
pub pm1b_event_block: u32,
pub pm1a_control_block: u32,
pub pm1b_control_block: u32,
pub pm2_control_block: u32,
pub pm_timer_block: u32,
pub gpe0_block: u32,
pub gpe1_block: u32,
pub pm1_event_length: u8,
pub pm1_control_length: u8,
pub pm2_control_length: u8,
pub pm_timer_length: u8,
pub gpe0_block_length: u8,
pub gpe1_block_length: u8,
pub gpe1_base: u8,
pub c_state_control: u8,
/// The worst-case latency to enter and exit the C2 state, in microseconds. A value `>100` indicates that the
/// system does not support the C2 state.
pub worst_c2_latency: u16,
/// The worst-case latency to enter and exit the C3 state, in microseconds. A value `>1000` indicates that the
/// system does not support the C3 state.
pub worst_c3_latency: u16,
pub flush_size: u16,
pub flush_stride: u16,
pub duty_offset: u8,
pub duty_width: u8,
pub day_alarm: u8,
pub month_alarm: u8,
pub century: u8,
pub iapc_boot_arch: IaPcBootArchFlags,
_reserved2: u8, // must be 0
pub flags: FixedFeatureFlags,
pub reset_reg: RawGenericAddress,
pub reset_value: u8,
pub arm_boot_arch: ArmBootArchFlags,
pub fadt_minor_version: u8,
pub x_firmware_ctrl: ExtendedField<u64, 2>,
pub x_dsdt_address: ExtendedField<u64, 2>,
pub x_pm1a_event_block: ExtendedField<RawGenericAddress, 2>,
pub x_pm1b_event_block: ExtendedField<RawGenericAddress, 2>,
pub x_pm1a_control_block: ExtendedField<RawGenericAddress, 2>,
pub x_pm1b_control_block: ExtendedField<RawGenericAddress, 2>,
pub x_pm2_control_block: ExtendedField<RawGenericAddress, 2>,
pub x_pm_timer_block: ExtendedField<RawGenericAddress, 2>,
pub x_gpe0_block: ExtendedField<RawGenericAddress, 2>,
pub x_gpe1_block: ExtendedField<RawGenericAddress, 2>,
pub sleep_control_reg: ExtendedField<RawGenericAddress, 2>,
pub sleep_status_reg: ExtendedField<RawGenericAddress, 2>,
pub hypervisor_vendor_id: ExtendedField<u64, 2>,
}
/// ## Safety
/// Implementation properly represents a valid FADT.
unsafe impl AcpiTable for Fadt {
const SIGNATURE: Signature = Signature::FADT;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Fadt {
pub fn validate(&self) -> Result<(), AcpiError> {
unsafe { self.header.validate(crate::sdt::Signature::FADT) }
}
pub fn facs_address(&self) -> Result<usize, AcpiError> {
unsafe {
{ self.x_firmware_ctrl }
.access(self.header.revision)
.filter(|&p| p != 0)
.or(Some(self.firmware_ctrl as u64))
.filter(|&p| p != 0)
.map(|p| p as usize)
.ok_or(AcpiError::InvalidFacsAddress)
}
}
pub fn dsdt_address(&self) -> Result<usize, AcpiError> {
unsafe {
{ self.x_dsdt_address }
.access(self.header.revision)
.filter(|&p| p != 0)
.or(Some(self.dsdt_address as u64))
.filter(|&p| p != 0)
.map(|p| p as usize)
.ok_or(AcpiError::InvalidDsdtAddress)
}
}
pub fn power_profile(&self) -> PowerProfile {
match self.preferred_pm_profile {
0 => PowerProfile::Unspecified,
1 => PowerProfile::Desktop,
2 => PowerProfile::Mobile,
3 => PowerProfile::Workstation,
4 => PowerProfile::EnterpriseServer,
5 => PowerProfile::SohoServer,
6 => PowerProfile::AppliancePc,
7 => PowerProfile::PerformanceServer,
8 => PowerProfile::Tablet,
other => PowerProfile::Reserved(other),
}
}
pub fn pm1a_event_block(&self) -> Result<GenericAddress, AcpiError> {
if let Some(raw) = unsafe { self.x_pm1a_event_block.access(self.header().revision) }
&& raw.address != 0x0
{
return GenericAddress::from_raw(raw);
}
Ok(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.pm1_event_length * 8,
bit_offset: 0,
access_size: 0,
address: self.pm1a_event_block.into(),
})
}
pub fn pm1b_event_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.x_pm1b_event_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.pm1b_event_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.pm1_event_length * 8,
bit_offset: 0,
access_size: 0,
address: self.pm1b_event_block.into(),
}))
} else {
Ok(None)
}
}
pub fn pm1a_control_block(&self) -> Result<GenericAddress, AcpiError> {
if let Some(raw) = unsafe { self.x_pm1a_control_block.access(self.header().revision) }
&& raw.address != 0x0
{
return GenericAddress::from_raw(raw);
}
Ok(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.pm1_control_length * 8,
bit_offset: 0,
access_size: 0,
address: self.pm1a_control_block.into(),
})
}
pub fn pm1b_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.x_pm1b_control_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.pm1b_control_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.pm1_control_length * 8,
bit_offset: 0,
access_size: 0,
address: self.pm1b_control_block.into(),
}))
} else {
Ok(None)
}
}
pub fn pm2_control_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.x_pm2_control_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.pm2_control_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.pm2_control_length * 8,
bit_offset: 0,
access_size: 0,
address: self.pm2_control_block.into(),
}))
} else {
Ok(None)
}
}
/// Attempts to parse the FADT's PWM timer blocks, first returning the extended block, and falling back to
/// parsing the legacy block into a `GenericAddress`.
pub fn pm_timer_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
// ACPI spec indicates `PM_TMR_LEN` should be 4, or otherwise the PM_TMR is not supported.
if self.pm_timer_length != 4 {
return Ok(None);
}
if let Some(raw) = unsafe { self.x_pm_timer_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.pm_timer_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: 32,
bit_offset: 0,
access_size: 0,
address: self.pm_timer_block.into(),
}))
} else {
Ok(None)
}
}
pub fn gpe0_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.x_gpe0_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.gpe0_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.gpe0_block_length * 8,
bit_offset: 0,
access_size: 0,
address: self.gpe0_block.into(),
}))
} else {
Ok(None)
}
}
pub fn gpe1_block(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.x_gpe1_block.access(self.header().revision) }
&& raw.address != 0x0
{
return Ok(Some(GenericAddress::from_raw(raw)?));
}
if self.gpe1_block != 0 {
Ok(Some(GenericAddress {
address_space: AddressSpace::SystemIo,
bit_width: self.gpe1_block_length * 8,
bit_offset: 0,
access_size: 0,
address: self.gpe1_block.into(),
}))
} else {
Ok(None)
}
}
pub fn reset_register(&self) -> Result<GenericAddress, AcpiError> {
GenericAddress::from_raw(self.reset_reg)
}
pub fn sleep_control_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.sleep_control_reg.access(self.header().revision) } {
Ok(Some(GenericAddress::from_raw(raw)?))
} else {
Ok(None)
}
}
pub fn sleep_status_register(&self) -> Result<Option<GenericAddress>, AcpiError> {
if let Some(raw) = unsafe { self.sleep_status_reg.access(self.header().revision) } {
Ok(Some(GenericAddress::from_raw(raw)?))
} else {
Ok(None)
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct FixedFeatureFlags(u32);
impl FixedFeatureFlags {
/// If true, an equivalent to the x86 [WBINVD](https://www.felixcloutier.com/x86/wbinvd) instruction is supported.
/// All caches will be flushed and invalidated upon completion of this instruction,
/// and memory coherency is properly maintained. The cache *SHALL* only contain what OSPM references or allows to be cached.
pub fn supports_equivalent_to_wbinvd(&self) -> bool {
self.0.get_bit(0)
}
/// If true, [WBINVD](https://www.felixcloutier.com/x86/wbinvd) properly flushes all caches and memory coherency is maintained, but caches may not be invalidated.
pub fn wbinvd_flushes_all_caches(&self) -> bool {
self.0.get_bit(1)
}
/// If true, all processors implement the C1 power state.
pub fn all_procs_support_c1_power_state(&self) -> bool {
self.0.get_bit(2)
}
/// If true, the C2 power state is configured to work on a uniprocessor and multiprocessor system.
pub fn c2_configured_for_mp_system(&self) -> bool {
self.0.get_bit(3)
}
/// If true, the power button is handled as a control method device.
/// If false, the power button is handled as a fixed-feature programming model.
pub fn power_button_is_control_method(&self) -> bool {
self.0.get_bit(4)
}
/// If true, the sleep button is handled as a control method device.
/// If false, the sleep button is handled as a fixed-feature programming model.
pub fn sleep_button_is_control_method(&self) -> bool {
self.0.get_bit(5)
}
/// If true, the RTC wake status is not supported in fixed register space.
pub fn no_rtc_wake_in_fixed_register_space(&self) -> bool {
self.0.get_bit(6)
}
/// If true, the RTC alarm function can wake the system from an S4 sleep state.
pub fn rtc_wakes_system_from_s4(&self) -> bool {
self.0.get_bit(7)
}
/// If true, indicates that the PM timer is a 32-bit value.
/// If false, the PM timer is a 24-bit value and the remaining 8 bits are clear.
pub fn pm_timer_is_32_bit(&self) -> bool {
self.0.get_bit(8)
}
/// If true, the system supports docking.
pub fn supports_docking(&self) -> bool {
self.0.get_bit(9)
}
/// If true, the system supports system reset via the reset_reg field of the FADT.
pub fn supports_system_reset_via_fadt(&self) -> bool {
self.0.get_bit(10)
}
/// If true, the system supports no expansion capabilities and the case is sealed.
pub fn case_is_sealed(&self) -> bool {
self.0.get_bit(11)
}
/// If true, the system cannot detect the monitor or keyboard/mouse devices.
pub fn system_is_headless(&self) -> bool {
self.0.get_bit(12)
}
/// If true, OSPM must use a processor instruction after writing to the SLP_TYPx register.
pub fn use_instr_after_write_to_slp_typx(&self) -> bool {
self.0.get_bit(13)
}
/// If set, the platform supports the `PCIEXP_WAKE_STS` and `PCIEXP_WAKE_EN` bits in the PM1 status and enable registers.
pub fn supports_pciexp_wake_in_pm1(&self) -> bool {
self.0.get_bit(14)
}
/// If true, OSPM should use the ACPI power management timer or HPET for monotonically-decreasing timers.
pub fn use_pm_or_hpet_for_monotonically_decreasing_timers(&self) -> bool {
self.0.get_bit(15)
}
/// If true, the contents of the `RTC_STS` register are valid after wakeup from S4.
pub fn rtc_sts_is_valid_after_wakeup_from_s4(&self) -> bool {
self.0.get_bit(16)
}
/// If true, the platform supports OSPM leaving GPE wake events armed prior to an S5 transition.
pub fn ospm_may_leave_gpe_wake_events_armed_before_s5(&self) -> bool {
self.0.get_bit(17)
}
/// If true, all LAPICs must be configured using the cluster destination model when delivering interrupts in logical mode.
pub fn lapics_must_use_cluster_model_for_logical_mode(&self) -> bool {
self.0.get_bit(18)
}
/// If true, all LXAPICs must be configured using physical destination mode.
pub fn local_xapics_must_use_physical_destination_mode(&self) -> bool {
self.0.get_bit(19)
}
/// If true, this system is a hardware-reduced ACPI platform, and software methods are used for fixed-feature functions defined in chapter 4 of the ACPI specification.
pub fn system_is_hw_reduced_acpi(&self) -> bool {
self.0.get_bit(20)
}
/// If true, the system can achieve equal or better power savings in an S0 power state, making an S3 transition useless.
pub fn no_benefit_to_s3(&self) -> bool {
self.0.get_bit(21)
}
}
#[derive(Clone, Copy, Debug)]
pub struct IaPcBootArchFlags(u16);
impl IaPcBootArchFlags {
/// If true, legacy user-accessible devices are available on the LPC and/or ISA buses.
pub fn legacy_devices_are_accessible(&self) -> bool {
self.0.get_bit(0)
}
/// If true, the motherboard exposes an IO port 60/64 keyboard controller, typically implemented as an 8042 microcontroller.
pub fn motherboard_implements_8042(&self) -> bool {
self.0.get_bit(1)
}
/// If true, OSPM *must not* blindly probe VGA hardware.
/// VGA hardware is at MMIO addresses A0000h-BFFFFh and IO ports 3B0h-3BBh and 3C0h-3DFh.
pub fn dont_probe_vga(&self) -> bool {
self.0.get_bit(2)
}
/// If true, OSPM *must not* enable message-signaled interrupts.
pub fn dont_enable_msi(&self) -> bool {
self.0.get_bit(3)
}
/// If true, OSPM *must not* enable PCIe ASPM control.
pub fn dont_enable_pcie_aspm(&self) -> bool {
self.0.get_bit(4)
}
/// If true, OSPM *must not* use the RTC via its IO ports, either because it isn't implemented or is at other addresses;
/// instead, OSPM *MUST* use the time and alarm namespace device control method.
pub fn use_time_and_alarm_namespace_for_rtc(&self) -> bool {
self.0.get_bit(5)
}
}
#[derive(Clone, Copy, Debug)]
pub struct ArmBootArchFlags(u16);
impl ArmBootArchFlags {
/// If true, the system implements PSCI.
pub fn implements_psci(&self) -> bool {
self.0.get_bit(0)
}
/// If true, OSPM must use HVC instead of SMC as the PSCI conduit.
pub fn use_hvc_as_psci_conduit(&self) -> bool {
self.0.get_bit(1)
}
}
-87
View File
@@ -1,87 +0,0 @@
use crate::{
AcpiError,
AcpiTable,
AcpiTables,
Handler,
address::RawGenericAddress,
sdt::{SdtHeader, Signature},
};
use bit_field::BitField;
use log::warn;
#[derive(Debug)]
pub enum PageProtection {
None,
/// Access to the rest of the 4KiB, relative to the base address, will not generate a fault.
Protected4K,
/// Access to the rest of the 64KiB, relative to the base address, will not generate a fault.
Protected64K,
Other,
}
/// Information about the High Precision Event Timer (HPET)
#[derive(Debug)]
pub struct HpetInfo {
pub hardware_rev: u8,
pub num_comparators: u8,
pub main_counter_is_64bits: bool,
pub legacy_irq_capable: bool,
pub pci_vendor_id: u16,
pub base_address: usize,
pub hpet_number: u8,
/// The minimum number of clock ticks that can be set without losing interrupts (for timers in Periodic Mode)
pub clock_tick_unit: u16,
pub page_protection: PageProtection,
}
impl HpetInfo {
pub fn new<H>(tables: &AcpiTables<H>) -> Result<HpetInfo, AcpiError>
where
H: Handler,
{
let Some(hpet) = tables.find_table::<HpetTable>() else { Err(AcpiError::TableNotFound(Signature::HPET))? };
if hpet.base_address.address_space != 0 {
warn!("HPET reported as not in system memory; tables invalid?");
}
let event_timer_block_id = hpet.event_timer_block_id;
Ok(HpetInfo {
hardware_rev: event_timer_block_id.get_bits(0..8) as u8,
num_comparators: event_timer_block_id.get_bits(8..13) as u8,
main_counter_is_64bits: event_timer_block_id.get_bit(13),
legacy_irq_capable: event_timer_block_id.get_bit(15),
pci_vendor_id: event_timer_block_id.get_bits(16..32) as u16,
base_address: hpet.base_address.address as usize,
hpet_number: hpet.hpet_number,
clock_tick_unit: hpet.clock_tick_unit,
page_protection: match hpet.page_protection_and_oem.get_bits(0..4) {
0 => PageProtection::None,
1 => PageProtection::Protected4K,
2 => PageProtection::Protected64K,
3..=15 => PageProtection::Other,
_ => unreachable!(),
},
})
}
}
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct HpetTable {
pub header: SdtHeader,
pub event_timer_block_id: u32,
pub base_address: RawGenericAddress,
pub hpet_number: u8,
pub clock_tick_unit: u16,
/// Bits `0..4` specify the page protection guarantee. Bits `4..8` are reserved for OEM attributes.
pub page_protection_and_oem: u8,
}
unsafe impl AcpiTable for HpetTable {
const SIGNATURE: Signature = Signature::HPET;
fn header(&self) -> &SdtHeader {
&self.header
}
}
-471
View File
@@ -1,471 +0,0 @@
use crate::{
AcpiError,
AcpiTable,
sdt::{ExtendedField, SdtHeader, Signature},
};
use bit_field::BitField;
use core::{
marker::{PhantomData, PhantomPinned},
mem,
pin::Pin,
};
use log::warn;
#[derive(Clone, Copy, Debug)]
pub enum MadtError {
UnexpectedEntry,
InterruptOverrideEntryHasInvalidBus,
InvalidLocalNmiLine,
MpsIntiInvalidPolarity,
MpsIntiInvalidTriggerMode,
WakeupApsTimeout,
}
/// Represents the MADT - this contains the MADT header fields. You can then iterate over a `Madt`
/// to read each entry from it.
///
/// In modern versions of ACPI, the MADT can detail one of four interrupt models:
/// - The ancient dual-i8259 legacy PIC model
/// - The Advanced Programmable Interrupt Controller (APIC) model
/// - The Streamlined Advanced Programmable Interrupt Controller (SAPIC) model (for Itanium systems)
/// - The Generic Interrupt Controller (GIC) model (for ARM systems)
///
/// The MADT is a variable-sized structure consisting of a static header and then a variable number of entries.
/// This type only contains the static portion, and then uses pointer arithmetic to parse the following entries.
/// To make this sound, this type is `!Unpin` - this prevents `Madt` being moved, which would leave
/// the entries behind.
#[repr(C, packed)]
#[derive(Debug)]
pub struct Madt {
pub header: SdtHeader,
pub local_apic_address: u32,
pub flags: u32,
_pinned: PhantomPinned,
}
unsafe impl AcpiTable for Madt {
const SIGNATURE: Signature = Signature::MADT;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Madt {
pub fn entries(self: Pin<&Self>) -> MadtEntryIter<'_> {
let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Madt as *const u8 };
MadtEntryIter {
pointer: unsafe { ptr.add(mem::size_of::<Madt>()) },
remaining_length: self.header.length - mem::size_of::<Madt>() as u32,
_phantom: PhantomData,
}
}
pub fn supports_8259(&self) -> bool {
{ self.flags }.get_bit(0)
}
pub fn get_mpwk_mailbox_addr(self: Pin<&Self>) -> Result<u64, AcpiError> {
for entry in self.entries() {
if let MadtEntry::MultiprocessorWakeup(entry) = entry {
return Ok(entry.mailbox_address);
}
}
Err(AcpiError::InvalidMadt(MadtError::UnexpectedEntry))
}
}
#[derive(Debug)]
pub struct MadtEntryIter<'a> {
pointer: *const u8,
/*
* The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
* whole SDT can only be at most `u32::MAX`.
*/
remaining_length: u32,
_phantom: PhantomData<&'a ()>,
}
#[derive(Debug)]
pub enum MadtEntry<'a> {
LocalApic(&'a LocalApicEntry),
IoApic(&'a IoApicEntry),
InterruptSourceOverride(&'a InterruptSourceOverrideEntry),
NmiSource(&'a NmiSourceEntry),
LocalApicNmi(&'a LocalApicNmiEntry),
LocalApicAddressOverride(&'a LocalApicAddressOverrideEntry),
IoSapic(&'a IoSapicEntry),
LocalSapic(&'a LocalSapicEntry),
PlatformInterruptSource(&'a PlatformInterruptSourceEntry),
LocalX2Apic(&'a LocalX2ApicEntry),
X2ApicNmi(&'a X2ApicNmiEntry),
Gicc(&'a GiccEntry),
Gicd(&'a GicdEntry),
GicMsiFrame(&'a GicMsiFrameEntry),
GicRedistributor(&'a GicRedistributorEntry),
GicInterruptTranslationService(&'a GicInterruptTranslationServiceEntry),
MultiprocessorWakeup(&'a MultiprocessorWakeupEntry),
}
impl<'a> Iterator for MadtEntryIter<'a> {
type Item = MadtEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.remaining_length > 0 {
let entry_pointer = self.pointer;
let header = unsafe { *(self.pointer as *const EntryHeader) };
if header.length as u32 > self.remaining_length {
warn!(
"Invalid entry of type {} in MADT - extending past length of table. Ignoring",
header.entry_type
);
return None;
}
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
self.remaining_length = self.remaining_length.saturating_sub(header.length as u32);
macro_rules! construct_entry {
($entry_type:expr,
$entry_pointer:expr,
$(($value:expr => $variant:path as $type:ty)),*
) => {
match $entry_type {
$(
$value => {
return Some($variant(unsafe {
&*($entry_pointer as *const $type)
}))
}
)*
/*
* These entry types are reserved by the ACPI standard. We should skip them
* if they appear in a real MADT.
*/
0x11..=0x7f => {}
/*
* These entry types are reserved for OEM use. Atm, we just skip them too.
* TODO: work out if we should ever do anything else here
*/
0x80..=0xff => {}
}
}
}
#[rustfmt::skip]
construct_entry!(
header.entry_type,
entry_pointer,
(0x0 => MadtEntry::LocalApic as LocalApicEntry),
(0x1 => MadtEntry::IoApic as IoApicEntry),
(0x2 => MadtEntry::InterruptSourceOverride as InterruptSourceOverrideEntry),
(0x3 => MadtEntry::NmiSource as NmiSourceEntry),
(0x4 => MadtEntry::LocalApicNmi as LocalApicNmiEntry),
(0x5 => MadtEntry::LocalApicAddressOverride as LocalApicAddressOverrideEntry),
(0x6 => MadtEntry::IoSapic as IoSapicEntry),
(0x7 => MadtEntry::LocalSapic as LocalSapicEntry),
(0x8 => MadtEntry::PlatformInterruptSource as PlatformInterruptSourceEntry),
(0x9 => MadtEntry::LocalX2Apic as LocalX2ApicEntry),
(0xa => MadtEntry::X2ApicNmi as X2ApicNmiEntry),
(0xb => MadtEntry::Gicc as GiccEntry),
(0xc => MadtEntry::Gicd as GicdEntry),
(0xd => MadtEntry::GicMsiFrame as GicMsiFrameEntry),
(0xe => MadtEntry::GicRedistributor as GicRedistributorEntry),
(0xf => MadtEntry::GicInterruptTranslationService as GicInterruptTranslationServiceEntry),
(0x10 => MadtEntry::MultiprocessorWakeup as MultiprocessorWakeupEntry)
);
}
None
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct EntryHeader {
pub entry_type: u8,
pub length: u8,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalApicEntry {
pub header: EntryHeader,
pub processor_id: u8,
pub apic_id: u8,
pub flags: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct IoApicEntry {
pub header: EntryHeader,
pub io_apic_id: u8,
_reserved: u8,
pub io_apic_address: u32,
pub global_system_interrupt_base: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct InterruptSourceOverrideEntry {
pub header: EntryHeader,
pub bus: u8, // 0 - ISA bus
pub irq: u8, // This is bus-relative
pub global_system_interrupt: u32,
pub flags: u16,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct NmiSourceEntry {
pub header: EntryHeader,
pub flags: u16,
pub global_system_interrupt: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalApicNmiEntry {
pub header: EntryHeader,
pub processor_id: u8,
pub flags: u16,
pub nmi_line: u8, // Describes which LINTn is the NMI connected to
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalApicAddressOverrideEntry {
pub header: EntryHeader,
_reserved: u16,
pub local_apic_address: u64,
}
/// If this entry is present, the system has an I/O SAPIC, which must be used instead of the I/O
/// APIC.
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct IoSapicEntry {
pub header: EntryHeader,
pub io_apic_id: u8,
_reserved: u8,
pub global_system_interrupt_base: u32,
pub io_sapic_address: u64,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalSapicEntry {
pub header: EntryHeader,
pub processor_id: u8,
pub local_sapic_id: u8,
pub local_sapic_eid: u8,
_reserved: [u8; 3],
pub flags: u32,
pub processor_uid: u32,
/// This string can be used to associate this local SAPIC to a processor defined in the
/// namespace when the `_UID` object is a string. It is a null-terminated ASCII string, and so
/// this field will be `'\0'` if the string is not present, otherwise it extends from the
/// address of this field.
pub processor_uid_string: u8,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct PlatformInterruptSourceEntry {
pub header: EntryHeader,
pub flags: u16,
pub interrupt_type: u8,
pub processor_id: u8,
pub processor_eid: u8,
pub io_sapic_vector: u8,
pub global_system_interrupt: u32,
pub platform_interrupt_source_flags: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalX2ApicEntry {
pub header: EntryHeader,
_reserved: u16,
pub x2apic_id: u32,
pub flags: u32,
pub processor_uid: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct X2ApicNmiEntry {
pub header: EntryHeader,
pub flags: u16,
pub processor_uid: u32,
pub nmi_line: u8,
_reserved: [u8; 3],
}
/// This field will appear for ARM processors that support ACPI and use the Generic Interrupt
/// Controller. In the GICC interrupt model, each logical process has a Processor Device object in
/// the namespace, and uses this structure to convey its GIC information.
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GiccEntry {
pub header: EntryHeader,
_reserved1: u16,
pub cpu_interface_number: u32,
pub processor_uid: u32,
pub flags: u32,
pub parking_protocol_version: u32,
pub performance_interrupt_gsiv: u32,
pub parked_address: u64,
pub gic_registers_address: u64,
pub gic_virtual_registers_address: u64,
pub gic_hypervisor_registers_address: u64,
pub vgic_maintenance_interrupt: u32,
pub gicr_base_address: u64,
pub mpidr: u64,
pub processor_power_efficiency_class: u8,
_reserved2: u8,
/// SPE overflow Interrupt.
///
/// ACPI 6.3 defined this field. It is zero in prior versions or
/// if this processor does not support SPE.
pub spe_overflow_interrupt: u16,
pub trbe_interrupt: ExtendedField<u16, 6>,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicdEntry {
pub header: EntryHeader,
_reserved1: u16,
pub gic_id: u32,
pub physical_base_address: u64,
pub system_vector_base: u32,
/// The GIC version
/// 0x00: Fall back to hardware discovery
/// 0x01: GICv1
/// 0x02: GICv2
/// 0x03: GICv3
/// 0x04: GICv4
/// 0x05-0xff: Reserved for future use
pub gic_version: u8,
_reserved2: [u8; 3],
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicMsiFrameEntry {
pub header: EntryHeader,
_reserved: u16,
pub frame_id: u32,
pub physical_base_address: u64,
pub flags: u32,
pub spi_count: u16,
pub spi_base: u16,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicRedistributorEntry {
pub header: EntryHeader,
_reserved: u16,
pub discovery_range_base_address: u64,
pub discovery_range_length: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicInterruptTranslationServiceEntry {
pub header: EntryHeader,
_reserved1: u16,
pub id: u32,
pub physical_base_address: u64,
_reserved2: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MultiprocessorWakeupEntry {
pub header: EntryHeader,
pub mailbox_version: u16,
_reserved: u32,
pub mailbox_address: u64,
}
#[derive(Debug, PartialEq, Eq)]
pub enum MpProtectedModeWakeupCommand {
Noop = 0,
Wakeup = 1,
Sleep = 2,
AcceptPages = 3,
}
impl From<u16> for MpProtectedModeWakeupCommand {
fn from(value: u16) -> Self {
match value {
0 => MpProtectedModeWakeupCommand::Noop,
1 => MpProtectedModeWakeupCommand::Wakeup,
2 => MpProtectedModeWakeupCommand::Sleep,
3 => MpProtectedModeWakeupCommand::AcceptPages,
_ => panic!("Invalid value for MpProtectedModeWakeupCommand"),
}
}
}
#[repr(C)]
pub struct MultiprocessorWakeupMailbox {
pub command: u16,
_reserved: u16,
pub apic_id: u32,
pub wakeup_vector: u64,
pub reserved_for_os: [u64; 254],
pub reserved_for_firmware: [u64; 256],
}
/// Polarity indicates what signal mode the interrupt line needs to be in to be considered 'active'.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Polarity {
SameAsBus,
ActiveHigh,
ActiveLow,
}
/// Trigger mode of an interrupt, describing how the interrupt is triggered.
///
/// When an interrupt is `Edge` triggered, it is triggered exactly once, when the interrupt
/// signal goes from its opposite polarity to its active polarity.
///
/// For `Level` triggered interrupts, a continuous signal is emitted so long as the interrupt
/// is in its active polarity.
///
/// `SameAsBus`-triggered interrupts will utilize the same interrupt triggering as the system bus
/// they communicate across.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerMode {
SameAsBus,
Edge,
Level,
}
pub fn parse_mps_inti_flags(flags: u16) -> Result<(Polarity, TriggerMode), AcpiError> {
let polarity = match flags.get_bits(0..2) {
0b00 => Polarity::SameAsBus,
0b01 => Polarity::ActiveHigh,
0b11 => Polarity::ActiveLow,
_ => return Err(AcpiError::InvalidMadt(MadtError::MpsIntiInvalidPolarity)),
};
let trigger_mode = match flags.get_bits(2..4) {
0b00 => TriggerMode::SameAsBus,
0b01 => TriggerMode::Edge,
0b11 => TriggerMode::Level,
_ => return Err(AcpiError::InvalidMadt(MadtError::MpsIntiInvalidTriggerMode)),
};
Ok((polarity, trigger_mode))
}
-54
View File
@@ -1,54 +0,0 @@
use crate::{
AcpiTable,
sdt::{SdtHeader, Signature},
};
use core::{fmt, mem, slice};
#[repr(C, packed)]
pub struct Mcfg {
pub header: SdtHeader,
_reserved: u64,
// Followed by `n` entries with format `McfgEntry`
}
/// ### Safety: Implementation properly represents a valid MCFG.
unsafe impl AcpiTable for Mcfg {
const SIGNATURE: Signature = Signature::MCFG;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Mcfg {
/// Returns a slice containing each of the entries in the MCFG table. Where possible, `PlatformInfo.interrupt_model` should
/// be enumerated instead.
pub fn entries(&self) -> &[McfgEntry] {
let length = self.header.length as usize - mem::size_of::<Mcfg>();
// Intentionally round down in case length isn't an exact multiple of McfgEntry size - this
// has been observed on real hardware (see rust-osdev/acpi#58)
let num_entries = length / mem::size_of::<McfgEntry>();
unsafe {
let pointer = (self as *const Mcfg as *const u8).add(mem::size_of::<Mcfg>()) as *const McfgEntry;
slice::from_raw_parts(pointer, num_entries)
}
}
}
impl fmt::Debug for Mcfg {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_struct("Mcfg").field("header", &self.header).field("entries", &self.entries()).finish()
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct McfgEntry {
pub base_address: u64,
pub pci_segment_group: u16,
pub bus_number_start: u8,
pub bus_number_end: u8,
_reserved: u32,
}
-279
View File
@@ -1,279 +0,0 @@
pub mod bgrt;
pub mod facs;
pub mod fadt;
pub mod hpet;
pub mod madt;
pub mod mcfg;
pub mod slit;
pub mod spcr;
pub mod srat;
use crate::AcpiError;
use core::{fmt, mem::MaybeUninit, str};
/// Represents a field which may or may not be present within an ACPI structure, depending on the version of ACPI
/// that a system supports. If the field is not present, it is not safe to treat the data as initialised.
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct ExtendedField<T: Copy, const MIN_REVISION: u8>(MaybeUninit<T>);
impl<T: Copy, const MIN_REVISION: u8> ExtendedField<T, MIN_REVISION> {
/// Access the field if it's present for the given revision of the table.
///
/// ### Safety
/// If a bogus ACPI version is passed, this function may access uninitialised data.
pub unsafe fn access(&self, revision: u8) -> Option<T> {
if revision >= MIN_REVISION { Some(unsafe { self.0.assume_init() }) } else { None }
}
}
/// All SDTs share the same header, and are `length` bytes long. The signature tells us which SDT
/// this is.
///
/// The ACPI Spec (Version 6.4) defines the following SDT signatures:
///
/// * APIC - Multiple APIC Description Table (MADT)
/// * BERT - Boot Error Record Table
/// * BGRT - Boot Graphics Resource Table
/// * CPEP - Corrected Platform Error Polling Table
/// * DSDT - Differentiated System Description Table (DSDT)
/// * ECDT - Embedded Controller Boot Resources Table
/// * EINJ - Error Injection Table
/// * ERST - Error Record Serialization Table
/// * FACP - Fixed ACPI Description Table (FADT)
/// * FACS - Firmware ACPI Control Structure
/// * FPDT - Firmware Performance Data Table
/// * GTDT - Generic Timer Description Table
/// * HEST - Hardware Error Source Table
/// * MSCT - Maximum System Characteristics Table
/// * MPST - Memory Power StateTable
/// * NFIT - NVDIMM Firmware Interface Table
/// * OEMx - OEM Specific Information Tables
/// * PCCT - Platform Communications Channel Table
/// * PHAT - Platform Health Assessment Table
/// * PMTT - Platform Memory Topology Table
/// * PSDT - Persistent System Description Table
/// * RASF - ACPI RAS Feature Table
/// * RSDT - Root System Description Table
/// * SBST - Smart Battery Specification Table
/// * SDEV - Secure DEVices Table
/// * SLIT - System Locality Distance Information Table
/// * SRAT - System Resource Affinity Table
/// * SSDT - Secondary System Description Table
/// * XSDT - Extended System Description Table
///
/// ACPI also reserves the following signatures and the specifications for them can be found [here](https://uefi.org/acpi):
///
/// * AEST - ARM Error Source Table
/// * BDAT - BIOS Data ACPI Table
/// * CDIT - Component Distance Information Table
/// * CEDT - CXL Early Discovery Table
/// * CRAT - Component Resource Attribute Table
/// * CSRT - Core System Resource Table
/// * DBGP - Debug Port Table
/// * DBG2 - Debug Port Table 2 (note: ACPI 6.4 defines this as "DBPG2" but this is incorrect)
/// * DMAR - DMA Remapping Table
/// * DRTM -Dynamic Root of Trust for Measurement Table
/// * ETDT - Event Timer Description Table (obsolete, superseeded by HPET)
/// * HPET - IA-PC High Precision Event Timer Table
/// * IBFT - iSCSI Boot Firmware Table
/// * IORT - I/O Remapping Table
/// * IVRS - I/O Virtualization Reporting Structure
/// * LPIT - Low Power Idle Table
/// * MCFG - PCI Express Memory-mapped Configuration Space base address description table
/// * MCHI - Management Controller Host Interface table
/// * MPAM - ARM Memory Partitioning And Monitoring table
/// * MSDM - Microsoft Data Management Table
/// * PRMT - Platform Runtime Mechanism Table
/// * RGRT - Regulatory Graphics Resource Table
/// * SDEI - Software Delegated Exceptions Interface table
/// * SLIC - Microsoft Software Licensing table
/// * SPCR - Microsoft Serial Port Console Redirection table
/// * SPMI - Server Platform Management Interface table
/// * STAO - _STA Override table
/// * SVKL - Storage Volume Key Data table (Intel TDX only)
/// * TCPA - Trusted Computing Platform Alliance Capabilities Table
/// * TPM2 - Trusted Platform Module 2 Table
/// * UEFI - Unified Extensible Firmware Interface Specification table
/// * WAET - Windows ACPI Emulated Devices Table
/// * WDAT - Watch Dog Action Table
/// * WDRT - Watchdog Resource Table
/// * WPBT - Windows Platform Binary Table
/// * WSMT - Windows Security Mitigations Table
/// * XENV - Xen Project
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct SdtHeader {
pub signature: Signature,
// TODO: Make sure this and other fields are interpreted as little-endian on big-endian machine.
pub length: u32,
pub revision: u8,
pub checksum: u8,
pub oem_id: [u8; 6],
pub oem_table_id: [u8; 8],
pub oem_revision: u32,
pub creator_id: [u8; 4],
pub creator_revision: u32,
}
impl SdtHeader {
/// Checks that:
/// 1. The signature matches the one given.
/// 2. The values of various fields in the header are allowed.
/// 3. The checksum of the SDT is valid.
///
/// ### Safety
/// The entire `length` bytes of the SDT must be mapped to compute the checksum.
pub unsafe fn validate(&self, signature: Signature) -> Result<(), AcpiError> {
// Check the signature
if self.signature != signature || str::from_utf8(&self.signature.0).is_err() {
return Err(AcpiError::SdtInvalidSignature(signature));
}
// Check the OEM id
if str::from_utf8(&self.oem_id).is_err() {
return Err(AcpiError::SdtInvalidOemId(signature));
}
// Check the OEM table id
if str::from_utf8(&self.oem_table_id).is_err() {
return Err(AcpiError::SdtInvalidTableId(signature));
}
// Check the checksum
let table_bytes =
unsafe { core::slice::from_raw_parts((self as *const SdtHeader).cast::<u8>(), self.length as usize) };
let sum = table_bytes.iter().fold(0u8, |sum, &byte| sum.wrapping_add(byte));
if sum != 0 {
return Err(AcpiError::SdtInvalidChecksum(signature));
}
Ok(())
}
#[inline]
pub fn length(&self) -> u32 {
self.length
}
#[inline]
pub fn revision(&self) -> u8 {
self.revision
}
#[inline]
pub fn checksum(&self) -> u8 {
self.checksum
}
pub fn oem_id(&self) -> Result<&str, AcpiError> {
str::from_utf8(&self.oem_id).map_err(|_| AcpiError::SdtInvalidOemId(self.signature))
}
pub fn oem_table_id(&self) -> Result<&str, AcpiError> {
str::from_utf8(&self.oem_table_id).map_err(|_| AcpiError::SdtInvalidTableId(self.signature))
}
#[inline]
pub fn oem_revision(&self) -> u32 {
self.oem_revision
}
#[inline]
pub fn creator_id(&self) -> Result<&str, AcpiError> {
str::from_utf8(&self.creator_id).map_err(|_| AcpiError::SdtInvalidCreatorId(self.signature))
}
#[inline]
pub fn creator_revision(&self) -> u32 {
self.creator_revision
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Signature([u8; 4]);
impl Signature {
pub const RSDT: Signature = Signature(*b"RSDT");
pub const XSDT: Signature = Signature(*b"XSDT");
pub const FADT: Signature = Signature(*b"FACP");
pub const HPET: Signature = Signature(*b"HPET");
pub const MADT: Signature = Signature(*b"APIC");
pub const MCFG: Signature = Signature(*b"MCFG");
pub const SSDT: Signature = Signature(*b"SSDT");
pub const BERT: Signature = Signature(*b"BERT");
pub const BGRT: Signature = Signature(*b"BGRT");
pub const CPEP: Signature = Signature(*b"CPEP");
pub const DSDT: Signature = Signature(*b"DSDT");
pub const ECDT: Signature = Signature(*b"ECDT");
pub const EINJ: Signature = Signature(*b"EINJ");
pub const ERST: Signature = Signature(*b"ERST");
pub const FACS: Signature = Signature(*b"FACS");
pub const FPDT: Signature = Signature(*b"FPDT");
pub const GTDT: Signature = Signature(*b"GTDT");
pub const HEST: Signature = Signature(*b"HEST");
pub const MSCT: Signature = Signature(*b"MSCT");
pub const MPST: Signature = Signature(*b"MPST");
pub const NFIT: Signature = Signature(*b"NFIT");
pub const PCCT: Signature = Signature(*b"PCCT");
pub const PHAT: Signature = Signature(*b"PHAT");
pub const PMTT: Signature = Signature(*b"PMTT");
pub const PSDT: Signature = Signature(*b"PSDT");
pub const RASF: Signature = Signature(*b"RASF");
pub const SBST: Signature = Signature(*b"SBST");
pub const SDEV: Signature = Signature(*b"SDEV");
pub const SLIT: Signature = Signature(*b"SLIT");
pub const SRAT: Signature = Signature(*b"SRAT");
pub const AEST: Signature = Signature(*b"AEST");
pub const BDAT: Signature = Signature(*b"BDAT");
pub const CDIT: Signature = Signature(*b"CDIT");
pub const CEDT: Signature = Signature(*b"CEDT");
pub const CRAT: Signature = Signature(*b"CRAT");
pub const CSRT: Signature = Signature(*b"CSRT");
pub const DBGP: Signature = Signature(*b"DBGP");
pub const DBG2: Signature = Signature(*b"DBG2");
pub const DMAR: Signature = Signature(*b"DMAR");
pub const DRTM: Signature = Signature(*b"DRTM");
pub const ETDT: Signature = Signature(*b"ETDT");
pub const IBFT: Signature = Signature(*b"IBFT");
pub const IORT: Signature = Signature(*b"IORT");
pub const IVRS: Signature = Signature(*b"IVRS");
pub const LPIT: Signature = Signature(*b"LPIT");
pub const MCHI: Signature = Signature(*b"MCHI");
pub const MPAM: Signature = Signature(*b"MPAM");
pub const MSDM: Signature = Signature(*b"MSDM");
pub const PRMT: Signature = Signature(*b"PRMT");
pub const RGRT: Signature = Signature(*b"RGRT");
pub const SDEI: Signature = Signature(*b"SDEI");
pub const SLIC: Signature = Signature(*b"SLIC");
pub const SPCR: Signature = Signature(*b"SPCR");
pub const SPMI: Signature = Signature(*b"SPMI");
pub const STAO: Signature = Signature(*b"STAO");
pub const SVKL: Signature = Signature(*b"SVKL");
pub const TCPA: Signature = Signature(*b"TCPA");
pub const TPM2: Signature = Signature(*b"TPM2");
pub const UEFI: Signature = Signature(*b"UEFI");
pub const WAET: Signature = Signature(*b"WAET");
pub const WDAT: Signature = Signature(*b"WDAT");
pub const WDRT: Signature = Signature(*b"WDRT");
pub const WPBT: Signature = Signature(*b"WPBT");
pub const WSMT: Signature = Signature(*b"WSMT");
pub const XENV: Signature = Signature(*b"XENV");
pub fn as_str(&self) -> &str {
str::from_utf8(&self.0).unwrap()
}
}
impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self.as_str())
}
}
-62
View File
@@ -1,62 +0,0 @@
use crate::{
AcpiTable,
sdt::{SdtHeader, Signature},
};
use core::{marker::PhantomPinned, mem, pin::Pin, slice};
use log::warn;
/// The SLIT (System Locality Information Table) provides a matrix of relative distances between
/// all proximity domains. It encodes a list of N*N entries, where each entry is `u8` and the
/// relative distance between proximity domains `(i, j)` is the entry at `i*N+j`. The distance
/// between a proximity domain and itself is normalised to a value of `10`.
#[derive(Debug)]
#[repr(C, packed)]
pub struct Slit {
pub header: SdtHeader,
pub num_proximity_domains: u64,
_pinned: PhantomPinned,
}
unsafe impl AcpiTable for Slit {
const SIGNATURE: Signature = Signature::SLIT;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Slit {
pub fn matrix(self: Pin<&Self>) -> DistanceMatrix<'_> {
DistanceMatrix { num_proximity_domains: self.num_proximity_domains, matrix: self.matrix_raw() }
}
pub fn matrix_raw(self: Pin<&Self>) -> &[u8] {
let mut num_entries = self.num_proximity_domains * self.num_proximity_domains;
if (mem::size_of::<Slit>() + num_entries as usize * num_entries as usize * mem::size_of::<u8>())
> self.header.length as usize
{
warn!("SLIT too short for given number of proximity domains! Returning empty matrix");
num_entries = 0;
}
unsafe {
let ptr = Pin::into_inner_unchecked(self) as *const Slit as *const u8;
slice::from_raw_parts(ptr.byte_add(mem::size_of::<Slit>()), num_entries as usize)
}
}
}
pub struct DistanceMatrix<'a> {
pub num_proximity_domains: u64,
pub matrix: &'a [u8],
}
impl DistanceMatrix<'_> {
pub fn distance(&self, i: u32, j: u32) -> Option<u8> {
if i as u64 <= self.num_proximity_domains && j as u64 <= self.num_proximity_domains {
Some(self.matrix[i as usize + j as usize * self.num_proximity_domains as usize])
} else {
None
}
}
}
-286
View File
@@ -1,286 +0,0 @@
use crate::{
AcpiError,
AcpiTable,
SdtHeader,
Signature,
address::{GenericAddress, RawGenericAddress},
};
use core::{
num::{NonZeroU8, NonZeroU32},
ptr,
slice,
str::{self, Utf8Error},
};
/// Serial Port Console Redirection (SPCR) Table.
///
/// The table provides information about the configuration and use of the
/// serial port or non-legacy UART interface. On a system where the BIOS or
/// system firmware uses the serial port for console input/output, this table
/// should be used to convey information about the settings.
///
/// For more information, see [the official documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/serports/serial-port-console-redirection-table).
#[repr(C, packed)]
#[derive(Debug)]
pub struct Spcr {
pub header: SdtHeader,
pub interface_type: u8,
_reserved: [u8; 3],
pub base_address: RawGenericAddress,
pub interrupt_type: u8,
pub irq: u8,
pub global_system_interrupt: u32,
/// The baud rate the BIOS used for redirection.
pub configured_baud_rate: u8,
pub parity: u8,
pub stop_bits: u8,
pub flow_control: u8,
pub terminal_type: u8,
/// Language which the BIOS was redirecting. Must be 0.
pub language: u8,
pub pci_device_id: u16,
pub pci_vendor_id: u16,
pub pci_bus_number: u8,
pub pci_device_number: u8,
pub pci_function_number: u8,
pub pci_flags: u32,
/// PCI segment number. systems with fewer than 255 PCI buses, this number
/// will be 0.
pub pci_segment: u8,
pub uart_clock_freq: u32,
pub precise_baud_rate: u32,
pub namespace_string_length: u16,
pub namespace_string_offset: u16,
}
unsafe impl AcpiTable for Spcr {
const SIGNATURE: Signature = Signature::SPCR;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Spcr {
/// Gets the type of the register interface.
pub fn interface_type(&self) -> SpcrInterfaceType {
SpcrInterfaceType::from(self.interface_type)
}
/// The base address of the Serial Port register set, if if console
/// redirection is enabled.
pub fn base_address(&self) -> Option<Result<GenericAddress, AcpiError>> {
(!self.base_address.is_empty()).then(|| GenericAddress::from_raw(self.base_address))
}
fn configured_baud_rate(&self) -> Option<NonZeroU32> {
match self.configured_baud_rate {
3 => unsafe { Some(NonZeroU32::new_unchecked(9600)) },
4 => unsafe { Some(NonZeroU32::new_unchecked(19200)) },
6 => unsafe { Some(NonZeroU32::new_unchecked(57600)) },
7 => unsafe { Some(NonZeroU32::new_unchecked(115200)) },
_ => None,
}
}
/// The baud rate the BIOS used for redirection, if configured.
pub fn baud_rate(&self) -> Option<NonZeroU32> {
NonZeroU32::new(self.precise_baud_rate).or_else(|| self.configured_baud_rate())
}
/// Flow control flags for the UART.
pub fn flow_control(&self) -> SpcrFlowControl {
SpcrFlowControl::from_bits_truncate(self.flow_control)
}
/// Interrupt type(s) used by the UART.
pub fn interrupt_type(&self) -> SpcrInterruptType {
SpcrInterruptType::from_bits_truncate(self.interrupt_type)
}
/// The PC-AT-compatible IRQ used by the UART, if the UART supports it.
/// Support is indicated by the [`interrupt_type`](Self::interrupt_type).
pub fn irq(&self) -> Option<u8> {
self.interrupt_type().contains(SpcrInterruptType::DUAL_8259).then_some(self.irq)
}
/// The Global System Interrupt (GSIV) used by the UART, if the UART
/// supports it. Support is indicated by the
/// [`interrupt_type`](Self::interrupt_type).
pub fn global_system_interrupt(&self) -> Option<u32> {
if self.interrupt_type().difference(SpcrInterruptType::DUAL_8259).is_empty() {
return None;
}
Some(self.global_system_interrupt)
}
/// The terminal protocol the BIOS was using for console redirection.
pub fn terminal_type(&self) -> SpcrTerminalType {
SpcrTerminalType::from_bits_truncate(self.terminal_type)
}
/// If the UART is a PCI device, returns its Device ID.
pub fn pci_device_id(&self) -> Option<u16> {
(self.pci_device_id != 0xffff).then_some(self.pci_device_id)
}
/// If the UART is a PCI device, returns its Vendor ID.
pub fn pci_vendor_id(&self) -> Option<u16> {
(self.pci_vendor_id != 0xffff).then_some(self.pci_vendor_id)
}
/// If the UART is a PCI device, returns its bus number.
pub fn pci_bus_number(&self) -> Option<NonZeroU8> {
NonZeroU8::new(self.pci_bus_number)
}
/// If the UART is a PCI device, returns its device number.
pub fn pci_device_number(&self) -> Option<NonZeroU8> {
NonZeroU8::new(self.pci_device_number)
}
/// If the UART is a PCI device, returns its function number.
pub fn pci_function_number(&self) -> Option<NonZeroU8> {
NonZeroU8::new(self.pci_function_number)
}
/// The UART clock frequency in Hz, if it can be determined.
pub const fn uart_clock_frequency(&self) -> Option<NonZeroU32> {
if self.header.revision <= 2 {
return None;
}
NonZeroU32::new(self.uart_clock_freq)
}
/// An ASCII string to uniquely identify this device. This string consists
/// of a fully qualified reference to the object that represents this
/// device in the ACPI namespace. If no namespace device exists,
/// the namespace string must only contain a single '.'.
pub fn namespace_string(&self) -> Result<&str, Utf8Error> {
let start = ptr::from_ref(self).cast::<u8>();
let bytes = unsafe {
let str_start = start.add(self.namespace_string_offset as usize);
slice::from_raw_parts(str_start, self.namespace_string_length as usize)
};
str::from_utf8(bytes)
}
}
bitflags::bitflags! {
/// Interrupt type(s) used by an UART.
#[derive(Clone, Copy, Debug)]
pub struct SpcrInterruptType: u8 {
/// PC-AT-compatible dual-8259 IRQ interrupt.
const DUAL_8259 = 1 << 0;
/// I/O APIC interrupt (Global System Interrupt).
const IO_APIC = 1 << 1;
/// I/O SAPIC interrupt (Global System Interrupt).
const IO_SAPIC = 1 << 2;
/// ARMH GIC interrupt (Global System Interrupt).
const ARMH_GIC = 1 << 3;
/// RISC-V PLIC/APLIC interrupt (Global System Interrupt).
const RISCV_PLIC = 1 << 4;
}
}
bitflags::bitflags! {
/// The terminal protocol the BIOS uses for console redirection.
#[derive(Clone, Copy, Debug)]
pub struct SpcrTerminalType: u8 {
const VT1000 = 1 << 0;
const EXTENDED_VT1000 = 1 << 1;
const VT_UTF8 = 1 << 2;
const ANSI = 1 << 3;
}
}
bitflags::bitflags! {
/// Flow control flags for the UART.
#[derive(Clone, Copy, Debug)]
pub struct SpcrFlowControl: u8 {
/// DCD required for transmit
const DCD = 1 << 0;
/// RTS/CTS hardware flow control
const RTS_CTS = 1 << 1;
/// XON/XOFF software control
const XON_XOFF = 1 << 2;
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum SpcrInterfaceType {
/// Full 16550 interface
Full16550,
/// Full 16450 interface (must also accept writing to the 16550 FCR register).
Full16450,
/// MAX311xE SPI UART
MAX311xE,
/// Arm PL011 UART
ArmPL011,
/// MSM8x60 (e.g. 8960)
MSM8x60,
/// Nvidia 16550
Nvidia16550,
/// TI OMAP
TiOmap,
/// APM88xxxx
APM88xxxx,
/// MSM8974
Msm8974,
/// SAM5250
Sam5250,
/// Intel USIF
IntelUSIF,
/// i.MX 6
Imx6,
/// (deprecated) Arm SBSA (2.x only) Generic UART supporting only 32-bit accesses
ArmSBSAGeneric32bit,
/// Arm SBSA Generic UART
ArmSBSAGeneric,
/// Arm DCC
ArmDCC,
/// VCM2835
Bcm2835,
/// SDM845 with clock rate of 1.8432 MHz
Sdm845_18432,
/// 16550-compatible with parameters defined in Generic Address Structure
Generic16550,
/// SDM845 with clock rate of 7.372 MHz
Sdm845_7372,
/// Intel LPSS
IntelLPSS,
/// RISC-V SBI console (any supported SBI mechanism)
RiscVSbi,
/// Unknown interface
Unknown(u8),
}
impl From<u8> for SpcrInterfaceType {
fn from(val: u8) -> Self {
match val {
0x00 => Self::Full16550,
0x01 => Self::Full16450,
0x02 => Self::MAX311xE,
0x03 => Self::ArmPL011,
0x04 => Self::MSM8x60,
0x05 => Self::Nvidia16550,
0x06 => Self::TiOmap,
0x08 => Self::APM88xxxx,
0x09 => Self::Msm8974,
0x0A => Self::Sam5250,
0x0B => Self::IntelUSIF,
0x0C => Self::Imx6,
0x0D => Self::ArmSBSAGeneric32bit,
0x0E => Self::ArmSBSAGeneric,
0x0F => Self::ArmDCC,
0x10 => Self::Bcm2835,
0x11 => Self::Sdm845_18432,
0x12 => Self::Generic16550,
0x13 => Self::Sdm845_7372,
0x14 => Self::IntelLPSS,
0x15 => Self::RiscVSbi,
_ => Self::Unknown(val),
}
}
}
-228
View File
@@ -1,228 +0,0 @@
use crate::{
AcpiTable,
sdt::{SdtHeader, Signature},
};
use bit_field::BitField;
use core::{
marker::{PhantomData, PhantomPinned},
mem,
pin::Pin,
};
use log::warn;
/// Represents the SRAT (System Resource Affinity Table). This is a variable length table that
/// allows devices (processors, memory ranges, and generic 'initiators') to be associated with
/// system locality / proximity domains and clock domains.
#[derive(Debug)]
#[repr(C, packed)]
pub struct Srat {
pub header: SdtHeader,
_reserved0: u32,
_reserved1: u64,
_pinned: PhantomPinned,
}
unsafe impl AcpiTable for Srat {
const SIGNATURE: Signature = Signature::SRAT;
fn header(&self) -> &SdtHeader {
&self.header
}
}
impl Srat {
pub fn entries(self: Pin<&Self>) -> SratEntryIter<'_> {
let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Srat as *const u8 };
SratEntryIter {
pointer: unsafe { ptr.add(mem::size_of::<Srat>()) },
remaining_length: self.header.length - mem::size_of::<Srat>() as u32,
_phantom: PhantomData,
}
}
}
#[derive(Debug)]
pub struct SratEntryIter<'a> {
pointer: *const u8,
/*
* The iterator can only have at most `u32::MAX` remaining bytes, because the length of the
* whole SDT can only be at most `u32::MAX`.
*/
remaining_length: u32,
_phantom: PhantomData<&'a ()>,
}
#[derive(Debug)]
pub enum SratEntry<'a> {
LocalApicAffinity(&'a LocalApicAffinity),
MemoryAffinity(&'a MemoryAffinity),
LocalApicX2Affinity(&'a LocalApicX2Affinity),
GiccAffinity(&'a GiccAffinity),
GicItsAffinity(&'a GicItsAffinity),
GicInitiatorAffinity(&'a GicInitiatorAffinity),
}
impl<'a> Iterator for SratEntryIter<'a> {
type Item = SratEntry<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.remaining_length > 0 {
let entry_pointer = self.pointer;
let header = unsafe { *(self.pointer as *const EntryHeader) };
if header.length as u32 > self.remaining_length {
warn!("Invalid entry of type {} in SRAT - extending past length of table. Ignoring", header.typ);
return None;
}
self.pointer = unsafe { self.pointer.byte_offset(header.length as isize) };
self.remaining_length = self.remaining_length.saturating_sub(header.length as u32);
match header.typ {
0 => {
return Some(SratEntry::LocalApicAffinity(unsafe {
&*(entry_pointer as *const LocalApicAffinity)
}));
}
1 => {
return Some(SratEntry::MemoryAffinity(unsafe { &*(entry_pointer as *const MemoryAffinity) }));
}
2 => {
return Some(SratEntry::LocalApicX2Affinity(unsafe {
&*(entry_pointer as *const LocalApicX2Affinity)
}));
}
3 => return Some(SratEntry::GiccAffinity(unsafe { &*(entry_pointer as *const GiccAffinity) })),
4 => {
return Some(SratEntry::GicItsAffinity(unsafe { &*(entry_pointer as *const GicItsAffinity) }));
}
5 => {
return Some(SratEntry::GicInitiatorAffinity(unsafe {
&*(entry_pointer as *const GicInitiatorAffinity)
}));
}
other => warn!("Unrecognised entry in SRAT of type {}", other),
}
}
None
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct EntryHeader {
pub typ: u8,
pub length: u16,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalApicAffinity {
pub header: EntryHeader,
pub proximity_domain_low: u8,
pub apic_id: u8,
pub flags: LocalApicAffinityFlags,
pub local_sapic_eid: u8,
pub proximity_domain_high: [u8; 3],
pub clock_domain: u32,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct LocalApicAffinityFlags: u32 {
const ENABLED = 1;
}
}
impl LocalApicAffinity {
pub fn proximity_domain(&self) -> u32 {
u32::from_le_bytes([
self.proximity_domain_low,
self.proximity_domain_high[0],
self.proximity_domain_high[1],
self.proximity_domain_high[2],
])
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct MemoryAffinity {
pub header: EntryHeader,
pub proximity_domain: u32,
_reserved0: u16,
pub base_address_low: u32,
pub base_address_high: u32,
pub length_low: u32,
pub length_high: u32,
_reserved1: u32,
pub flags: MemoryAffinityFlags,
_reserved2: u64,
}
bitflags::bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct MemoryAffinityFlags: u32 {
const ENABLED = 1;
const HOT_PLUGGABLE = 1 << 1;
const NON_VOLATILE = 1 << 2;
}
}
impl MemoryAffinity {
pub fn base_address(&self) -> u64 {
let mut address = self.base_address_low as u64;
address.set_bits(32..64, self.base_address_high as u64);
address
}
pub fn length(&self) -> u64 {
let mut length = self.length_low as u64;
length.set_bits(32..64, self.base_address_high as u64);
length
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct LocalApicX2Affinity {
pub header: EntryHeader,
_reserved0: u16,
pub proximity_domain: u32,
pub x2apic_id: u32,
pub flags: LocalApicAffinityFlags,
pub clock_domain: u32,
_reserved1: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GiccAffinity {
pub header: EntryHeader,
pub proximity_domain: u32,
pub acpi_processor_uid: u32,
pub flags: u32,
pub clock_domain: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicItsAffinity {
pub header: EntryHeader,
pub proximity_domain: u32,
_reserved0: u16,
pub its_id: u32,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct GicInitiatorAffinity {
pub header: EntryHeader,
_reserved0: u8,
pub device_handle_type: u8,
pub proximity_domain: u32,
pub device_handle: [u8; 16],
pub flags: u32,
_reserved1: u32,
}
-33
View File
@@ -1,33 +0,0 @@
[package]
name = "acpid"
description = "ACPI daemon"
version = "0.1.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
acpi = { path = "../acpi-rs", features = ["alloc", "aml"] }
arrayvec = "0.7.6"
log.workspace = true
num-derive = "0.3"
num-traits = "0.2"
parking_lot.workspace = true
plain.workspace = true
redox_syscall.workspace = true
redox_event.workspace = true
rustc-hash = "1.1.0"
thiserror.workspace = true
ron.workspace = true
serde.workspace = true
amlserde = { path = "../amlserde" }
common = { path = "../common" }
daemon = { path = "../../daemon" }
libredox.workspace = true
redox-scheme.workspace = true
scheme-utils = { path = "../../scheme-utils" }
[lints]
workspace = true
File diff suppressed because it is too large Load Diff
-128
View File
@@ -1,128 +0,0 @@
use std::ops::{Deref, DerefMut};
use common::io::Mmio;
// TODO: Only wrap with Mmio where there are hardware-registers. (Some of these structs seem to be
// ring buffer entries, which are not to be treated the same way).
pub struct DrhdPage {
virt: *mut Drhd,
}
impl DrhdPage {
pub fn map(base_phys: usize) -> syscall::Result<Self> {
assert_eq!(
base_phys % crate::acpi::PAGE_SIZE,
0,
"DRHD registers must be page-aligned"
);
// TODO: Uncachable? Can reads have side-effects?
let virt = unsafe {
common::physmap(
base_phys,
crate::acpi::PAGE_SIZE,
common::Prot::RO,
common::MemoryType::default(),
)?
} as *mut Drhd;
Ok(Self { virt })
}
}
impl Deref for DrhdPage {
type Target = Drhd;
fn deref(&self) -> &Self::Target {
unsafe { &*self.virt }
}
}
impl DerefMut for DrhdPage {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.virt }
}
}
impl Drop for DrhdPage {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.virt.cast(), crate::acpi::PAGE_SIZE);
}
}
}
#[repr(C, packed)]
pub struct DrhdFault {
pub sts: Mmio<u32>,
pub ctrl: Mmio<u32>,
pub data: Mmio<u32>,
pub addr: [Mmio<u32>; 2],
_rsv: [Mmio<u64>; 2],
pub log: Mmio<u64>,
}
#[repr(C, packed)]
pub struct DrhdProtectedMemory {
pub en: Mmio<u32>,
pub low_base: Mmio<u32>,
pub low_limit: Mmio<u32>,
pub high_base: Mmio<u64>,
pub high_limit: Mmio<u64>,
}
#[repr(C, packed)]
pub struct DrhdInvalidation {
pub queue_head: Mmio<u64>,
pub queue_tail: Mmio<u64>,
pub queue_addr: Mmio<u64>,
_rsv: Mmio<u32>,
pub cmpl_sts: Mmio<u32>,
pub cmpl_ctrl: Mmio<u32>,
pub cmpl_data: Mmio<u32>,
pub cmpl_addr: [Mmio<u32>; 2],
}
#[repr(C, packed)]
pub struct DrhdPageRequest {
pub queue_head: Mmio<u64>,
pub queue_tail: Mmio<u64>,
pub queue_addr: Mmio<u64>,
_rsv: Mmio<u32>,
pub sts: Mmio<u32>,
pub ctrl: Mmio<u32>,
pub data: Mmio<u32>,
pub addr: [Mmio<u32>; 2],
}
#[repr(C, packed)]
pub struct DrhdMtrrVariable {
pub base: Mmio<u64>,
pub mask: Mmio<u64>,
}
#[repr(C, packed)]
pub struct DrhdMtrr {
pub cap: Mmio<u64>,
pub def_type: Mmio<u64>,
pub fixed: [Mmio<u64>; 11],
pub variable: [DrhdMtrrVariable; 10],
}
#[repr(C, packed)]
pub struct Drhd {
pub version: Mmio<u32>,
_rsv: Mmio<u32>,
pub cap: Mmio<u64>,
pub ext_cap: Mmio<u64>,
pub gl_cmd: Mmio<u32>,
pub gl_sts: Mmio<u32>,
pub root_table: Mmio<u64>,
pub ctx_cmd: Mmio<u64>,
_rsv1: Mmio<u32>,
pub fault: DrhdFault,
_rsv2: Mmio<u32>,
pub pm: DrhdProtectedMemory,
pub invl: DrhdInvalidation,
_rsv3: Mmio<u64>,
pub intr_table: Mmio<u64>,
pub page_req: DrhdPageRequest,
pub mtrr: DrhdMtrr,
}
-561
View File
@@ -1,561 +0,0 @@
//! DMA Remapping Table -- `DMAR`. This is Intel's implementation of IOMMU functionality, known as
//! VT-d.
//!
//! Too understand what all of these structs mean, refer to the "Intel(R) Virtualization
//! Technology for Directed I/O" specification.
// TODO: Move this code to a separate driver as well?
use std::convert::TryFrom;
use std::ops::Deref;
use std::{fmt, mem};
use common::io::Io as _;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use self::drhd::DrhdPage;
use crate::acpi::{AcpiContext, Sdt, SdtHeader};
pub mod drhd;
#[repr(C, packed)]
pub struct DmarStruct {
pub sdt_header: SdtHeader,
pub host_addr_width: u8,
pub flags: u8,
pub _rsvd: [u8; 10],
// This header is followed by N remapping structures.
}
unsafe impl plain::Plain for DmarStruct {}
/// The DMA Remapping Table
#[derive(Debug)]
pub struct Dmar(Sdt);
impl Dmar {
fn remmapping_structs_area(&self) -> &[u8] {
&self.0.as_slice()[mem::size_of::<DmarStruct>()..]
}
}
impl Deref for Dmar {
type Target = DmarStruct;
fn deref(&self) -> &Self::Target {
plain::from_bytes(self.0.as_slice())
.expect("expected Dmar struct to already have checked the length, and alignment issues should be impossible due to #[repr(packed)]")
}
}
impl Dmar {
// TODO: Again, perhaps put this code into a different driver, and read the table the regular
// way via the acpi scheme?
///
/// Phase E.4 fix: `init` now takes an opt-in flag. DMAR init was
/// previously disabled because MMIO reads (e.g. `gl_sts.read()`) on
/// some real hardware block or spin forever. The MMIO read loop has
/// a hard iteration limit to prevent hangs regardless of hardware
/// behavior, and callers must explicitly opt in via `init_with(..., true)`.
/// The high-level `init(acpi_ctx)` now calls `init_with(acpi_ctx, false)`
/// for safety, so DMAR is **not** initialized by default in this fork.
pub fn init(acpi_ctx: &AcpiContext) {
Self::init_with(acpi_ctx, false)
}
pub fn init_with(acpi_ctx: &AcpiContext, opt_in: bool) {
if !opt_in {
log::debug!("DMAR init skipped (opt-in not set; set REDBEAR_DMAR_INIT=1 to enable)");
return;
}
let dmar_sdt = match acpi_ctx.take_single_sdt(*b"DMAR") {
Some(dmar_sdt) => dmar_sdt,
None => {
log::warn!("Unable to find `DMAR` ACPI table.");
return;
}
};
let dmar = match Dmar::new(dmar_sdt) {
Some(dmar) => dmar,
None => {
log::error!("Failed to parse DMAR table, possibly malformed.");
return;
}
};
log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags);
log::debug!("DMAR: {:?}", dmar);
// Hard cap on DMAR entries to process. Real hardware typically
// has 1-4 DRHDs; cap at 32 to prevent any infinite-iterator
// hang in case of a malformed table.
const MAX_DMAR_ENTRIES: usize = 32;
let mut entry_count = 0;
for dmar_entry in dmar.iter().take(MAX_DMAR_ENTRIES) {
entry_count += 1;
log::debug!("DMAR entry: {:?}", dmar_entry);
match dmar_entry {
DmarEntry::Drhd(dmar_drhd) => {
let drhd = dmar_drhd.map();
log::debug!("VER: {:X}", drhd.version.read());
log::debug!("CAP: {:X}", drhd.cap.read());
log::debug!("EXT_CAP: {:X}", drhd.ext_cap.read());
log::debug!("GCMD: {:X}", drhd.gl_cmd.read());
log::debug!("GSTS: {:X}", drhd.gl_sts.read());
log::debug!("RT: {:X}", drhd.root_table.read());
}
_ => (),
}
}
if entry_count == MAX_DMAR_ENTRIES {
log::warn!(
"DMAR table reached the {} entry cap; truncating further processing",
MAX_DMAR_ENTRIES
);
}
}
fn new(sdt: Sdt) -> Option<Dmar> {
assert_eq!(
sdt.signature, *b"DMAR",
"signature already checked against `DMAR`"
);
if sdt.length() < mem::size_of::<DmarStruct>() {
log::error!(
"The DMAR table was too small ({} B < {} B).",
sdt.length(),
mem::size_of::<Dmar>()
);
return None;
}
// No need to check alignment for #[repr(packed)] structs.
Some(Dmar(sdt))
}
pub fn iter(&self) -> DmarIter<'_> {
DmarIter(DmarRawIter {
bytes: self.remmapping_structs_area(),
})
}
}
/// DMAR DMA Remapping Hardware Unit Definition
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarDrhdHeader {
pub kind: u16,
pub length: u16,
pub flags: u8,
pub _rsv: u8,
pub segment: u16,
pub base: u64,
}
unsafe impl plain::Plain for DmarDrhdHeader {}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DeviceScopeHeader {
pub ty: u8,
pub len: u8,
pub _rsvd: u16,
pub enumeration_id: u8,
pub start_bus_num: u8,
// The variable-sized path comes after.
}
unsafe impl plain::Plain for DeviceScopeHeader {}
pub struct DeviceScope(Box<[u8]>);
impl DeviceScope {
pub fn try_new(raw: &[u8]) -> Option<Self> {
// TODO: Check ty.
let header_bytes = match raw.get(..mem::size_of::<DeviceScopeHeader>()) {
Some(bytes) => bytes,
None => return None,
};
let header = plain::from_bytes::<DeviceScopeHeader>(header_bytes)
.expect("length already checked, and alignment 1 (#[repr(packed)] should suffice");
let len = usize::from(header.len);
if len > raw.len() {
log::warn!("Device scope smaller than len field.");
return None;
}
Some(Self(raw.into()))
}
}
impl fmt::Debug for DeviceScope {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceScope")
.field("header", &*self as &DeviceScopeHeader)
.field("path", &self.path())
.finish()
}
}
impl Deref for DeviceScope {
type Target = DeviceScopeHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes(&self.0)
.expect("expected length to be sufficient, and alignment (due to #[repr(packed)]")
}
}
impl DeviceScope {
pub fn path(&self) -> &[u8] {
&self.0[mem::size_of::<DeviceScopeHeader>()..]
}
}
pub struct DmarDrhd(Box<[u8]>);
impl DmarDrhd {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarDrhdHeader>() {
return None;
}
Some(Self(raw.into()))
}
pub fn device_scope_area(&self) -> &[u8] {
&self.0[mem::size_of::<DmarDrhdHeader>()..]
}
pub fn map(&self) -> DrhdPage {
let base = usize::try_from(self.base).expect("expected u64 to fit within usize");
DrhdPage::map(base).expect("failed to map DRHD registers")
}
}
impl Deref for DmarDrhd {
type Target = DmarDrhdHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes::<DmarDrhdHeader>(&self.0[..mem::size_of::<DmarDrhdHeader>()])
.expect("length is already checked, and alignment 1 (#[repr(packed)] should suffice")
}
}
impl fmt::Debug for DmarDrhd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DmarDrhd")
.field("header", &*self as &DmarDrhd)
// TODO: print out device scopes
.finish()
}
}
/// DMAR Reserved Memory Region Reporting
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarRmrrHeader {
pub kind: u16,
pub length: u16,
pub _rsv: u16,
pub segment: u16,
pub base: u64,
pub limit: u64,
// The device scopes come after.
}
unsafe impl plain::Plain for DmarRmrrHeader {}
pub struct DmarRmrr(Box<[u8]>);
impl DmarRmrr {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarRmrrHeader>() {
return None;
}
Some(Self(raw.into()))
}
}
impl Deref for DmarRmrr {
type Target = DmarRmrrHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes(&self.0[..mem::size_of::<DmarRmrrHeader>()])
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
}
}
impl fmt::Debug for DmarRmrr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DmarRmrr")
.field("header", &*self as &DmarRmrrHeader)
// TODO: print out device scopes
.finish()
}
}
/// DMAR Root Port ATS Capability Reporting
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarAtsrHeader {
kind: u16,
length: u16,
flags: u8,
_rsv: u8,
segment: u16,
// The device scopes come after.
}
unsafe impl plain::Plain for DmarAtsrHeader {}
pub struct DmarAtsr(Box<[u8]>);
impl DmarAtsr {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarAtsrHeader>() {
return None;
}
Some(Self(raw.into()))
}
}
impl Deref for DmarAtsr {
type Target = DmarAtsrHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes(&self.0[..mem::size_of::<DmarAtsrHeader>()])
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
}
}
impl fmt::Debug for DmarAtsr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DmarAtsr")
.field("header", &*self as &DmarAtsrHeader)
// TODO: print out device scopes
.finish()
}
}
/// DMAR Remapping Hardware Static Affinity
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarRhsa {
pub kind: u16,
pub length: u16,
pub _rsv: u32,
pub base: u64,
pub domain: u32,
}
unsafe impl plain::Plain for DmarRhsa {}
impl DmarRhsa {
pub fn try_new(raw: &[u8]) -> Option<Self> {
let bytes = raw.get(..mem::size_of::<DmarRhsa>())?;
let this = plain::from_bytes(bytes)
.expect("length is already checked, and alignment 1 should suffice (#[repr(packed)])");
Some(*this)
}
}
/// DMAR ACPI Name-space Device Declaration
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarAnddHeader {
pub kind: u16,
pub length: u16,
pub _rsv: [u8; 3],
pub acpi_dev: u8,
// The device scopes come after.
}
unsafe impl plain::Plain for DmarAnddHeader {}
pub struct DmarAndd(Box<[u8]>);
impl DmarAndd {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarAnddHeader>() {
return None;
}
Some(Self(raw.into()))
}
}
impl Deref for DmarAndd {
type Target = DmarAnddHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes(&self.0[..mem::size_of::<DmarAnddHeader>()])
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
}
}
impl fmt::Debug for DmarAndd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DmarAndd")
.field("header", &*self as &DmarAnddHeader)
// TODO: print out device scopes
.finish()
}
}
/// DMAR ACPI Name-space Device Declaration
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct DmarSatcHeader {
pub kind: u16,
pub length: u16,
pub flags: u8,
pub _rsvd: u8,
pub seg_num: u16,
// The device scopes come after.
}
unsafe impl plain::Plain for DmarSatcHeader {}
pub struct DmarSatc(Box<[u8]>);
impl DmarSatc {
pub fn try_new(raw: &[u8]) -> Option<Self> {
if raw.len() < mem::size_of::<DmarSatcHeader>() {
return None;
}
Some(Self(raw.into()))
}
}
impl Deref for DmarSatc {
type Target = DmarSatcHeader;
fn deref(&self) -> &Self::Target {
plain::from_bytes(&self.0[..mem::size_of::<DmarSatcHeader>()])
.expect("length already checked, and with #[repr(packed)] alignment should be okay")
}
}
impl fmt::Debug for DmarSatc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DmarSatc")
.field("header", &*self as &DmarSatcHeader)
// TODO: print out device scopes
.finish()
}
}
/// The list of different "Remapping Structure Types".
///
/// Refer to section 8.2 in the VTIO spec (as of revision 3.2).
#[derive(Clone, Copy, Debug, FromPrimitive)]
#[repr(u16)]
pub enum EntryType {
Drhd = 0,
Rmrr = 1,
Atsr = 2,
Rhsa = 3,
Andd = 4,
Satc = 5,
}
/// DMAR Entries
#[derive(Debug)]
pub enum DmarEntry {
Drhd(DmarDrhd),
Rmrr(DmarRmrr),
Atsr(DmarAtsr),
Rhsa(DmarRhsa),
Andd(DmarAndd),
// TODO: "SoC Integrated Address Translation Cache Reporting Structure".
Satc(DmarSatc),
TooShort(EntryType),
Unknown(u16),
}
struct DmarRawIter<'sdt> {
bytes: &'sdt [u8],
}
impl<'sdt> Iterator for DmarRawIter<'sdt> {
type Item = (u16, &'sdt [u8]);
fn next(&mut self) -> Option<Self::Item> {
let type_bytes = match self.bytes.get(..2) {
Some(bytes) => bytes,
None => {
if !self.bytes.is_empty() {
log::warn!("DMAR table ended between two entries.");
}
return None;
}
};
let len_bytes = match self.bytes.get(2..4) {
Some(bytes) => bytes,
None => {
log::warn!("DMAR table ended between two entries.");
return None;
}
};
let remainder = &self.bytes[4..];
let type_bytes = <[u8; 2]>::try_from(type_bytes)
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
let len_bytes = <[u8; 2]>::try_from(type_bytes)
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
let len = u16::from_ne_bytes(len_bytes) as usize;
if len < 4 {
return None;
}
let ty = u16::from_ne_bytes(type_bytes);
if len > remainder.len() {
log::warn!("DMAR remapping structure length was smaller than the remaining length of the table.");
return None;
}
let (current, residue) = self.bytes.split_at(len);
self.bytes = residue;
Some((ty, current))
}
}
pub struct DmarIter<'sdt>(DmarRawIter<'sdt>);
impl Iterator for DmarIter<'_> {
type Item = DmarEntry;
fn next(&mut self) -> Option<Self::Item> {
let (raw_type, raw) = self.0.next()?;
// NOTE: If any of these entries look incorrect, we should simply continue the iterator,
// and instead print a warning.
let entry_type = match EntryType::from_u16(raw_type) {
Some(ty) => ty,
None => {
log::warn!(
"Encountered invalid entry type {} (length {})",
raw_type,
raw.len()
);
return Some(DmarEntry::Unknown(raw_type));
}
};
let item_opt = match entry_type {
EntryType::Drhd => DmarDrhd::try_new(raw).map(DmarEntry::Drhd),
EntryType::Rmrr => DmarRmrr::try_new(raw).map(DmarEntry::Rmrr),
EntryType::Atsr => DmarAtsr::try_new(raw).map(DmarEntry::Atsr),
EntryType::Rhsa => DmarRhsa::try_new(raw).map(DmarEntry::Rhsa),
EntryType::Andd => DmarAndd::try_new(raw).map(DmarEntry::Andd),
EntryType::Satc => DmarSatc::try_new(raw).map(DmarEntry::Satc),
};
let item = item_opt.unwrap_or(DmarEntry::TooShort(entry_type));
Some(item)
}
}
-553
View File
@@ -1,553 +0,0 @@
use acpi::{aml::AmlError, Handle, PciAddress, PhysicalMapping};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use common::io::{Io, Pio};
use num_traits::PrimInt;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fmt::LowerHex;
use std::mem::size_of;
use std::ptr::NonNull;
use std::sync::{Arc, Mutex};
use syscall::PAGE_SIZE;
const PAGE_MASK: usize = !(PAGE_SIZE - 1);
const OFFSET_MASK: usize = PAGE_SIZE - 1;
struct MappedPage {
phys_page: usize,
virt_page: usize,
}
impl MappedPage {
fn new(phys_page: usize) -> std::io::Result<Self> {
let virt_page = unsafe {
common::physmap(
phys_page,
PAGE_SIZE,
common::Prot::RW,
common::MemoryType::default(),
)
.map_err(|error| std::io::Error::from_raw_os_error(error.errno()))?
} as usize;
Ok(Self {
phys_page,
virt_page,
})
}
}
impl Drop for MappedPage {
fn drop(&mut self) {
log::trace!("Drop page {:#x}", self.phys_page);
if let Err(e) = unsafe { libredox::call::munmap(self.virt_page as *mut (), PAGE_SIZE) } {
log::error!("funmap (phys): {:?}", e);
}
}
}
#[derive(Default)]
pub struct AmlPageCache {
page_cache: FxHashMap<usize, MappedPage>,
}
impl AmlPageCache {
/// get a virtual address for the given physical page
fn get_page(&mut self, phys_target: usize) -> std::io::Result<&MappedPage> {
let phys_page = phys_target & PAGE_MASK;
if self.page_cache.contains_key(&phys_page) {
log::trace!("re-using cached page {:#x}", phys_page);
Ok(self
.page_cache
.get(&phys_page)
.expect("could not get page after contains=true"))
} else {
let mapped_page = MappedPage::new(phys_page)?;
log::trace!("adding page {:#x} to cache", mapped_page.phys_page);
self.page_cache.insert(phys_page, mapped_page);
Ok(self
.page_cache
.get(&phys_page)
.expect("can't find page that was just inserted"))
}
}
/// The offset into the virtual slice of T that matches the physical target
fn sized_index<T>(phys_target: usize) -> usize {
assert_eq!(
phys_target & !(size_of::<T>() - 1),
phys_target,
"address {} is not aligned",
phys_target
);
(phys_target & OFFSET_MASK) / size_of::<T>()
}
/// Read from the given physical address
fn read_from_phys<T: PrimInt + LowerHex>(&mut self, phys_target: usize) -> std::io::Result<T> {
let mapped_page = self.get_page(phys_target)?;
let page_as_slice = unsafe {
std::slice::from_raw_parts(
mapped_page.virt_page as *const T,
PAGE_SIZE / size_of::<T>(),
)
};
// for debugging only
let _virt_ptr = page_as_slice[Self::sized_index::<T>(phys_target)..].as_ptr() as usize;
let val = page_as_slice[Self::sized_index::<T>(phys_target)];
log::trace!(
"read {:#x}, virt {:#x}, val {:#x}",
phys_target,
_virt_ptr,
val
);
Ok(val)
}
/// Write to the given physical address
fn write_to_phys<T: PrimInt + LowerHex>(
&mut self,
phys_target: usize,
val: T,
) -> std::io::Result<()> {
let mapped_page = self.get_page(phys_target)?;
let page_as_slice = unsafe {
std::slice::from_raw_parts_mut(
mapped_page.virt_page as *mut T,
PAGE_SIZE / size_of::<T>(),
)
};
// for debugging only
let _virt_ptr = page_as_slice[Self::sized_index::<T>(phys_target)..].as_ptr() as usize;
page_as_slice[Self::sized_index::<T>(phys_target)] = val;
log::trace!(
"write {:#x}, virt {:#x}, val {:#x}",
phys_target,
_virt_ptr,
val
);
Ok(())
}
pub fn clear(&mut self) {
log::trace!("Clear page cache");
self.page_cache.clear();
}
}
#[derive(Clone)]
pub struct AmlPhysMemHandler {
page_cache: Arc<Mutex<AmlPageCache>>,
pci_fd: Arc<Option<libredox::Fd>>,
mutex_state: Arc<Mutex<AmlMutexState>>,
notifications: crate::notifications::AmlNotifications,
}
struct AmlMutexState {
next_id: u32,
/// Mutex id -> (owning thread, recursion depth).
///
/// ACPI mutexes are recursive: the owning thread may acquire the same mutex
/// again (nested `Acquire`), and each `Acquire` must be matched by a
/// `Release`. Tracking ownership is therefore mandatory -- a plain "is it
/// held" set makes a nested acquire wait for a release that only the
/// waiting thread itself could perform, which self-deadlocks the AML
/// interpreter.
held: FxHashMap<u32, (std::thread::ThreadId, u32)>,
}
/// Upper bound on how long an AML mutex acquire may wait.
///
/// acpid is single-threaded: the same thread that evaluates AML also serves the
/// `acpi` scheme socket. A long wait here does not just delay one AML method --
/// it stops acpid answering scheme requests, which blocks the namespace manager
/// that is waiting on that open, which in turn blocks *every* path resolution in
/// the system. Bounding the wait keeps a misbehaving AML method local.
const MAX_MUTEX_WAIT: std::time::Duration = std::time::Duration::from_secs(5);
/// Read from a physical address.
/// Generic parameter must be u8, u16, u32 or u64.
impl AmlPhysMemHandler {
pub fn new(
pci_fd_opt: Option<&libredox::Fd>,
page_cache: Arc<Mutex<AmlPageCache>>,
notifications: crate::notifications::AmlNotifications,
) -> Self {
let pci_fd = if let Some(pci_fd) = pci_fd_opt {
Some(libredox::Fd::new(pci_fd.raw()))
} else {
log::error!("pci_fd is not registered");
None
};
Self {
page_cache,
pci_fd: Arc::new(pci_fd),
mutex_state: Arc::new(Mutex::new(AmlMutexState {
next_id: 1,
held: FxHashMap::default(),
})),
notifications,
}
}
fn pci_call_metadata(kind: u8, addr: PciAddress, off: u16) -> [u64; 2] {
// Segment: u16, at 28 bits
// Bus: u8, 8 bits, 256 total, at 20 bits
// Device: u8, 5 bits, 32 total, at 15 bits
// Function: u8, 3 bits, 8 total, at 12 bits
// Offset: u16, 12 bits, 4096 total, at 0 bits
[
kind.into(),
(u64::from(addr.segment()) << 28)
| (u64::from(addr.bus()) << 20)
| (u64::from(addr.device()) << 15)
| (u64::from(addr.function()) << 12)
| u64::from(off),
]
}
fn read_pci(&self, addr: PciAddress, off: u16, value: &mut [u8]) {
let metadata = Self::pci_call_metadata(1, addr, off);
match &*self.pci_fd {
Some(pci_fd) => match pci_fd.call_ro(value, syscall::CallFlags::empty(), &metadata) {
Ok(_) => {}
Err(err) => {
log::error!("read pci {addr}@{off:04X}:{:02X}: {}", value.len(), err);
}
},
None => {
log::error!(
"read pci {addr}@{off:04X}:{:02X}: pci access not available",
value.len()
);
}
}
}
fn write_pci(&self, addr: PciAddress, off: u16, value: &[u8]) {
let metadata = Self::pci_call_metadata(2, addr, off);
match &*self.pci_fd {
Some(pci_fd) => match pci_fd.call_wo(value, syscall::CallFlags::empty(), &metadata) {
Ok(_) => {}
Err(err) => {
log::error!("write pci {addr}@{off:04X}={value:02X?}: {}", err);
}
},
None => {
log::error!("write pci {addr}@{off:04X}={value:02X?}: pci access not available");
}
}
}
}
impl acpi::Handler for AmlPhysMemHandler {
unsafe fn map_physical_region<T>(&self, phys: usize, size: usize) -> PhysicalMapping<Self, T> {
let phys_page = phys & PAGE_MASK;
let offset = phys & OFFSET_MASK;
let pages = (offset + size + PAGE_SIZE - 1) / PAGE_SIZE;
let map_size = pages * PAGE_SIZE;
let virt_page = common::physmap(
phys_page,
map_size,
common::Prot::RW,
common::MemoryType::default(),
)
.expect("failed to map physical region") as usize;
PhysicalMapping {
physical_start: phys,
virtual_start: NonNull::new((virt_page + offset) as *mut T).unwrap(),
region_length: size,
mapped_length: map_size,
handler: self.clone(),
}
}
fn unmap_physical_region<T>(region: &PhysicalMapping<Self, T>) {
let virt_page = region.virtual_start.addr().get() & PAGE_MASK;
unsafe {
libredox::call::munmap(virt_page as *mut (), region.mapped_length)
.expect("failed to unmap physical region")
}
}
fn read_u8(&self, address: usize) -> u8 {
log::trace!("read u8 {:X}", address);
if let Ok(mut page_cache) = self.page_cache.lock() {
if let Ok(value) = page_cache.read_from_phys::<u8>(address) {
return value;
}
}
log::error!("failed to read u8 {:#x}", address);
0
}
fn read_u16(&self, address: usize) -> u16 {
log::trace!("read u16 {:X}", address);
if let Ok(mut page_cache) = self.page_cache.lock() {
if let Ok(value) = page_cache.read_from_phys::<u16>(address) {
return value;
}
}
log::error!("failed to read u16 {:#x}", address);
0
}
fn read_u32(&self, address: usize) -> u32 {
log::trace!("read u32 {:X}", address);
if let Ok(mut page_cache) = self.page_cache.lock() {
if let Ok(value) = page_cache.read_from_phys::<u32>(address) {
return value;
}
}
log::error!("failed to read u32 {:#x}", address);
0
}
fn read_u64(&self, address: usize) -> u64 {
log::trace!("read u64 {:X}", address);
if let Ok(mut page_cache) = self.page_cache.lock() {
if let Ok(value) = page_cache.read_from_phys::<u64>(address) {
return value;
}
}
log::error!("failed to read u64 {:#x}", address);
0
}
fn write_u8(&self, address: usize, value: u8) {
log::trace!("write u8 {:X} = {:X}", address, value);
if let Ok(mut page_cache) = self.page_cache.lock() {
if page_cache.write_to_phys::<u8>(address, value).is_ok() {
return;
}
}
log::error!("failed to write u8 {:#x}", address);
}
fn write_u16(&self, address: usize, value: u16) {
log::trace!("write u16 {:X} = {:X}", address, value);
if let Ok(mut page_cache) = self.page_cache.lock() {
if page_cache.write_to_phys::<u16>(address, value).is_ok() {
return;
}
}
log::error!("failed to write u16 {:#x}", address);
}
fn write_u32(&self, address: usize, value: u32) {
log::trace!("write u32 {:X} = {:X}", address, value);
if let Ok(mut page_cache) = self.page_cache.lock() {
if page_cache.write_to_phys::<u32>(address, value).is_ok() {
return;
}
}
log::error!("failed to write u32 {:#x}", address);
}
fn write_u64(&self, address: usize, value: u64) {
log::trace!("write u64 {:X} = {:X}", address, value);
if let Ok(mut page_cache) = self.page_cache.lock() {
if page_cache.write_to_phys::<u64>(address, value).is_ok() {
return;
}
}
log::error!("failed to write u64 {:#x}", address);
}
// Pio must be enabled via syscall::iopl
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_io_u8(&self, port: u16) -> u8 {
Pio::<u8>::new(port).read()
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_io_u16(&self, port: u16) -> u16 {
Pio::<u16>::new(port).read()
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn read_io_u32(&self, port: u16) -> u32 {
Pio::<u32>::new(port).read()
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_io_u8(&self, port: u16, value: u8) {
Pio::<u8>::new(port).write(value)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_io_u16(&self, port: u16, value: u16) {
Pio::<u16>::new(port).write(value)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn write_io_u32(&self, port: u16, value: u32) {
Pio::<u32>::new(port).write(value)
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn read_io_u8(&self, port: u16) -> u8 {
log::error!("cannot read u8 from port 0x{port:04X}");
0
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn read_io_u16(&self, port: u16) -> u16 {
log::error!("cannot read u16 from port 0x{port:04X}");
0
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn read_io_u32(&self, port: u16) -> u32 {
log::error!("cannot read u32 from port 0x{port:04X}");
0
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn write_io_u8(&self, port: u16, value: u8) {
log::error!("cannot write 0x{value:02X} to port 0x{port:04X}");
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn write_io_u16(&self, port: u16, value: u16) {
log::error!("cannot write 0x{value:04X} to port 0x{port:04X}");
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn write_io_u32(&self, port: u16, value: u32) {
log::error!("cannot write 0x{value:08X} to port 0x{port:04X}");
}
fn read_pci_u8(&self, addr: PciAddress, off: u16) -> u8 {
let mut value = [0u8];
self.read_pci(addr, off, &mut value);
value[0]
}
fn read_pci_u16(&self, addr: PciAddress, off: u16) -> u16 {
let mut value = [0u8; 2];
self.read_pci(addr, off, &mut value);
u16::from_le_bytes(value)
}
fn read_pci_u32(&self, addr: PciAddress, off: u16) -> u32 {
let mut value = [0u8; 4];
self.read_pci(addr, off, &mut value);
u32::from_le_bytes(value)
}
fn write_pci_u8(&self, addr: PciAddress, off: u16, value: u8) {
self.write_pci(addr, off, &[value]);
}
fn write_pci_u16(&self, addr: PciAddress, off: u16, value: u16) {
self.write_pci(addr, off, &value.to_le_bytes());
}
fn write_pci_u32(&self, addr: PciAddress, off: u16, value: u32) {
self.write_pci(addr, off, &value.to_le_bytes());
}
fn nanos_since_boot(&self) -> u64 {
let ts = libredox::call::clock_gettime(libredox::flag::CLOCK_MONOTONIC)
.expect("failed to get time");
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
}
fn stall(&self, microseconds: u64) {
// ACPI Stall is a short, busy-wait delay. The spec caps it at 100us;
// ACPICA allows up to 255us of slack and rejects anything longer as an
// AML operand error (exsystem.c:129-147). acpid is single-threaded and
// serves the `acpi` scheme on this same thread, so an over-long busy
// spin would stall scheme requests (the head-of-line class). Match
// ACPICA: refuse >255us rather than burn the CPU, warn >100us.
const ACPI_STALL_MAX_US: u64 = 255;
if microseconds > ACPI_STALL_MAX_US {
log::error!(
"acpid: AML Stall({microseconds}us) exceeds the {ACPI_STALL_MAX_US}us cap; refusing (firmware bug)"
);
return;
}
if microseconds > 100 {
log::warn!(
"acpid: AML Stall({microseconds}us) > 100us violates the ACPI spec (firmware bug)"
);
}
let start = std::time::Instant::now();
while start.elapsed().as_micros() < microseconds.into() {
std::hint::spin_loop();
}
}
fn sleep(&self, milliseconds: u64) {
std::thread::sleep(std::time::Duration::from_millis(milliseconds));
}
fn create_mutex(&self) -> Handle {
let mut state = self.mutex_state.lock().unwrap();
let id = state.next_id;
state.next_id += 1;
Handle(id)
}
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> {
// ACPI `_ACQ` timeouts are expressed in MILLISECONDS, and 0xFFFF is the
// spec's "wait forever" value (Linux: ACPI_WAIT_FOREVER,
// include/acpi/actypes.h:459). The previous code multiplied the value by
// 1000, treating it as seconds, which turned a routine wait into hours.
const ACPI_WAIT_FOREVER: u16 = 0xFFFF;
let wait = if timeout == ACPI_WAIT_FOREVER {
MAX_MUTEX_WAIT
} else {
std::time::Duration::from_millis(u64::from(timeout)).min(MAX_MUTEX_WAIT)
};
let deadline = std::time::Instant::now() + wait;
let me = std::thread::current().id();
loop {
{
let mut state = self.mutex_state.lock().unwrap();
match state.held.get_mut(&mutex.0) {
None => {
state.held.insert(mutex.0, (me, 1));
return Ok(());
}
// Nested acquire by the owning thread: bump the depth rather
// than waiting for ourselves to release. Mirrors ACPICA
// acpi_ex_acquire_mutex_object (exmutex.c:140).
Some((owner, depth)) if *owner == me => {
*depth += 1;
return Ok(());
}
Some(_) => {}
}
}
if std::time::Instant::now() >= deadline {
// Observability: a timeout here means an AML mutex stayed held
// longer than we are willing to block the scheme-serving thread.
// Surface it — it is the early-warning signal for the wedge class.
log::warn!(
"acpid: AML mutex {} acquire timed out after {:?}",
mutex.0,
wait
);
return Err(AmlError::MutexAcquireTimeout);
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
}
fn release(&self, mutex: Handle) {
let me = std::thread::current().id();
let mut state = self.mutex_state.lock().unwrap();
match state.held.get_mut(&mutex.0) {
// ACPICA returns AE_NOT_ACQUIRED for a release with acquisition
// depth 0 (exmutex.c:287). The Handler trait cannot return that, but
// a release with no matching acquire is an AML/interpreter bug worth
// surfacing rather than silently hiding.
None => log::warn!(
"acpid: release of AML mutex {} that is not currently acquired",
mutex.0
),
// ACPICA rejects a release by a non-owning thread (exmutex.c:376).
// Do not touch another owner's depth.
Some((owner, _)) if *owner != me => log::warn!(
"acpid: release of AML mutex {} by a non-owning thread",
mutex.0
),
Some((_, depth)) => {
*depth -= 1;
if *depth == 0 {
state.held.remove(&mutex.0);
}
}
}
}
fn handle_notify(&self, device: &str, value: u64) {
log::info!("acpid: AML Notify {} = {:#x}", device, value);
self.notifications.push(device.to_owned(), value);
}
}
-959
View File
@@ -1,959 +0,0 @@
//! SMBIOS / DMI table scanning and parsing.
//!
//! Implements the same algorithm as the Linux kernel's `dmi_scan.c`, adapted
//! for Redox's userspace acpid. Two entry-point conventions are recognized:
//!
//! 1. **SMBIOS 3.x 64-bit entry point** (signature `_SM3_`, preferred when
//! present). Points directly at the structure table via a 64-bit physical
//! address with an explicit length, and has no fixed structure count.
//! 2. **Legacy 32-bit entry point** (signature `_SM_`, with embedded `_DMI_`
//! header 16 bytes later). Provides a structure count and a 32-bit
//! table base address.
//!
//! Both entry points are scanned in the standard 0xF0000-0xFFFFF BIOS
//! anchor region, 16 bytes aligned, with the 64-bit variant preferred.
//!
//! Once the structure table is located we walk it linearly, decoding
//! the structure types that callers actually need:
//!
//! - Type 0 (BIOS Information): vendor, version, release date,
//! BIOS / EC firmware revision.
//! - Type 1 (System Information): manufacturer, product name, version,
//! serial, UUID, SKU, family.
//! - Type 2 (Baseboard Information): manufacturer, product, version,
//! serial, asset tag.
//!
//! The variable-length string area at the tail of each structure is
//! accessed by index (1-based) per the SMBIOS reference spec.
//!
//! Strings that contain only spaces are treated as empty (matching Linux
//! behavior), and a number of defensive validations are applied to
//! tolerate malformed firmware.
use std::fs::File;
use std::io::Read;
use std::str;
use log::{debug, info, warn};
use syscall::PAGE_SIZE;
use common::{MemoryType, Prot};
/// Standard SMBIOS BIOS anchor scan range.
const SMBIOS_ANCHOR_START: usize = 0x000F_0000;
/// 64 KiB scan window (matches Linux `dmi_scan_machine`).
const SMBIOS_ANCHOR_LEN: usize = 0x0001_0000;
/// 16-byte alignment step for anchor scans.
const SMBIOS_ANCHOR_STEP: usize = 16;
/// Sentinel byte string for the 64-bit SMBIOS entry point.
const SMBIOS3_SIG: &[u8; 5] = b"_SM3_";
/// Sentinel byte string for the legacy 32-bit entry point.
const SMBIOS_SIG: &[u8; 4] = b"_SM_";
/// Sentinel for the legacy DMI header (16 bytes into the legacy entry point).
const DMI_SIG: &[u8; 5] = b"_DMI_";
/// Upper bound on a single structure's formatted area. Mirrors Linux
/// (the spec allows 256, but Linux is more conservative). Used as a
/// defensive guard against malformed firmware.
const MAX_STRUCTURE_LENGTH: usize = 256;
/// A single DMI / SMBIOS structure table entry (decoded).
#[derive(Clone, Debug, Default)]
pub struct DmiInfo {
pub bios_vendor: Option<String>,
pub bios_version: Option<String>,
pub bios_date: Option<String>,
pub bios_release: Option<String>,
pub ec_firmware_release: Option<String>,
pub sys_vendor: Option<String>,
pub product_name: Option<String>,
pub product_version: Option<String>,
pub product_serial: Option<String>,
pub product_uuid: Option<String>,
pub product_sku: Option<String>,
pub product_family: Option<String>,
pub board_vendor: Option<String>,
pub board_name: Option<String>,
pub board_version: Option<String>,
pub board_serial: Option<String>,
pub board_asset_tag: Option<String>,
}
/// SMBIOS version that produced this table (major.minor.revision or
/// major.minor for the 32-bit entry point), useful for diagnostics.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SmbiosVersion {
pub major: u8,
pub minor: u8,
pub revision: u8,
}
impl core::fmt::Display for SmbiosVersion {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.revision)
}
}
/// Result of a successful SMBIOS scan.
#[derive(Clone, Debug)]
pub struct SmbiosTable {
/// Major / minor / revision.
pub version: SmbiosVersion,
/// Decoded identity fields.
pub info: DmiInfo,
}
/// Error type for DMI scanning.
#[derive(Debug)]
pub enum DmiError {
/// No SMBIOS entry point could be located.
NotPresent,
/// The SMBIOS entry point was found but failed validation
/// (bad checksum, length out of bounds, etc).
InvalidEntryPoint,
/// The structure table was reported to live outside the
/// representable physical range or overlapped the anchor region
/// in a way that suggests a corrupt entry.
InvalidTableAddress,
/// Mapping physical memory failed.
Map(syscall::error::Error),
/// A structure was so malformed that walking must stop.
MalformedTable,
}
impl core::fmt::Display for DmiError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
DmiError::NotPresent => f.write_str("SMBIOS entry point not present"),
DmiError::InvalidEntryPoint => f.write_str("SMBIOS entry point failed validation"),
DmiError::InvalidTableAddress => f.write_str("SMBIOS structure table address invalid"),
DmiError::Map(e) => write!(f, "physmap failed: {:?}", e),
DmiError::MalformedTable => f.write_str("malformed SMBIOS structure table"),
}
}
}
impl std::error::Error for DmiError {}
/// Map a physical address range as read-only. The mapping is unmapped
/// when the returned `PhysmapGuard` is dropped.
struct PhysmapGuard {
virt: *mut u8,
size: usize,
}
impl PhysmapGuard {
fn map(base_phys: usize, length: usize) -> Result<Self, DmiError> {
let phys_start = base_phys & !(PAGE_SIZE - 1);
let offset_in_page = base_phys - phys_start;
let total = offset_in_page + length;
let pages = total.div_ceil(PAGE_SIZE);
let map_size = pages * PAGE_SIZE;
let virt = unsafe {
common::physmap(phys_start, map_size, Prot { read: true, write: false }, MemoryType::default())
.map_err(|e| DmiError::Map(syscall::error::Error::new(e.errno())))?
};
Ok(Self {
virt: virt as *mut u8,
size: map_size,
})
}
}
impl Drop for PhysmapGuard {
fn drop(&mut self) {
unsafe {
let _ = libredox::call::munmap(self.virt as *mut (), self.size);
}
}
}
/// Locate and decode the SMBIOS structure table.
///
/// Returns `Ok(None)` when no SMBIOS entry point is present (e.g. on
/// embedded firmware that omits SMBIOS, or on very old BIOSes that use
/// only the legacy DMI 2.0 convention). Returns `Err` when scanning
/// failed in a way that suggests the firmware is buggy; callers should
/// log the error and continue without DMI rather than panicking.
pub fn scan() -> Result<Option<SmbiosTable>, DmiError> {
// First try the 64-bit entry point, then fall back to 32-bit.
match scan_anchor(true) {
Ok(Some(table)) => return Ok(Some(table)),
Ok(None) => {}
Err(e) => {
// Don't bail out; the legacy entry point may still be valid.
debug!("SMBIOS3 anchor scan failed: {}", e);
}
}
match scan_anchor(false) {
Ok(Some(table)) => Ok(Some(table)),
// Anchor scan saw no signatures at all -> SMBIOS not present.
Ok(None) => Ok(None),
Err(DmiError::NotPresent) => Ok(None),
Err(e) => Err(e),
}
}
fn scan_anchor(prefer_smbios3: bool) -> Result<Option<SmbiosTable>, DmiError> {
let map = PhysmapGuard::map(SMBIOS_ANCHOR_START, SMBIOS_ANCHOR_LEN)?;
// SAFETY: PhysmapGuard owns the mapping and we read within its bounds.
let bytes = unsafe { std::slice::from_raw_parts(map.virt, SMBIOS_ANCHOR_LEN) };
// The SMBIOS anchor is required to start on a 16-byte boundary
// (this is how the BIOS POST code aligns the structure). We step
// through the F-segment looking for either `_SM3_` (preferred) or
// `_SM_` (legacy). The entry point itself is 24-32 bytes; we read
// 32 bytes from the candidate offset and let the decode functions
// validate length and checksum.
let sig_len = if prefer_smbios3 { 5 } else { 4 };
let mut offset = 0usize;
while offset + 32 <= SMBIOS_ANCHOR_LEN {
let candidate = &bytes[offset..offset + 32];
if prefer_smbios3 {
if &candidate[..sig_len] == SMBIOS3_SIG {
match try_decode_smbios3(candidate) {
Ok(Some(table)) => return Ok(Some(table)),
Ok(None) => {}
Err(e) => {
debug!("SMBIOS3 candidate at {:#x} invalid: {}", offset, e);
}
}
}
} else {
// The legacy entry point requires the `_DMI_` signature
// 16 bytes after `_SM_`. Validate that the candidate is
// structurally plausible before invoking the full decoder.
if &candidate[..sig_len] == SMBIOS_SIG && &candidate[16..21] == DMI_SIG {
match try_decode_smbios_legacy(candidate) {
Ok(Some(table)) => return Ok(Some(table)),
Ok(None) => {}
Err(e) => {
debug!("legacy SMBIOS candidate at {:#x} invalid: {}", offset, e);
}
}
}
}
offset += SMBIOS_ANCHOR_STEP;
}
if offset >= SMBIOS_ANCHOR_LEN {
// Whole F-segment scanned, no anchor found.
Err(DmiError::NotPresent)
} else {
Ok(None)
}
}
/// Try to decode a 32-byte window as a 64-bit SMBIOS 3.x entry point.
/// On success returns `Some(table)`; returns `Ok(None)` if the
/// signature does not match; returns `Err(InvalidEntryPoint)` if
/// validation of an apparent SMBIOS3 anchor fails (length out of
/// bounds, bad checksum). Callers can choose to fall back to the
/// legacy entry point on the latter.
fn try_decode_smbios3(buf: &[u8]) -> Result<Option<SmbiosTable>, DmiError> {
if buf.len() < 24 {
return Ok(None);
}
if &buf[..5] != SMBIOS3_SIG {
return Ok(None);
}
let len = buf[6] as usize;
// Spec mandates >= 24; spec v3.0 errata allow up to 32.
if !(24..=32).contains(&len) {
debug!("SMBIOS3 length {} out of range", len);
return Err(DmiError::InvalidEntryPoint);
}
if buf.len() < len {
return Err(DmiError::InvalidEntryPoint);
}
if !checksum_ok(&buf[..len]) {
debug!("SMBIOS3 checksum failed");
return Err(DmiError::InvalidEntryPoint);
}
// Version: major (u8), minor (u8), revision (u8), big-endian 24-bit.
let version = SmbiosVersion {
major: buf[7],
minor: buf[8],
revision: buf[9],
};
// Structure table length (LE u32 at offset 12) and address (LE u64 at offset 16).
let table_len = u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]) as usize;
let mut addr_bytes = [0u8; 8];
addr_bytes.copy_from_slice(&buf[16..24]);
let table_addr = u64::from_le_bytes(addr_bytes) as usize;
info!(
"SMBIOS {}.{}.{} entry point, table @ {:#x} ({} bytes)",
version.major, version.minor, version.revision, table_addr, table_len
);
if table_addr == 0 || table_len == 0 {
return Err(DmiError::InvalidTableAddress);
}
let info = decode_structure_table(table_addr, table_len, 0, version)?;
Ok(Some(SmbiosTable { version, info }))
}
/// Try to decode a 32-byte window as the legacy 32-bit SMBIOS entry
/// point (with embedded `_DMI_` at offset 16). Returns `Ok(None)` if
/// the signature does not match; returns `Err(InvalidEntryPoint)` if
/// validation of an apparent SMBIOS anchor fails.
///
/// Offsets below use the absolute position in the 32-byte window. The
/// `_DMI_` sub-header lives at byte 16, so DMI-local offsets from the
/// SMBIOS reference spec are offset by +16 here. This matches the
/// Linux kernel's `dmi_present()` parser verbatim.
fn try_decode_smbios_legacy(buf: &[u8]) -> Result<Option<SmbiosTable>, DmiError> {
if buf.len() < 31 {
return Ok(None);
}
if &buf[..4] != SMBIOS_SIG {
return Ok(None);
}
let len = buf[5] as usize;
// The spec says 31, but version 2.1 mistakenly reports 30.
if !(30..=32).contains(&len) {
return Err(DmiError::InvalidEntryPoint);
}
if buf.len() < len {
return Err(DmiError::InvalidEntryPoint);
}
// Checksum covers the `_SM_` EPS structure itself: buf[0..buf[5]].
if !checksum_ok(&buf[..len]) {
debug!("legacy SMBIOS checksum failed");
return Err(DmiError::InvalidEntryPoint);
}
let version = SmbiosVersion {
major: buf[6],
minor: buf[7],
revision: 0,
};
let _max_struct_size = u16::from_be_bytes([buf[8], buf[9]]);
// Embedded `_DMI_` header at absolute offset 16. DMI-local layout:
// 0..5 signature "_DMI_"
// 5 checksum (covers 15 bytes: DMI[0..15])
// 6..8 table length (LE u16)
// 8..12 table address (LE u32)
// 12..14 number of structures (LE u16)
// 14 BCD revision
// 15 reserved
if &buf[16..21] != DMI_SIG {
return Ok(None);
}
// DMI checksum is over 15 bytes starting at the `_DMI_` signature,
// i.e. absolute buf[16..31].
if !checksum_ok(&buf[16..31]) {
debug!("legacy _DMI_ header checksum failed");
return Err(DmiError::InvalidEntryPoint);
}
// Structure count: DMI[12..14] → absolute buf[28..30].
let num_structs = u16::from_le_bytes([buf[28], buf[29]]);
// Table length: DMI[6..8] → absolute buf[22..24].
let total_len = u16::from_le_bytes([buf[22], buf[23]]) as usize;
// Table address: DMI[8..12] → absolute buf[24..28].
let mut addr_bytes = [0u8; 4];
addr_bytes.copy_from_slice(&buf[24..28]);
let table_addr = u32::from_le_bytes(addr_bytes) as usize;
info!(
"SMBIOS {}.{} entry point, {} structures, table @ {:#x} ({} bytes)",
version.major, version.minor, num_structs, table_addr, total_len
);
if table_addr == 0 || total_len == 0 {
return Err(DmiError::InvalidTableAddress);
}
let info = decode_structure_table(table_addr, total_len, num_structs, version)?;
Ok(Some(SmbiosTable { version, info }))
}
/// Decode a SMBIOS structure table located at physical address `base`
/// with `total_len` bytes. For SMBIOS 3.x, `num_structs` is zero
/// (terminated by Type 127); for the legacy entry point it is the
/// declared structure count.
fn decode_structure_table(
base: usize,
total_len: usize,
num_structs: u16,
version: SmbiosVersion,
) -> Result<DmiInfo, DmiError> {
let map = PhysmapGuard::map(base, total_len)?;
let bytes = unsafe { std::slice::from_raw_parts(map.virt, total_len) };
let mut info = DmiInfo::default();
let mut offset = 0usize;
let mut seen = 0u32;
while offset + 4 <= total_len {
if num_structs != 0 && seen >= num_structs as u32 {
break;
}
let header = &bytes[offset..];
let struct_type = header[0];
let struct_len = header[1] as usize;
if struct_len < 4 {
warn!(
"DMI: structure at offset {:#x} has invalid length {}, aborting walk",
offset, struct_len
);
return Err(DmiError::MalformedTable);
}
if struct_len > MAX_STRUCTURE_LENGTH {
warn!(
"DMI: structure at offset {:#x} reports length {}, exceeds cap {}",
offset, struct_len, MAX_STRUCTURE_LENGTH
);
return Err(DmiError::MalformedTable);
}
if offset + struct_len > total_len {
warn!("DMI: structure at offset {:#x} overruns table", offset);
return Err(DmiError::MalformedTable);
}
let structured = &bytes[offset..offset + struct_len];
// The strings section begins immediately after the formatted
// area and runs until the double-NUL terminator.
let strings_start = offset + struct_len;
let mut strings_end = strings_start;
while strings_end + 1 < total_len {
if bytes[strings_end] == 0 && bytes[strings_end + 1] == 0 {
break;
}
strings_end += 1;
}
if strings_end + 1 >= total_len {
warn!("DMI: structure at offset {:#x} has unterminated strings", offset);
return Err(DmiError::MalformedTable);
}
let strings = &bytes[strings_start..strings_end];
match struct_type {
0 => decode_type_0(structured, strings, &mut info, version),
1 => decode_type_1(structured, strings, &mut info),
2 => decode_type_2(structured, strings, &mut info),
// End-of-table marker (type 127). For SMBIOS 3.x tables this
// is the only stop signal.
127 if num_structs == 0 => break,
_ => {}
}
// Advance past formatted area, strings, and the double-NUL
// terminator.
offset = strings_end + 2;
seen += 1;
}
Ok(info)
}
/// Sum the bytes in `buf` and check that the result is zero.
fn checksum_ok(buf: &[u8]) -> bool {
let sum: u8 = buf.iter().fold(0u8, |acc, b| acc.wrapping_add(*b));
sum == 0
}
/// Look up a string in the variable-length string area by 1-based
/// index. Strings containing only spaces are returned as `None` to
/// match Linux semantics (an empty-but-present string should not
/// appear in the `dmi_ident` table).
fn dmi_string(strings: &[u8], index: u8) -> Option<String> {
if index == 0 {
return None;
}
let mut current = 1u8;
let mut start = 0usize;
for (i, &b) in strings.iter().enumerate() {
if b == 0 {
if current == index {
let raw = &strings[start..i];
let trimmed: &[u8] = match raw.iter().position(|c| *c != b' ') {
Some(p) => &raw[p..],
None => &[],
};
// Re-trim trailing spaces.
let end = trimmed
.iter()
.rposition(|c| *c != b' ')
.map(|p| p + 1)
.unwrap_or(0);
let s = &trimmed[..end];
if s.is_empty() {
return None;
}
return str::from_utf8(s).ok().map(|s| s.to_owned());
}
current = current.saturating_add(1);
start = i + 1;
}
}
None
}
/// Decode Type 0 — BIOS Information.
///
/// Reference: DMTF DSP0134 §7.1.
///
/// Offset Size Field
/// 0 1 Type = 0
/// 1 1 Length
/// 2 2 Handle
/// 4 1 Vendor string index
/// 5 1 BIOS Version string index
/// 8 1 BIOS Release Date string index
/// 21 1 BIOS Revision (major)
/// 22 1 BIOS Revision (minor)
/// 23 1 Embedded Controller Firmware Major Release
/// 24 1 Embedded Controller Firmware Minor Release
fn decode_type_0(
s: &[u8],
strings: &[u8],
info: &mut DmiInfo,
_version: SmbiosVersion,
) {
if s.len() < 22 {
return;
}
if info.bios_vendor.is_none() {
info.bios_vendor = dmi_string(strings, s[4]);
}
if info.bios_version.is_none() {
info.bios_version = dmi_string(strings, s[5]);
}
if info.bios_date.is_none() {
info.bios_date = dmi_string(strings, s[8]);
}
if info.bios_release.is_none() && s.len() >= 22 {
// 0xFF means "unsupported" per spec.
if !(s[20] == 0xFF && s[21] == 0xFF) {
info.bios_release = Some(format!("{}.{}", s[20], s[21]));
}
}
if info.ec_firmware_release.is_none() && s.len() >= 24 {
if !(s[22] == 0xFF && s[23] == 0xFF) {
info.ec_firmware_release = Some(format!("{}.{}", s[22], s[23]));
}
}
}
/// Decode Type 1 — System Information.
///
/// Reference: DMTF DSP0134 §7.2.
///
/// Offset Size Field
/// 0 1 Type = 1
/// 1 1 Length
/// 2 2 Handle
/// 4 1 Manufacturer string index
/// 5 1 Product Name string index
/// 6 1 Version string index
/// 7 1 Serial Number string index
/// 8 16 UUID
/// 24 1 Wake-up Type
/// 25 1 SKU Number string index (SMBIOS 2.4+)
/// 26 1 Family string index (SMBIOS 2.4+)
fn decode_type_1(s: &[u8], strings: &[u8], info: &mut DmiInfo) {
if s.len() < 8 {
return;
}
if info.sys_vendor.is_none() {
info.sys_vendor = dmi_string(strings, s[4]);
}
if info.product_name.is_none() {
info.product_name = dmi_string(strings, s[5]);
}
if info.product_version.is_none() {
info.product_version = dmi_string(strings, s[6]);
}
if info.product_serial.is_none() {
info.product_serial = dmi_string(strings, s[7]);
}
if info.product_uuid.is_none() && s.len() >= 24 {
let uuid = &s[8..24];
// Skip all-FF / all-00 sentinels (matches Linux).
let all_ff = uuid.iter().all(|b| *b == 0xFF);
let all_00 = uuid.iter().all(|b| *b == 0x00);
if !(all_ff || all_00) {
// Per SMBIOS 2.6+ the first three fields are little-endian.
// We accept the table as-is; consumers that want a textual
// UUID should parse this manually. We provide the raw hex
// form, which is unambiguous regardless of endianness.
info.product_uuid = Some(format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5],
uuid[6], uuid[7],
uuid[8], uuid[9],
uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]
));
}
}
if s.len() >= 26 {
if info.product_sku.is_none() {
info.product_sku = dmi_string(strings, s[25]);
}
}
if s.len() >= 27 {
if info.product_family.is_none() {
info.product_family = dmi_string(strings, s[26]);
}
}
}
/// Decode Type 2 — Baseboard (a.k.a. Module) Information.
///
/// Reference: DMTF DSP0134 §7.3.
///
/// Offset Size Field
/// 0 1 Type = 2
/// 1 1 Length
/// 2 2 Handle
/// 4 1 Manufacturer string index
/// 5 1 Product string index
/// 6 1 Version string index
/// 7 1 Serial Number string index
/// 8 1 Asset Tag string index
fn decode_type_2(s: &[u8], strings: &[u8], info: &mut DmiInfo) {
if s.len() < 9 {
return;
}
if info.board_vendor.is_none() {
info.board_vendor = dmi_string(strings, s[4]);
}
if info.board_name.is_none() {
info.board_name = dmi_string(strings, s[5]);
}
if info.board_version.is_none() {
info.board_version = dmi_string(strings, s[6]);
}
if info.board_serial.is_none() {
info.board_serial = dmi_string(strings, s[7]);
}
if info.board_asset_tag.is_none() {
info.board_asset_tag = dmi_string(strings, s[8]);
}
}
impl DmiInfo {
/// Format the identity fields as `key=value` lines for the
/// `/scheme/acpi/dmi` "summary" file consumed by
/// `redox-driver-sys` and `redbear-info`.
pub fn to_match_lines(&self) -> String {
let mut out = String::with_capacity(512);
let mut put = |key: &str, value: &Option<String>| {
if let Some(v) = value.as_deref() {
if !v.is_empty() {
out.push_str(key);
out.push('=');
out.push_str(v);
out.push('\n');
}
}
};
put("sys_vendor", &self.sys_vendor);
put("board_vendor", &self.board_vendor);
put("board_name", &self.board_name);
put("board_version", &self.board_version);
put("product_name", &self.product_name);
put("product_version", &self.product_version);
put("bios_version", &self.bios_version);
out
}
}
/// Read a single DMI field as a `String` from `/scheme/acpi/dmi/{field}`.
///
/// This helper exists so that the scheme handler does not need to
/// depend on the DMI scan logic directly; it only needs to know how to
/// map a field name to a stored value. The handler-side mapping
/// (camelCase → snake_case) is done here so we can accept both the
/// i2c-hidd naming (`system_vendor`) and the redox-driver-sys naming
/// (`sys_vendor`).
pub fn read_field(info: Option<&DmiInfo>, field: &str) -> Option<String> {
let info = info?;
let slot = match field {
"system_vendor" | "sys_vendor" => info.sys_vendor.as_ref(),
"product_name" => info.product_name.as_ref(),
"product_version" => info.product_version.as_ref(),
"product_serial" => info.product_serial.as_ref(),
"product_uuid" => info.product_uuid.as_ref(),
"product_sku" => info.product_sku.as_ref(),
"product_family" => info.product_family.as_ref(),
"board_name" => info.board_name.as_ref(),
"board_vendor" => info.board_vendor.as_ref(),
"board_version" => info.board_version.as_ref(),
"board_serial" => info.board_serial.as_ref(),
"board_asset_tag" => info.board_asset_tag.as_ref(),
"bios_vendor" => info.bios_vendor.as_ref(),
"bios_version" => info.bios_version.as_ref(),
"bios_date" => info.bios_date.as_ref(),
"bios_release" => info.bios_release.as_ref(),
"ec_firmware_release" => info.ec_firmware_release.as_ref(),
_ => None,
};
slot.cloned()
}
/// List of valid `/scheme/acpi/dmi/<field>` entries. Order matches
/// the order in which the kernel's `dmi-id` sysfs class files appear,
/// with the additional fields acpid exposes.
pub const DMI_FIELDS: &[&str] = &[
"sys_vendor",
"product_name",
"product_version",
"product_serial",
"product_uuid",
"product_sku",
"product_family",
"board_vendor",
"board_name",
"board_version",
"board_serial",
"board_asset_tag",
"bios_vendor",
"bios_version",
"bios_date",
"bios_release",
"ec_firmware_release",
];
/// Try to load an existing `/scheme/acpi/dmi` cache (if another
/// process already exposed one). This is unused at the moment but
/// kept as a stub for future kernel-side SMBIOS scheme support.
#[allow(dead_code)]
pub fn try_load_existing() -> Option<DmiInfo> {
let mut file = File::open("/scheme/acpi/dmi").ok()?;
let mut s = String::new();
file.read_to_string(&mut s).ok()?;
parse_match_lines(&s)
}
/// Parse a `key=value` blob (one entry per line) into a `DmiInfo`.
#[allow(dead_code)]
pub fn parse_match_lines(s: &str) -> Option<DmiInfo> {
let mut info = DmiInfo::default();
let mut any = false;
for line in s.lines() {
let Some((key, value)) = line.split_once('=') else {
continue;
};
let key = key.trim();
let value = value.trim();
if value.is_empty() {
continue;
}
any = true;
match key {
"sys_vendor" => info.sys_vendor = Some(value.to_owned()),
"product_name" => info.product_name = Some(value.to_owned()),
"product_version" => info.product_version = Some(value.to_owned()),
"product_serial" => info.product_serial = Some(value.to_owned()),
"product_uuid" => info.product_uuid = Some(value.to_owned()),
"product_sku" => info.product_sku = Some(value.to_owned()),
"product_family" => info.product_family = Some(value.to_owned()),
"board_vendor" => info.board_vendor = Some(value.to_owned()),
"board_name" => info.board_name = Some(value.to_owned()),
"board_version" => info.board_version = Some(value.to_owned()),
"board_serial" => info.board_serial = Some(value.to_owned()),
"board_asset_tag" => info.board_asset_tag = Some(value.to_owned()),
"bios_vendor" => info.bios_vendor = Some(value.to_owned()),
"bios_version" => info.bios_version = Some(value.to_owned()),
"bios_date" => info.bios_date = Some(value.to_owned()),
"bios_release" => info.bios_release = Some(value.to_owned()),
"ec_firmware_release" => info.ec_firmware_release = Some(value.to_owned()),
_ => {}
}
}
if any {
Some(info)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checksum_of_known_zero() {
assert!(checksum_ok(&[0u8; 16]));
}
#[test]
fn checksum_rejects_nonzero() {
assert!(!checksum_ok(&[1u8, 2, 3, 4]));
}
#[test]
fn dmi_string_basic() {
let s = b"Foo\0Bar\0Baz\0";
assert_eq!(dmi_string(s, 1).as_deref(), Some("Foo"));
assert_eq!(dmi_string(s, 2).as_deref(), Some("Bar"));
assert_eq!(dmi_string(s, 3).as_deref(), Some("Baz"));
assert!(dmi_string(s, 0).is_none());
assert!(dmi_string(s, 4).is_none());
}
#[test]
fn dmi_string_spaces_are_empty() {
let s = b" \0Real\0";
// Per Linux semantics a string that contains only spaces is empty.
assert!(dmi_string(s, 1).is_none());
assert_eq!(dmi_string(s, 2).as_deref(), Some("Real"));
}
#[test]
fn to_match_lines_skips_empty() {
let info = DmiInfo {
sys_vendor: Some("Framework".to_owned()),
product_name: Some("Laptop 16".to_owned()),
..Default::default()
};
let s = info.to_match_lines();
assert!(s.contains("sys_vendor=Framework"));
assert!(s.contains("product_name=Laptop 16"));
assert!(!s.contains("board_vendor"));
}
#[test]
fn parse_match_lines_roundtrip() {
let src = "sys_vendor=Framework\nproduct_name=Laptop 16\nboard_name=FRANMECP01\n";
let info = parse_match_lines(src).expect("must parse");
assert_eq!(info.sys_vendor.as_deref(), Some("Framework"));
assert_eq!(info.product_name.as_deref(), Some("Laptop 16"));
assert_eq!(info.board_name.as_deref(), Some("FRANMECP01"));
// `to_match_lines` emits fields in a canonical order, so we
// compare field-by-field rather than asserting string equality.
let out = info.to_match_lines();
assert!(out.contains("sys_vendor=Framework\n"));
assert!(out.contains("product_name=Laptop 16\n"));
assert!(out.contains("board_name=FRANMECP01\n"));
}
#[test]
fn read_field_handles_aliases() {
let info = DmiInfo {
sys_vendor: Some("Dell Inc.".to_owned()),
product_name: Some("OptiPlex 7090".to_owned()),
..Default::default()
};
// i2c-hidd uses `system_vendor`; redox-driver-sys uses
// `sys_vendor`. Both must work.
assert_eq!(
read_field(Some(&info), "system_vendor").as_deref(),
Some("Dell Inc.")
);
assert_eq!(
read_field(Some(&info), "sys_vendor").as_deref(),
Some("Dell Inc.")
);
assert_eq!(
read_field(Some(&info), "product_name").as_deref(),
Some("OptiPlex 7090")
);
assert!(read_field(Some(&info), "missing").is_none());
assert!(read_field(None, "sys_vendor").is_none());
}
/// Build a synthetic 32-byte SMBIOS 2.x legacy entry-point
/// window with the given DMI header fields, returning the bytes.
/// This is a unit-test helper, not a real firmware entry point —
/// it only exercises our parser.
fn synth_legacy_eps(
smbios_major: u8,
smbios_minor: u8,
num_structs: u16,
table_addr: u32,
table_len: u16,
) -> [u8; 32] {
let mut buf = [0u8; 32];
buf[..4].copy_from_slice(b"_SM_");
buf[5] = 31; // EPS length
buf[6] = smbios_major;
buf[7] = smbios_minor;
buf[8..10].copy_from_slice(&0u16.to_be_bytes()); // max struct size
buf[16..21].copy_from_slice(b"_DMI_");
buf[22..24].copy_from_slice(&table_len.to_le_bytes());
buf[24..28].copy_from_slice(&table_addr.to_le_bytes());
buf[28..30].copy_from_slice(&num_structs.to_le_bytes());
buf[30] = (smbios_major << 4) | (smbios_minor & 0x0F);
// SMBIOS EPS checksum: sum of buf[0..31] must be 0 mod 256.
let smbios_sum: u8 = buf[..31].iter().copied().fold(0u8, u8::wrapping_add);
buf[4] = (0u8).wrapping_sub(smbios_sum);
// _DMI_ checksum: sum of buf[16..31] must be 0 mod 256.
let dmi_sum: u8 = buf[16..31].iter().copied().fold(0u8, u8::wrapping_add);
buf[21] = (0u8).wrapping_sub(dmi_sum);
buf
}
#[test]
fn try_decode_smbios_legacy_picks_correct_offsets() {
// Build a synthetic EPS that advertises 7 structures at
// physical address 0x12345678, total length 0x400. Verify
// the parser returns those exact values (i.e. it is reading
// from the DMI sub-header, not from the `_SM_` prefix).
let buf = synth_legacy_eps(2, 7, 7, 0x1234_5678, 0x400);
let parsed = try_decode_smbios_legacy(&buf)
.expect("parser should not error")
.expect("parser should succeed");
assert_eq!(parsed.version.major, 2);
assert_eq!(parsed.version.minor, 7);
// We don't decode structures here, only verify header fields
// would be passed correctly. The decoder may return Ok(None)
// because the structure table address is not mapped, so we
// only assert the version here. The legacy decoder routes
// table reading through PhysmapGuard; the unit-level test
// for offsets lives in the checksum/signature tests above.
assert_eq!(parsed.version.revision, 0);
}
#[test]
fn try_decode_smbios_legacy_rejects_bad_dmi_checksum() {
let mut buf = synth_legacy_eps(2, 7, 7, 0x1234_5678, 0x400);
// Flip a bit in the DMI sub-header to break its checksum.
buf[24] ^= 0x01;
// Re-seal the SMBIOS checksum so we exercise the DMI path.
let smbios_sum: u8 = buf[..31].iter().copied().fold(0u8, u8::wrapping_add);
buf[4] = (0u8).wrapping_sub(smbios_sum);
match try_decode_smbios_legacy(&buf) {
Err(DmiError::InvalidEntryPoint) => {}
other => panic!("expected InvalidEntryPoint, got {:?}", other),
}
}
/// Verify that decode_type_1 handles the field layout we depend on.
#[test]
fn decode_type_1_minimum_layout() {
// 4-byte header (type, length, handle_lo, handle_hi) plus the
// seven 1-byte string indices we care about.
let mut s = [0u8; 9];
s[0] = 1; // type
s[1] = 9; // length
s[4] = 1; // manufacturer string
s[5] = 2; // product name string
s[6] = 3; // version string
s[7] = 4; // serial string
let strings = b"Acme Corp\0Widget 3000\0Rev A\0SN12345\0";
let mut info = DmiInfo::default();
decode_type_1(&s, strings, &mut info);
assert_eq!(info.sys_vendor.as_deref(), Some("Acme Corp"));
assert_eq!(info.product_name.as_deref(), Some("Widget 3000"));
assert_eq!(info.product_version.as_deref(), Some("Rev A"));
assert_eq!(info.product_serial.as_deref(), Some("SN12345"));
}
}
-288
View File
@@ -1,288 +0,0 @@
use std::time::Duration;
use acpi::aml::{
op_region::{OpRegion, RegionHandler, RegionSpace},
AmlError,
};
use common::{
io::{Io, Pio},
timeout::Timeout,
};
use log::*;
const EC_DATA: u16 = 0x62;
const EC_SC: u16 = 0x66;
const OBF: u8 = 1 << 0; // output full / data ready for host <> empty
const IBF: u8 = 1 << 1; // input full / data ready for ec <> empty
const CMD: u8 = 1 << 3; // byte in data reg is command <> data
const BURST: u8 = 1 << 4; // burst mode <> normal mode
const SCI_EVT: u8 = 1 << 5; // sci event pending <> not
const SMI_EVT: u8 = 1 << 6; // smi event pending <> not
const RD_EC: u8 = 0x80;
const WR_EC: u8 = 0x81;
const BE_EC: u8 = 0x82;
const BD_EC: u8 = 0x83;
const QR_EC: u8 = 0x84;
const BURST_ACK: u8 = 0x90;
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
#[repr(transparent)]
pub struct ScBits(u8);
#[allow(dead_code)]
impl ScBits {
const fn obf(&self) -> bool {
(self.0 & OBF) != 0
}
const fn ibf(&self) -> bool {
(self.0 & IBF) != 0
}
const fn cmd(&self) -> bool {
(self.0 & CMD) != 0
}
const fn burst(&self) -> bool {
(self.0 & BURST) != 0
}
const fn sci_evt(&self) -> bool {
(self.0 & SCI_EVT) != 0
}
const fn smi_evt(&self) -> bool {
(self.0 & SMI_EVT) != 0
}
}
#[derive(Debug, Clone, Copy)]
pub struct Ec {
sc: u16,
data: u16,
timeout: Duration,
}
impl Ec {
pub fn new() -> Self {
Self {
sc: EC_SC,
data: EC_DATA,
timeout: DEFAULT_EC_TIMEOUT,
}
}
#[allow(dead_code)]
pub fn with_address(sc: u16, data: u16, timeout: Duration) -> Self {
Self { sc, data, timeout }
}
#[inline]
fn read_reg_sc(&self) -> ScBits {
ScBits(Pio::<u8>::new(self.sc).read())
}
#[inline]
fn read_reg_data(&self) -> u8 {
Pio::<u8>::new(self.data).read()
}
#[inline]
fn write_reg_sc(&self, value: u8) {
Pio::<u8>::new(self.sc).write(value);
}
#[inline]
fn write_reg_data(&self, value: u8) {
Pio::<u8>::new(self.data).write(value);
}
#[inline]
fn wait_for_write_ready(&self) -> Option<()> {
let timeout = Timeout::new(self.timeout);
loop {
if !self.read_reg_sc().ibf() {
return Some(());
}
timeout.run().ok()?;
}
}
#[inline]
fn wait_for_read_ready(&self) -> Option<()> {
let timeout = Timeout::new(self.timeout);
loop {
if self.read_reg_sc().obf() {
return Some(());
}
timeout.run().ok()?;
}
}
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
pub fn read(&self, address: u8) -> Option<u8> {
trace!("ec read addr: {:x}", address);
self.wait_for_write_ready()?;
self.write_reg_sc(RD_EC);
self.wait_for_write_ready()?;
self.write_reg_data(address);
self.wait_for_read_ready()?;
let val = self.read_reg_data();
trace!("got: {:x}", val);
Some(val)
}
pub fn write(&self, address: u8, value: u8) -> Option<()> {
trace!("ec write addr: {:x}, with: {:x}", address, value);
self.wait_for_write_ready()?;
self.write_reg_sc(WR_EC);
self.wait_for_write_ready()?;
self.write_reg_data(address);
self.wait_for_write_ready()?;
self.write_reg_data(value);
trace!("done");
Some(())
}
// disabled if not met
// First Access - 400 microseconds
// Subsequent Accesses - 50 microseconds each
// Total Burst Time - 1 millisecond
//Accesses should be responded to within 50 microseconds.
#[allow(dead_code)]
fn enable_burst(&self) -> bool {
trace!("ec burst enable");
self.wait_for_write_ready();
self.write_reg_sc(BE_EC);
self.wait_for_read_ready();
let res = self.read_reg_data() == BURST_ACK;
trace!("success: {}", res);
res
}
#[allow(dead_code)]
fn disable_burst(&self) {
trace!("ec burst disable");
self.wait_for_write_ready();
self.write_reg_sc(BD_EC);
trace!("done");
}
/// Whether the EC has a pending System Control Interrupt event
/// (ACPI 12.4 `SCI_EVT` flag in EC_SC).
pub fn sci_evt_set(&self) -> bool {
self.read_reg_sc().sci_evt()
}
/// `QR_EC` command (ACPI 12.4): returns the next pending query value,
/// or 0 when the queue is empty. Called while `sci_evt_set()` holds.
pub fn query(&mut self) -> u8 {
self.wait_for_write_ready();
self.write_reg_sc(QR_EC);
self.wait_for_read_ready();
self.read_reg_data()
}
//OSPM driver sends this command when the SCI_EVT flag in the EC_SC register is set.
#[allow(dead_code)]
fn queue_query(&mut self) -> u8 {
trace!("ec query");
self.wait_for_write_ready();
self.write_reg_sc(QR_EC);
self.wait_for_read_ready();
let val = self.read_reg_data();
trace!("got: {}", val);
val
}
}
impl RegionHandler for Ec {
fn read_u8(
&self,
region: &acpi::aml::op_region::OpRegion,
offset: usize,
) -> Result<u8, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
}
fn write_u8(
&self,
region: &OpRegion,
offset: usize,
value: u8,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
self.write(offset as u8, value)
.ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
}
fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
// EC is 8-bit; compose 16-bit AML reads as little-endian 8-bit EC reads.
// Cross-referenced with Linux drivers/acpi/ec.c: acpi_ec_read() and
// AML acpi_extract_value() which handles the same byte-decomposition.
let lo = self.read_u8(region, offset)? as u16;
let hi = self.read_u8(region, offset + 1)? as u16;
Ok(lo | (hi << 8))
}
fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let part = self.read_u16(region, offset)? as u32;
let part2 = self.read_u16(region, offset + 2)? as u32;
Ok(part | (part2 << 16))
}
fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let part = self.read_u32(region, offset)? as u64;
let part2 = self.read_u32(region, offset + 4)? as u64;
Ok(part | (part2 << 32))
}
fn write_u16(
&self,
region: &OpRegion,
offset: usize,
value: u16,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
Ok(())
}
fn write_u32(
&self,
region: &OpRegion,
offset: usize,
value: u32,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
self.write_u8(region, offset + 2, bytes[2])?;
self.write_u8(region, offset + 3, bytes[3])?;
Ok(())
}
fn write_u64(
&self,
region: &OpRegion,
offset: usize,
value: u64,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
self.write_u8(region, offset + 2, bytes[2])?;
self.write_u8(region, offset + 3, bytes[3])?;
self.write_u8(region, offset + 4, bytes[4])?;
self.write_u8(region, offset + 5, bytes[5])?;
self.write_u8(region, offset + 6, bytes[6])?;
self.write_u8(region, offset + 7, bytes[7])?;
Ok(())
}
}
-253
View File
@@ -1,253 +0,0 @@
//! GPE (General Purpose Event) and PM1 fixed-event infrastructure.
//!
//! Ported from Linux 7.1 `drivers/acpi/evgpeblk.c` (GPE block enable/status
//! register layout) and `drivers/acpi/events/evxface.c` (PM1 fixed events:
//! power button, sleep button). The GPE block is split in half: the first
//! `len/2` bytes are the status registers (write-1-to-clear), the second
//! `len/2` bytes are the enable registers. acpid owns only the GPEs it
//! explicitly enables (the EC GPE on laptop platforms); all other enable
//! bits are left exactly as firmware set them.
use common::io::{Io, Pio};
use crate::acpi::FadtStruct;
pub const PM_TIMER_FREQUENCY_HZ: u64 = 3_579_545;
pub fn pm_timer_read(fadt: &FadtStruct) -> Option<u32> {
let port = fadt.pm_timer_block as u16;
if fadt.pm_timer_block == 0 || fadt.pm_timer_length == 0 {
return None;
}
Some(Pio::<u32>::new(port).read())
}
// PM1 fixed-event bits in PM1_STS/PM1_EN (ACPI 6.4 §4.8.3.1).
pub const PM1_PWRBTN: u16 = 1 << 8;
pub const PM1_SLPBTN: u16 = 1 << 9;
pub const PM1_RTC: u16 = 1 << 10;
#[derive(Clone, Copy, Debug)]
pub struct GpeBlock {
pub port: u16,
/// Total block length in bytes (status half + enable half).
pub len: u8,
/// First GPE number covered by this block (0 for GPE0, `gpe1_base` for GPE1).
pub base: u8,
}
impl GpeBlock {
fn status_port(&self, gpe: u8) -> Option<(u16, u8)> {
let index = gpe.checked_sub(self.base)?;
let byte = index / 8;
if byte >= self.len / 2 {
return None;
}
Some((self.port + byte as u16, index % 8))
}
fn enable_port(&self, gpe: u8) -> Option<(u16, u8)> {
let index = gpe.checked_sub(self.base)?;
let byte = index / 8;
if byte >= self.len / 2 {
return None;
}
Some((self.port + (self.len / 2) as u16 + byte as u16, index % 8))
}
}
#[derive(Clone, Copy, Debug)]
pub struct GpeBlocks {
pub sci_irq: u16,
pub gpe0: Option<GpeBlock>,
pub gpe1: Option<GpeBlock>,
pub pm1a_event: Option<u16>,
pub pm1b_event: Option<u16>,
/// Total PM1 event block length in bytes (STS half + EN half).
pub pm1_event_len: u8,
}
impl GpeBlocks {
/// Build the register map from the parsed FADT. Blocks with a zero
/// address are absent (ACPI 6.4 §5.2.9). Event-block ports come from the
/// 32-bit FADT fields; the extended X_ GAS variants are used by few
/// laptops and are covered by the 32-bit fields on x86.
pub fn from_fadt(fadt: &FadtStruct) -> Self {
let gpe0 = (fadt.gpe0_block != 0 && fadt.gpe0_ength != 0).then_some(GpeBlock {
port: fadt.gpe0_block as u16,
len: fadt.gpe0_ength,
base: 0,
});
let gpe1 = (fadt.gpe1_block != 0 && fadt.gpe1_length != 0).then_some(GpeBlock {
port: fadt.gpe1_block as u16,
len: fadt.gpe1_length,
base: fadt.gpe1_base,
});
Self {
sci_irq: fadt.sci_interrupt,
gpe0,
gpe1,
pm1a_event: (fadt.pm1a_event_block != 0).then_some(fadt.pm1a_event_block as u16),
pm1b_event: (fadt.pm1b_event_block != 0).then_some(fadt.pm1b_event_block as u16),
pm1_event_len: fadt.pm1_event_length,
}
}
fn block_for(&self, gpe: u8) -> Option<&GpeBlock> {
self.gpe0
.as_ref()
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8)
.or(self
.gpe1
.as_ref()
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8))
}
/// Enable a single GPE (evgpeblk's enable-write-preserve: read-modify-write
/// only the one bit; every other GPE keeps its firmware state).
pub fn enable_gpe(&self, gpe: u8) -> bool {
let Some(block) = self.block_for(gpe) else {
return false;
};
let Some((port, bit)) = block.enable_port(gpe) else {
return false;
};
let mut value = Pio::<u8>::new(port).read();
value |= 1 << bit;
Pio::<u8>::new(port).write(value);
true
}
pub fn gpe_status(&self, gpe: u8) -> bool {
let Some(block) = self.block_for(gpe) else {
return false;
};
let Some((port, bit)) = block.status_port(gpe) else {
return false;
};
Pio::<u8>::new(port).read() & (1 << bit) != 0
}
/// Write-1-to-clear the status bit for one GPE.
pub fn clear_gpe(&self, gpe: u8) {
let Some(block) = self.block_for(gpe) else {
return;
};
let Some((port, bit)) = block.status_port(gpe) else {
return;
};
Pio::<u8>::new(port).write(1 << bit);
}
/// GPEs with both status and enable bits set (evgpe detect semantics).
pub fn enabled_active_gpes(&self) -> Vec<u8> {
let mut out = Vec::new();
for block in [self.gpe0, self.gpe1].into_iter().flatten() {
let half = block.len / 2;
for byte in 0..half {
let status_port = block.port + byte as u16;
let enable_port = block.port + (half as u16) + byte as u16;
let active = Pio::<u8>::new(status_port).read() & Pio::<u8>::new(enable_port).read();
if active == 0 {
continue;
}
for bit in 0..8 {
if active & (1 << bit) != 0 {
out.push(block.base + byte * 8 + bit);
}
}
}
}
out
}
/// PM1 fixed-event status register (16-bit across the a/b blocks).
pub fn pm1_status(&self) -> u16 {
let mut value = 0u16;
if let Some(port) = self.pm1a_event {
value |= Pio::<u16>::new(port).read();
}
if let Some(port) = self.pm1b_event {
value |= Pio::<u16>::new(port).read();
}
value
}
pub fn pm1_clear(&self, bits: u16) {
if let Some(port) = self.pm1a_event {
Pio::<u16>::new(port).write(bits);
}
if let Some(port) = self.pm1b_event {
Pio::<u16>::new(port).write(bits);
}
}
/// Enable fixed events in PM1_EN (read-modify-write; other enable bits
/// keep their firmware state). PM1_EN lives `pm1_event_len / 2` bytes
/// after PM1_STS within each event block (ACPI 6.4 §4.8.3.1).
pub fn pm1_enable(&self, bits: u16) {
let en_offset = (self.pm1_event_len / 2) as u16;
if let Some(port) = self.pm1a_event {
let enable_port = port + en_offset;
let mut value = Pio::<u16>::new(enable_port).read();
value |= bits;
Pio::<u16>::new(enable_port).write(value);
}
if let Some(port) = self.pm1b_event {
let enable_port = port + en_offset;
let mut value = Pio::<u16>::new(enable_port).read();
value |= bits;
Pio::<u16>::new(enable_port).write(value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fadt() -> FadtStruct {
let mut fadt: FadtStruct = unsafe { core::mem::zeroed() };
fadt.sci_interrupt = 9;
fadt.gpe0_block = 0x1828;
fadt.gpe0_ength = 0x20;
fadt.gpe1_block = 0x1848;
fadt.gpe1_length = 0x10;
fadt.gpe1_base = 0x80;
fadt.pm1a_event_block = 0x1800;
fadt
}
#[test]
fn gpe_block_mapping() {
let blocks = GpeBlocks::from_fadt(&fadt());
let gpe0 = blocks.gpe0.expect("gpe0 present");
assert_eq!(gpe0.port, 0x1828);
assert_eq!(gpe0.len, 0x20);
assert_eq!(gpe0.base, 0);
// GPE 0x6E (LG Gram EC GPE): index 110 → byte 13, bit 6.
let (status_port, bit) = gpe0.status_port(0x6e).expect("in range");
assert_eq!(status_port, 0x1828 + 13);
assert_eq!(bit, 6);
// Enable port is offset by len/2 (16).
let (enable_port, _) = gpe0.enable_port(0x6e).expect("in range");
assert_eq!(enable_port, 0x1828 + 16 + 13);
// Out of range: len/2 = 16 bytes → 128 GPEs max.
assert!(gpe0.status_port(0x7f).is_some());
assert!(gpe0.status_port(0x80).is_none());
}
#[test]
fn gpe1_base_offset() {
let blocks = GpeBlocks::from_fadt(&fadt());
let gpe1 = blocks.gpe1.expect("gpe1 present");
assert_eq!(gpe1.base, 0x80);
assert_eq!(gpe1.status_port(0x7f), None);
let (port, bit) = gpe1.status_port(0x81).expect("in range");
assert_eq!(port, 0x1848);
assert_eq!(bit, 1);
assert!(blocks.block_for(0x6e).is_some());
assert!(blocks.block_for(0x81).is_some());
assert!(blocks.block_for(0xff).is_none());
}
}
-314
View File
@@ -1,314 +0,0 @@
use std::convert::TryFrom;
use std::mem;
use std::ops::ControlFlow;
use std::sync::Arc;
use ::acpi::aml::op_region::{RegionHandler, RegionSpace};
use event::{EventFlags, RawEventQueue};
use libredox::Fd;
use redox_scheme::{scheme::register_sync_scheme, Socket};
use scheme_utils::Blocking;
use syscall::flag::{AcpiVerb, CallFlags};
mod acpi;
mod aml_physmem;
mod dmi;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod ec;
mod gpe;
mod notifications;
mod power_events;
mod wake;
mod scheme;
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"misc",
"acpi",
"acpid",
common::output_level(),
common::file_level(),
);
log::info!("acpid start");
let kernel_acpi_handle = Fd::open("/scheme/kernel.acpi", libredox::flag::O_CLOEXEC, 0)
.expect("acpid: failed to open kernel ACPI handle");
let rxsdt_raw_data: Arc<[u8]> = {
let len = kernel_acpi_handle
.call_ro(&mut [], CallFlags::READ, &[AcpiVerb::ReadRxsdt as u64])
.expect("acpid: failed to get rxsdt length");
let mut buf = vec![0_u8; len];
kernel_acpi_handle
.call_ro(&mut buf, CallFlags::READ, &[AcpiVerb::ReadRxsdt as u64])
.expect("acpid: failed to read rxsdt");
buf.into()
};
if rxsdt_raw_data.is_empty() {
log::info!("System doesn't use ACPI");
daemon.ready();
std::process::exit(0);
}
let sdt = self::acpi::Sdt::new(rxsdt_raw_data).expect("acpid: failed to parse [RX]SDT");
let mut thirty_two_bit;
let mut sixty_four_bit;
let physaddrs_iter = match &sdt.signature {
b"RSDT" => {
thirty_two_bit = sdt
.data()
.chunks(mem::size_of::<u32>())
// TODO: With const generics, the compiler has some way of doing this for static sizes.
.map(|chunk| <[u8; mem::size_of::<u32>()]>::try_from(chunk).unwrap())
.map(|chunk| u32::from_le_bytes(chunk))
.map(u64::from);
&mut thirty_two_bit as &mut dyn Iterator<Item = u64>
}
b"XSDT" => {
sixty_four_bit = sdt
.data()
.chunks(mem::size_of::<u64>())
.map(|chunk| <[u8; mem::size_of::<u64>()]>::try_from(chunk).unwrap())
.map(|chunk| u64::from_le_bytes(chunk));
&mut sixty_four_bit as &mut dyn Iterator<Item = u64>
}
_ => panic!("acpid: expected [RX]SDT from kernel to be either of those"),
};
let region_handlers: Vec<(RegionSpace, Box<dyn RegionHandler + 'static>)> = vec![
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
(RegionSpace::EmbeddedControl, Box::new(ec::Ec::new())),
];
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
// LPIT parse for s2idle residency reporting (Phase 9.1).
if let Some(lpit) = wake::init_lpit(&acpi_context) {
log::info!(
"acpid: LPIT loaded — {} LPI state(s), deepest {} us residency",
lpit.entries.len(),
lpit.deepest_lpi().map(|e| e.residency_us).unwrap_or(0)
);
} else {
log::debug!("acpid: no LPIT table found (s2idle residency reporting unavailable)");
}
if let Some(fadt) = acpi_context.fadt() {
if fadt.supports_s0_idle() {
log::info!("acpid: platform supports S0 low-power idle (s2idle)");
} else {
log::info!("acpid: platform does not advertise S0 low-power idle — s2idle unavailable");
}
}
// _PRW wake device enumeration for s2idle wake management.
let wake_registry = wake::WakeRegistry::enumerate(&acpi_context);
if wake_registry.device_count() > 0 {
log::info!(
"acpid: {} wake-capable device(s) registered (GPEs: {:?})",
wake_registry.device_count(),
wake_registry.wake_gpes()
);
}
*acpi_context.wake_registry.write() = Some(wake_registry);
// TODO: I/O permission bitmap?
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
let shutdown_pipe = kernel_acpi_handle
.openat("kstop", libredox::flag::O_CLOEXEC, 0)
.expect("acpid: failed to open kstop handle");
let mut event_queue = RawEventQueue::new().expect("acpid: failed to create event queue");
let socket = Socket::nonblock().expect("acpid: failed to create disk scheme");
let mut scheme = self::scheme::AcpiScheme::new(&acpi_context, &socket);
// Phase I.5: register the kstop handle fd so the main loop
// can call kstop_reason (kcall 2) to query the kernel for
// the reason of the most recent kstop event. The handle
// shares the underlying file descriptor; the kcall goes
// through the same fd that the event queue subscribes to.
scheme.set_kstop_fd(Fd::new(shutdown_pipe.raw()));
let mut handler = Blocking::new(&socket, 16);
event_queue
.subscribe(shutdown_pipe.raw() as usize, 0, EventFlags::READ)
.expect("acpid: failed to register shutdown pipe for event queue");
event_queue
.subscribe(socket.inner().raw(), 1, EventFlags::READ)
.expect("acpid: failed to register scheme socket for event queue");
if let Err(err) = register_sync_scheme(&socket, "acpi", &mut scheme) {
if err.errno == syscall::EEXIST {
// acpid is reachable via BOTH 41_acpid.service and hwd's ACPI
// backend (drivers/hwd/src/backend/acpi.rs spawns "acpid"), so a
// second instance races to register the "acpi" scheme. That is
// redundant, not fatal: exit cleanly so the duplicate does not
// abort and wedge the boot — the first instance keeps serving.
log::info!("acpid: 'acpi' scheme already registered; exiting duplicate instance");
std::process::exit(0);
}
panic!(
"acpid: failed to register acpi scheme to namespace: {}",
err
);
}
// GPE/PM1 power events: build the register map from the FADT, enable
// the EC GPE and the fixed-feature buttons, then subscribe the SCI IRQ.
// Must happen before setrens(0,0) — opening /scheme/irq needs the
// current namespace.
let sci_irq_fd = {
power_events::init_power_events(&acpi_context)
.expect("acpid: failed to initialize power events");
let sci_irq = acpi_context
.gpe
.read()
.map(|blocks| blocks.sci_irq)
.unwrap_or(9);
let path = format!("/scheme/irq/{sci_irq}");
match Fd::open(&path, libredox::flag::O_RDWR | libredox::flag::O_CLOEXEC, 0) {
Ok(fd) => {
log::info!("acpid: subscribed to ACPI SCI at {}", path);
Some(fd)
}
Err(err) => {
log::warn!(
"acpid: cannot open {} ({:?}); GPE/button events disabled",
path,
err
);
None
}
}
};
if let Some(sci_fd) = &sci_irq_fd {
event_queue
.subscribe(sci_fd.raw() as usize, 2, EventFlags::READ)
.expect("acpid: failed to register SCI irq fd for event queue");
}
libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
daemon.ready();
let mut mounted = true;
while mounted {
let Some(event) = event_queue
.next()
.transpose()
.expect("acpid: failed to read event file")
else {
break;
};
if event.fd == socket.inner().raw() {
loop {
match handler
.process_requests_nonblocking(&mut scheme)
.expect("acpid: failed to process requests")
{
ControlFlow::Continue(()) => {}
ControlFlow::Break(()) => break,
}
}
} else if event.fd == shutdown_pipe.raw() as usize {
// Phase I.5: dispatch on the kstop reason. The
// kcall 2 (CheckShutdown) verb returns the
// u8 reason. The kernel re-arms the EVENT_READ
// for the next event in the same fd; we read it
// once per cycle.
let reason = match scheme.kstop_reason() {
Ok(r) => r as u8,
Err(e) => {
log::warn!("kstop_reason failed: {:?}, falling back to shutdown", e);
1
}
};
match reason {
0 => {
// idle / no event — spurious wake, ignore
}
1 => {
// shutdown (S5)
log::info!("Received shutdown request from kernel.");
mounted = false;
}
2 => {
// s2idle wake (Phase I.5)
log::info!("s2idle wake: running \\_SST(2) -> \\_WAK(0) -> \\_SST(1)");
acpi_context.exit_s2idle();
}
3 => {
// s3 wake (Phase II.X.W)
// Run the standard S3 resume AML sequence:
// \_SST(2) -> \_WAK(3) -> \_SST(1). The kernel
// trampoline at s3_resume::s3_trampoline
// has already restored the kernel state. The
// acpid's job is the AML wake sequence.
log::info!("s3 wake: running \\_SST(2) -> \\_WAK(3) -> \\_SST(1)");
acpi_context.wake_from_sleep_state(3);
}
other => {
log::warn!("unknown kstop reason {}, treating as shutdown", other);
mounted = false;
}
}
} else if Some(event.fd) == sci_irq_fd.as_ref().map(|fd| fd.raw() as usize) {
// ACPI SCI: GPE + PM1 fixed events. Read+ack the IRQ line first
// (the IRQ scheme re-arms on write-back), then dispatch.
let mut irq_value = [0u8; 4];
if let Some(sci_fd) = &sci_irq_fd {
let _ = sci_fd.read(&mut irq_value);
let _ = sci_fd.write(&irq_value);
}
for event in power_events::handle_sci(&acpi_context) {
match event {
power_events::PowerEvent::PowerButton => {
*acpi_context.power_button_events.write() += 1;
log::info!("acpid: power button pressed — initiating shutdown");
mounted = false;
}
power_events::PowerEvent::SleepButton => {
*acpi_context.sleep_button_events.write() += 1;
log::info!("acpid: sleep button pressed — entering s2idle");
acpi_context.enter_s2idle();
}
power_events::PowerEvent::EcQuery(query) => {
log::debug!("acpid: EC query {:#04x} dispatched", query);
}
power_events::PowerEvent::LidChanged(open) => {
log::info!("acpid: lid {}", if open { "opened" } else { "closed" });
}
power_events::PowerEvent::Notify { device, value } => {
log::debug!("acpid: device notification {} = {:#x}", device, value);
}
}
}
} else {
log::debug!("Received request to unknown fd: {}", event.fd);
continue;
}
}
drop(shutdown_pipe);
drop(event_queue);
acpi_context.set_global_s_state(5);
unreachable!("System should have shut down before this is entered");
}
fn main() {
common::init();
daemon::Daemon::new(daemon);
}
-43
View File
@@ -1,43 +0,0 @@
//! Shared queue of AML `Notify (device, value)` events.
//!
//! The vendored `acpi` crate's `Handler::handle_notify` hook pushes into
//! this queue from inside the interpreter; acpid's main loop drains it and
//! republishes the events to scheme consumers (upower, session daemons).
//! Modelled on ACPICA's global notify dispatch (`EvNotify`) and Linux's
//! `acpi_ev_notify_dispatch` / `acpi_bus_notify` flow.
use std::collections::VecDeque;
use std::sync::Arc;
use parking_lot::Mutex;
#[derive(Clone, Default)]
pub struct AmlNotifications {
inner: Arc<Mutex<VecDeque<(String, u64)>>>,
}
impl AmlNotifications {
pub fn push(&self, device: String, value: u64) {
self.inner.lock().push_back((device, value));
}
pub fn drain(&self) -> Vec<(String, u64)> {
self.inner.lock().drain(..).collect()
}
pub fn is_empty(&self) -> bool {
self.inner.lock().is_empty()
}
/// Most recent notification value for a device path, without draining —
/// used to seed state surfaces (e.g. battery change counters) before the
/// main loop has processed the queue.
pub fn last_for(&self, device: &str) -> Option<u64> {
self.inner
.lock()
.iter()
.rev()
.find(|(path, _)| path == device)
.map(|(_, value)| *value)
}
}
-294
View File
@@ -1,294 +0,0 @@
//! Power-event bring-up and SCI dispatch: GPE/PM1 fixed events, EC query
//! loop, lid and button surfaces.
//!
//! Modelled on Linux 7.1: `drivers/acpi/evgpe.c` (GPE dispatch),
//! `drivers/acpi/ec.c` (`acpi_ec_ecdt_start`, `acpi_ec_event_handler`,
//! `ec_query`), and `drivers/acpi/button.c` (fixed-feature and lid events).
use std::error::Error;
use crate::acpi::{AcpiContext, Sdt};
use crate::gpe::{GpeBlocks, PM1_PWRBTN, PM1_SLPBTN};
/// Events produced by one SCI handling pass. The main loop turns these
/// into actions (shutdown, suspend prep, log/state updates) without the
/// dispatch layer knowing policy.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PowerEvent {
PowerButton,
SleepButton,
EcQuery(u8),
LidChanged(bool),
Notify { device: String, value: u64 },
}
/// ECDT (Embedded Controller Boot Resources Table) payload after the
/// 36-byte SDT header (ACPI 6.4 §5.2.16): EC_CONTROL GAS (12), EC_DATA
/// GAS (12), UID (4), GPE_BIT (1), then a null-terminated ACPI path.
struct EcdtInfo {
device_path: String,
gpe: u8,
}
fn parse_ecdt(sdt: &Sdt) -> Option<EcdtInfo> {
let data = sdt.data();
let name_start = 12 + 12 + 4;
let gpe = *data.get(name_start)?;
let name_bytes = data.get(name_start + 1..)?;
let end = name_bytes
.iter()
.position(|byte| *byte == 0)
.unwrap_or(name_bytes.len());
let path = core::str::from_utf8(&name_bytes[..end]).ok()?.to_owned();
if path.is_empty() {
return None;
}
Some(EcdtInfo {
device_path: path,
gpe,
})
}
/// Initialize GPE/PM1 fixed events and discover the EC + lid devices.
///
/// Mirrors `acpi_ec_ecdt_start` + `acpi_enable_gpe` ordering in Linux:
/// parse ECDT for the EC device path and its GPE, enable the EC GPE,
/// then enable PM1 fixed events for the power/sleep buttons. Firmware
/// enable states of every other GPE are left untouched.
pub fn init_power_events(context: &AcpiContext) -> Result<(), Box<dyn Error>> {
let Some(fadt) = context.fadt() else {
log::warn!("acpid: no FADT; GPE/PM1 power events unavailable");
return Ok(());
};
if fadt.is_hardware_reduced() {
// HW-reduced ACPI (FADT bit 20): no PM1/GPE register blocks. Sleep and
// wake use the HW-reduced sleep control/status registers instead.
log::info!("acpid: hardware-reduced ACPI platform — PM1/GPE blocks absent");
*context.gpe.write() = None;
return Ok(());
}
let blocks = GpeBlocks::from_fadt(&fadt);
log::info!(
"acpid: SCI irq {} gpe0={:?} gpe1={:?} pm1a_evt={:#x?} pm1_evt_len={}",
blocks.sci_irq,
blocks.gpe0,
blocks.gpe1,
blocks.pm1a_event,
blocks.pm1_event_len,
);
*context.gpe.write() = Some(blocks);
// EC discovery: ECDT first (canonical), then a _HID = PNP0C09 probe of
// the conventional paths as fallback (Linux does the same probe when
// ECDT is absent).
let ec = context
.take_single_sdt(*b"ECDT")
.and_then(|sdt| parse_ecdt(&sdt))
.map(|info| (info.device_path, info.gpe))
.or_else(|| probe_ec_device(context));
match &ec {
Some((path, gpe)) => {
log::info!("acpid: EC device at {} (GPE {:#04x})", path, gpe);
if blocks.enable_gpe(*gpe) {
log::info!("acpid: EC GPE {:#04x} enabled", gpe);
} else {
log::warn!("acpid: EC GPE {:#04x} not covered by any GPE block", gpe);
}
}
None => log::warn!("acpid: no embedded controller found (ECDT and probe both empty)"),
}
*context.ec_device.write() = ec;
// Lid device: typically a child of the EC, or directly under \_SB.
let lid = discover_lid_device(context);
if let Some(path) = &lid {
log::info!("acpid: lid device at {}", path);
}
*context.lid_device.write() = lid;
*context.lid_state.write() = read_lid_state(context);
// ACPI 4.0 fan devices (PNP0C0B — "Microsoft fan extensions" on the
// LG Gram): probe the conventional placements relative to the EC.
let fans = discover_fan_devices(context);
if !fans.is_empty() {
log::info!("acpid: {} ACPI fan device(s): {:?}", fans.len(), fans);
}
*context.fan_devices.write() = fans;
// Fixed-feature power/sleep buttons (PM1_EN), preserving every other
// enable bit as firmware set it.
blocks.pm1_enable(PM1_PWRBTN | PM1_SLPBTN);
Ok(())
}
fn probe_ec_device(context: &AcpiContext) -> Option<(String, u8)> {
const CANDIDATES: &[&str] = &[
"\\_SB.PC00.LPCB.H_EC",
"\\_SB.PCI0.LPCB.EC0",
"\\_SB.PCI0.LPCB.EC",
"\\_SB.PCI0.LPC.EC0",
"\\_SB.PCI0.SBRG.EC0",
"\\_SB.PCI0.SBRG.EC",
];
for path in CANDIDATES {
if context.evaluate_acpi_method(path, "_HID", &[]).is_ok() {
// The _GPE object is the EC's GPE assignment (ACPI 12.11).
let gpe = context
.evaluate_acpi_method(path, "_GPE", &[])
.ok()
.and_then(|values| values.first().copied())
.unwrap_or(0) as u8;
return Some((path.to_string(), gpe));
}
}
None
}
fn discover_lid_device(context: &AcpiContext) -> Option<String> {
let mut candidates: Vec<String> = Vec::new();
if let Some((ec_path, _)) = &*context.ec_device.read() {
candidates.push(format!("{ec_path}.LID0"));
candidates.push(format!("{ec_path}.LID"));
}
candidates.push("\\_SB.LID0".to_string());
candidates.push("\\_SB.PC00.LPCB.LID0".to_string());
candidates.push("\\_SB.PCI0.LPCB.LID0".to_string());
for path in &candidates {
if context.evaluate_acpi_method(path, "_LID", &[]).is_ok() {
return Some(path.clone());
}
}
None
}
/// `_LID` → `true` when the lid is open (ACPI 6.4 §12.9.3: nonzero = open).
fn read_lid_state(context: &AcpiContext) -> Option<bool> {
let path = context.lid_device.read().clone()?;
context
.evaluate_acpi_method(&path, "_LID", &[])
.ok()
.and_then(|values| values.first().copied())
.map(|value| value != 0)
}
fn discover_fan_devices(context: &AcpiContext) -> Vec<String> {
let mut prefixes: Vec<String> = Vec::new();
if let Some((ec_path, _)) = &*context.ec_device.read() {
prefixes.push(ec_path.clone());
}
prefixes.push("\\_SB.PC00.LPCB".to_string());
prefixes.push("\\_SB.PCI0.LPCB".to_string());
prefixes.push("\\_SB".to_string());
prefixes.push("\\_SB.PC00".to_string());
let mut fans = Vec::new();
for prefix in &prefixes {
for name in ["FAN0", "FAN1", "FAN"] {
let path = format!("{prefix}.{name}");
if fans.contains(&path) {
continue;
}
// An ACPI 4.0 fan device exposes _FST (fan status).
if context.evaluate_acpi_method(&path, "_FST", &[]).is_ok() {
fans.push(path);
}
}
}
fans
}
/// Handle one SCI: PM1 fixed events first (buttons), then the EC GPE and
/// its query loop, then drain the AML `Notify` queue.
///
/// Mirrors `acpi_ev_gpe_dispatch` + `acpi_ec_event_handler`: a level GPE
/// stays asserted until the driver clears it, so the status bit is cleared
/// only after the query loop has drained.
pub fn handle_sci(context: &AcpiContext) -> Vec<PowerEvent> {
let mut events = Vec::new();
let Some(blocks) = *context.gpe.read() else {
return events;
};
// PM1 fixed events (evxface): status bit set + enable bit set fires the SCI.
let pm1 = blocks.pm1_status();
if pm1 & PM1_PWRBTN != 0 {
blocks.pm1_clear(PM1_PWRBTN);
events.push(PowerEvent::PowerButton);
}
if pm1 & PM1_SLPBTN != 0 {
blocks.pm1_clear(PM1_SLPBTN);
events.push(PowerEvent::SleepButton);
}
// EC GPE: drain the query queue (ec_query loop). Query value 0 means
// "no pending event" per ACPI 12.4.
if let Some((ec_path, gpe)) = context.ec_device.read().clone() {
if blocks.gpe_status(gpe) {
let mut ec = crate::ec::Ec::new();
let mut guard = 0;
while ec.sci_evt_set() && guard < 32 {
let query = ec.query();
if query == 0 {
break;
}
events.push(PowerEvent::EcQuery(query));
let method = format!("{ec_path}._Q{query:02X}");
if let Err(err) = context.evaluate_acpi_method(&method, "", &[]) {
log::debug!("acpid: {} evaluation failed: {:?}", method, err);
}
guard += 1;
}
blocks.clear_gpe(gpe);
}
}
// General GPE dispatch (evgpe.c acpi_ev_gpe_detect). It runs on acpid's
// main-loop thread, which also serves /scheme/acpi — a blocking _Lxx/_Exx
// method would freeze the scheme and deadlock the boot. Opt-in via
// REDBEAR_ACPI_GPE_DISPATCH=1, default OFF (EC + PM1 stay always-on).
if std::env::var_os("REDBEAR_ACPI_GPE_DISPATCH").is_some() {
let ec_gpe = context.ec_device.read().clone().map(|(_, g)| g);
for gpe in blocks.enabled_active_gpes() {
if Some(gpe) == ec_gpe {
continue;
}
let level = format!("\\_GPE._L{gpe:02X}");
let edge = format!("\\_GPE._E{gpe:02X}");
let handled = context.evaluate_acpi_method(&level, "", &[]).is_ok()
|| context.evaluate_acpi_method(&edge, "", &[]).is_ok();
if handled {
log::info!("acpid: GPE {:#04x} dispatched to AML control method", gpe);
} else {
log::debug!(
"acpid: GPE {:#04x} active but no _L{gpe:02X}/_E{gpe:02X} method",
gpe
);
}
blocks.clear_gpe(gpe);
}
}
// Drain AML notifications (from _Qxx methods and any other Notify).
let lid_path = context.lid_device.read().clone();
for (device, value) in context.notifications().drain() {
log::info!("acpid: notify {} = {:#x}", device, value);
if let Some(lid) = &lid_path {
if &device == lid {
let state = read_lid_state(context);
if let Some(open) = state {
let previous = *context.lid_state.read();
*context.lid_state.write() = Some(open);
if previous != Some(open) {
events.push(PowerEvent::LidChanged(open));
}
}
continue;
}
}
events.push(PowerEvent::Notify { device, value });
}
events
}
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More