Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a4aa8a05 | |||
| 26595f1624 | |||
| 9774052fd1 | |||
| 620184ab6d | |||
| 9196d01417 | |||
| 36f95af890 | |||
| 86c27653ed | |||
| 5907c14c4e | |||
| 4b683014c9 | |||
| 3bc71a8161 | |||
| 72a916318b | |||
| f21d523529 | |||
| 1232fb742a | |||
| 11569da01e | |||
| 1268238ac0 | |||
| 9e625ef20f | |||
| 3399e18693 | |||
| dfd687e3cf | |||
| d00a02c791 | |||
| 83a5c11e21 | |||
| 6caad3a538 | |||
| a725e6ac8c | |||
| a31138efe9 | |||
| 4eabdf2016 | |||
| 826a984fdb | |||
| 08cf1e6e0a | |||
| 2f320c1ea0 | |||
| bae5afa1b4 | |||
| ae99d15bfa | |||
| e25fd20708 | |||
| 0c5f21d297 | |||
| 2ae6ef9a67 | |||
| ba2e6555af | |||
| c1b8c3b4cf | |||
| 4e40dc538c | |||
| fc8f0ec4fd | |||
| f00e969b82 | |||
| 3cb57fbc7f | |||
| a500cd9e6c | |||
| 32e402087f | |||
| 2cd334a1f9 | |||
| ae6549251b | |||
| 31ee8b3bf9 | |||
| a2e4cd27fe | |||
| d28963d88e | |||
| 047e7c09da | |||
| 1b3e94a20d |
@@ -0,0 +1,7 @@
|
|||||||
|
[**.c]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[**.yml]
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
+10
-10
@@ -1,11 +1,11 @@
|
|||||||
target/
|
|
||||||
sysroot/
|
|
||||||
|
|
||||||
# Local settings folder for Visual Studio Code
|
|
||||||
.vscode/
|
|
||||||
# Local settings folder for Jetbrains products (RustRover, IntelliJ, CLion)
|
|
||||||
.idea/
|
.idea/
|
||||||
# Local settings folder for Visual Studio Professional
|
prefix/
|
||||||
.vs/
|
sysroot/
|
||||||
# Local settings folder for the devcontainer extension that most IDEs support.
|
**/target/
|
||||||
.devcontainer/
|
.gdb_history
|
||||||
|
*.patch
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
/.vim
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
+42
-11
@@ -1,25 +1,36 @@
|
|||||||
image: "redoxos/redoxer:latest"
|
image: "redoxos/redoxer:latest"
|
||||||
|
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
|
|
||||||
workflow:
|
workflow:
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_COMMIT_BRANCH == "main" && $CI_PROJECT_NAMESPACE == "redox-os"'
|
- if: '$CI_COMMIT_BRANCH == "master" && $CI_PROJECT_NAMESPACE == "redox-os"'
|
||||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
|
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
- cross-build
|
- cross-build
|
||||||
- test
|
- test
|
||||||
|
before_script:
|
||||||
|
cargo install cbindgen
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
stage: build
|
stage: build
|
||||||
|
needs: []
|
||||||
script:
|
script:
|
||||||
- rustup component add rustfmt
|
- rustup component add rustfmt-preview
|
||||||
- CHECK_ONLY=1 ./fmt.sh
|
- ./fmt.sh -- --check
|
||||||
|
|
||||||
|
linux:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- ./check.sh --host
|
||||||
|
|
||||||
x86_64:
|
x86_64:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- rustup component add rustfmt
|
- ./check.sh --arch=x86_64
|
||||||
- ./check.sh
|
|
||||||
|
|
||||||
i586:
|
i586:
|
||||||
stage: cross-build
|
stage: cross-build
|
||||||
@@ -28,6 +39,7 @@ i586:
|
|||||||
|
|
||||||
aarch64:
|
aarch64:
|
||||||
stage: cross-build
|
stage: cross-build
|
||||||
|
image: "redoxos/redoxer:aarch64"
|
||||||
script:
|
script:
|
||||||
- ./check.sh --arch=aarch64
|
- ./check.sh --arch=aarch64
|
||||||
|
|
||||||
@@ -36,7 +48,26 @@ riscv64gc:
|
|||||||
script:
|
script:
|
||||||
- ./check.sh --arch=riscv64gc
|
- ./check.sh --arch=riscv64gc
|
||||||
|
|
||||||
boot:
|
test:linux:
|
||||||
stage: test
|
stage: test
|
||||||
|
needs: [linux]
|
||||||
script:
|
script:
|
||||||
- timeout -s KILL 9m ./check.sh --test
|
- ./check.sh --host --test
|
||||||
|
|
||||||
|
test:x86_64:
|
||||||
|
stage: test
|
||||||
|
needs: [x86_64]
|
||||||
|
script:
|
||||||
|
# timeout: https://gitlab.redox-os.org/redox-os/relibc/-/issues/238
|
||||||
|
- timeout -s KILL 9m ./check.sh --arch=x86_64 --test
|
||||||
|
|
||||||
|
test:aarch64:
|
||||||
|
stage: test
|
||||||
|
needs: [aarch64]
|
||||||
|
image: "redoxos/redoxer:aarch64"
|
||||||
|
# many issues that not exist in x86_64, and lack of interest to fix so far
|
||||||
|
allow_failure: true
|
||||||
|
script:
|
||||||
|
- timeout -s KILL 9m ./check.sh --arch=aarch64 --test
|
||||||
|
|
||||||
|
#TODO: Enable more arch once dynamic linker working
|
||||||
|
|||||||
@@ -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._
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[submodule "openlibm"]
|
||||||
|
path = openlibm
|
||||||
|
url = https://gitlab.redox-os.org/redox-os/openlibm.git
|
||||||
|
branch = master
|
||||||
|
[submodule "src/dlmalloc-rs"]
|
||||||
|
path = dlmalloc-rs
|
||||||
|
url = https://gitlab.redox-os.org/redox-os/dlmalloc-rs.git
|
||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
1. [What to do](#what-to-do)
|
||||||
|
2. [Code style](#code-style)
|
||||||
|
3. [Sending merge requests](#sending-merge-requests)
|
||||||
|
4. [Writing tests](#writing-tests)
|
||||||
|
5. [Running tests](#running-tests)
|
||||||
|
|
||||||
|
Maintaining a libc is tough work, and we'd love some help!
|
||||||
|
|
||||||
|
## What to do
|
||||||
|
|
||||||
|
For now, we are still trying to get full libc compatibility before we move on to
|
||||||
|
any optimisation.
|
||||||
|
|
||||||
|
- We currently have a number of unimplemented functions. Search for
|
||||||
|
`unimplemented!()` and hop right in!
|
||||||
|
- If you notice any missing functionality, feel free to add it in
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
We have a `rustfmt.toml` in the root directory of relibc. Please run `./fmt.sh`
|
||||||
|
before sending in any merge requests as it will automatically format your code.
|
||||||
|
|
||||||
|
With regards to general style:
|
||||||
|
|
||||||
|
### Where applicable, prefer using references to raw pointers
|
||||||
|
|
||||||
|
This is most obvious when looking at `stdio` functions. If raw pointers were
|
||||||
|
used instead of references, then the resulting code would be significantly
|
||||||
|
uglier. Instead try to check for pointer being valid with `pointer::as_ref()`
|
||||||
|
and `pointer::as_mut()` and then immediately use those references instead.
|
||||||
|
|
||||||
|
Internal functions should always take references.
|
||||||
|
|
||||||
|
### Use the c types exposed in our platform module instead of Rust's inbuilt integer types
|
||||||
|
|
||||||
|
This is so we can guarantee that everything works across platforms. While it is
|
||||||
|
generally accepted these days that an `int` has 32 bits (which matches against
|
||||||
|
an `i32`), some platforms have `int` as having 16 bits, and others have long as
|
||||||
|
being 32 bits instead of 64. If you use the types in platform, then we can
|
||||||
|
guarantee that your code will "just work" should we port relibc to a different
|
||||||
|
architecture.
|
||||||
|
|
||||||
|
### Use our internal functions
|
||||||
|
|
||||||
|
If you need to use a C string, don't reinvent the wheel. We have functions in
|
||||||
|
the platform module that convert C strings to Rust slices.
|
||||||
|
|
||||||
|
We also have structures that wrap files, wrap writable strings, and wrap various
|
||||||
|
other commonly used things that you should use instead of rolling your own.
|
||||||
|
|
||||||
|
## Sending merge requests
|
||||||
|
|
||||||
|
If you have sent us a merge request, first of all, thanks for taking your time
|
||||||
|
to help us!
|
||||||
|
|
||||||
|
The first thing to note is that we do most of our development on our
|
||||||
|
[GitLab server](https://gitlab.redox-os.org/redox-os/relibc), and as such it is
|
||||||
|
possible that none of the maintainers will see your merge request if it is
|
||||||
|
opened on GitHub.
|
||||||
|
|
||||||
|
In your merge request, please put in the description:
|
||||||
|
- What functions (if any) have been implemented or changed
|
||||||
|
- The rationale behind your merge request (e.g. why you thought this change was
|
||||||
|
required. If you are just implementing some functions, you can ignore this)
|
||||||
|
- Any issues that are related to the merge request
|
||||||
|
|
||||||
|
We have CI attached to our GitLab instance, so all merge requests are checked to
|
||||||
|
make sure that they are tested before they are merged. Please write tests for
|
||||||
|
the functions that you add/change and test locally on your own machine
|
||||||
|
***before*** submitting a merge request.
|
||||||
|
|
||||||
|
## Writing tests
|
||||||
|
|
||||||
|
Every function that gets written needs to have a test in C in order to make sure
|
||||||
|
it works as intended. Here are a few guidelines for writing good tests.
|
||||||
|
|
||||||
|
### Ensure that any literals you have are mapped to variables instead of being directly passed to a function.
|
||||||
|
|
||||||
|
Sometimes compilers take literals put into libc functions and run them
|
||||||
|
internally during compilation, which can cause some false positives. All tests
|
||||||
|
are compiled with `-fno-builtin`, which theoretically solves this issue, but
|
||||||
|
just in case, it'd be a good idea to map inputs to variables.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "string.h"
|
||||||
|
#include "stdio.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
// Don't do this
|
||||||
|
printf("%d\n", strcspn("Hello", "Hi"));
|
||||||
|
|
||||||
|
// Do this
|
||||||
|
char *first = "Hello";
|
||||||
|
char *second = "Hi";
|
||||||
|
printf("%d\n", strcspn(first, second));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ensure your tests cover every section of code.
|
||||||
|
|
||||||
|
What happens if a string in `strcmp()` is shorter than the other string? What
|
||||||
|
happens if the first argument to `strcspn()` is longer than the second string?
|
||||||
|
In order to make sure that all functions work as expected, we ask that any tests
|
||||||
|
cover as much of the code that you have written as possible.
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
Running tests is an important part in trying to find bugs. Before opening a
|
||||||
|
merge request, we ask that you test on your own machine to make sure there are
|
||||||
|
no regressions.
|
||||||
|
|
||||||
|
You can run tests with `make test` in the root directory of relibc to compile
|
||||||
|
relibc, compile the tests and run them. This *will* print a lot of output to
|
||||||
|
stdout, so be warned!
|
||||||
|
|
||||||
|
You can test against verified correct output with `make verify` in the tests
|
||||||
|
directory. You will need to manually create the correct output and put it in the
|
||||||
|
tests/expected directory. Running any `make` commands in the tests directory
|
||||||
|
will ***not*** rebuild relibc, so you'll need to go back to the root directory
|
||||||
|
if you need to rebuild relibc.
|
||||||
Generated
+345
-2551
File diff suppressed because it is too large
Load Diff
+140
-152
@@ -1,160 +1,148 @@
|
|||||||
|
[package]
|
||||||
|
name = "relibc"
|
||||||
|
version = "0.2.5"
|
||||||
|
authors = ["Jeremy Soller <jackpot51@gmail.com>"]
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "relibc"
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
|
||||||
members = [
|
members = [
|
||||||
"audiod",
|
"src/crt0",
|
||||||
"config",
|
"src/crti",
|
||||||
"daemon",
|
"src/crtn",
|
||||||
"dhcpd",
|
"redox-rt",
|
||||||
"init",
|
"ld_so",
|
||||||
"initfs",
|
"generic-rt",
|
||||||
"initfs/tools",
|
|
||||||
"ipcd",
|
|
||||||
"logd",
|
|
||||||
"netstack",
|
|
||||||
"ptyd",
|
|
||||||
"ramfs",
|
|
||||||
"randd",
|
|
||||||
"scheme-utils",
|
|
||||||
"zerod",
|
|
||||||
|
|
||||||
"drivers/common",
|
|
||||||
"drivers/executor",
|
|
||||||
|
|
||||||
"drivers/acpid",
|
|
||||||
"drivers/hwd",
|
|
||||||
"drivers/pcid",
|
|
||||||
"drivers/pcid-spawner",
|
|
||||||
"drivers/rtcd",
|
|
||||||
"drivers/vboxd",
|
|
||||||
"drivers/inputd",
|
|
||||||
"drivers/virtio-core",
|
|
||||||
|
|
||||||
"drivers/audio/ac97d",
|
|
||||||
"drivers/audio/ihdad",
|
|
||||||
"drivers/audio/sb16d",
|
|
||||||
|
|
||||||
"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",
|
|
||||||
|
|
||||||
"drivers/input/ps2d",
|
|
||||||
"drivers/input/usbhidd",
|
|
||||||
|
|
||||||
"drivers/net/driver-network",
|
|
||||||
"drivers/net/e1000d",
|
|
||||||
"drivers/net/ixgbed",
|
|
||||||
"drivers/net/rtl8139d",
|
|
||||||
"drivers/net/rtl8168d",
|
|
||||||
"drivers/net/virtio-netd",
|
|
||||||
|
|
||||||
"drivers/redoxerd",
|
|
||||||
|
|
||||||
"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",
|
|
||||||
|
|
||||||
"drivers/usb/xhcid",
|
|
||||||
"drivers/usb/usbctl",
|
|
||||||
"drivers/usb/usbhubd",
|
|
||||||
"drivers/usb/ucsid",
|
|
||||||
|
|
||||||
"drivers/i2c/i2c-interface",
|
|
||||||
"drivers/i2c/i2cd",
|
|
||||||
"drivers/i2c/amd-mp2-i2cd",
|
|
||||||
"drivers/i2c/dw-acpi-i2cd",
|
|
||||||
"drivers/i2c/intel-lpss-i2cd",
|
|
||||||
|
|
||||||
"drivers/gpio/gpiod",
|
|
||||||
"drivers/gpio/intel-gpiod",
|
|
||||||
"drivers/gpio/i2c-gpio-expanderd",
|
|
||||||
|
|
||||||
"drivers/input/i2c-hidd",
|
|
||||||
"drivers/input/intel-thc-hidd",
|
|
||||||
|
|
||||||
"drivers/acpi-resource",
|
|
||||||
]
|
]
|
||||||
|
exclude = ["tests", "dlmalloc-rs"]
|
||||||
# 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 = "0.1.17"
|
|
||||||
orbclient = "0.3.51"
|
|
||||||
parking_lot = "0.12"
|
|
||||||
pico-args = "0.5"
|
|
||||||
plain = "0.2.3"
|
|
||||||
ransid = "0.4"
|
|
||||||
redox_event = "0.4.6"
|
|
||||||
redox-ioctl = { git = "https://gitlab.redox-os.org/redox-os/relibc.git" }
|
|
||||||
redox-log = { git = "https://gitlab.redox-os.org/redox-os/redox-log.git" }
|
|
||||||
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc.git", default-features = false }
|
|
||||||
redox-scheme = "0.11.0"
|
|
||||||
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]
|
[workspace.lints.clippy]
|
||||||
missing_safety_doc = "warn" #TODO: set to deny when all safety documentation is completed
|
borrow_as_ptr = "deny"
|
||||||
|
cast_lossless = "warn" # TODO review occurrences
|
||||||
|
cast_possible_truncation = "allow" # TODO review occurrences
|
||||||
|
cast_possible_wrap = "allow" # TODO review occurrences
|
||||||
|
cast_precision_loss = "allow" # TODO review occurrences
|
||||||
|
cast_ptr_alignment = "allow" # TODO review occurrences
|
||||||
|
cast_sign_loss = "allow" # TODO review occurrences
|
||||||
|
missing_errors_doc = "allow" # TODO review occurrences
|
||||||
|
missing_panics_doc = "allow" # TODO review occurrences
|
||||||
|
missing_safety_doc = "allow" # TODO review occurrences
|
||||||
|
mut_from_ref = "warn" # TODO review occurrences
|
||||||
precedence = "deny"
|
precedence = "deny"
|
||||||
|
ptr_as_ptr = "warn" # TODO review occurrences
|
||||||
|
ptr_cast_constness = "warn" # TODO review occurrences
|
||||||
|
ref_as_ptr = "warn" # TODO review occurrences
|
||||||
|
upper_case_acronyms = "allow" # TODO review occurrences
|
||||||
|
zero_ptr = "warn" # must allow on public constants due to cbindgen issue
|
||||||
|
|
||||||
|
[workspace.lints.rust]
|
||||||
|
dangling_pointers_from_temporaries = "deny"
|
||||||
|
dead_code = "allow" # TODO review occuurences
|
||||||
|
deprecated = "deny"
|
||||||
|
improper_ctypes_definitions = "deny"
|
||||||
|
internal_features = "allow" # core_intrinsics and lang_items
|
||||||
|
irrefutable_let_patterns = "deny"
|
||||||
|
mismatched_lifetime_syntaxes = "deny"
|
||||||
|
non_camel_case_types = "allow" # needed for most POSIX type names
|
||||||
|
non_snake_case = "allow" # TODO review occuurences
|
||||||
|
non_upper_case_globals = "allow" # TODO review occuurences
|
||||||
|
unexpected_cfgs = "deny"
|
||||||
|
unpredictable_function_pointer_comparisons = "deny"
|
||||||
|
unreachable_code = "allow" # TODO review occuurences
|
||||||
|
unsafe_op_in_unsafe_fn = "deny"
|
||||||
|
unused_imports = "deny"
|
||||||
|
unused_must_use = "deny"
|
||||||
|
unused_mut = "deny"
|
||||||
|
unused_unsafe = "deny"
|
||||||
|
unused_variables = "allow" # TODO review occurrences (too many for now)
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
bitflags = "2"
|
||||||
|
ioslice = { version = "0.6", default-features = false }
|
||||||
|
plain = "0.2"
|
||||||
|
redox-path = "0.3"
|
||||||
|
redox_protocols = { package = "libredox", version = "0.1.16", default-features = false, features = ["protocol"] }
|
||||||
|
redox_syscall = "0.7.4"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bitflags.workspace = true
|
||||||
|
cbitset = "0.2"
|
||||||
|
posix-regex = { version = "0.1.4", features = ["no_std"] }
|
||||||
|
|
||||||
|
rand = { version = "0.10", default-features = false }
|
||||||
|
rand_xorshift = "0.5"
|
||||||
|
rand_jitter = "0.6"
|
||||||
|
|
||||||
|
memchr = { version = "2.2.0", default-features = false }
|
||||||
|
plain.workspace = true
|
||||||
|
unicode-width = "0.1"
|
||||||
|
__libc_only_for_layout_checks = { package = "libc", version = "0.2.149", optional = true }
|
||||||
|
md5-crypto = { package = "md-5", version = "0.10.6", default-features = false }
|
||||||
|
sha-crypt = { version = "0.5", default-features = false }
|
||||||
|
base64ct = { version = "1.6", default-features = false, features = ["alloc"] }
|
||||||
|
bcrypt-pbkdf = { version = "0.10", default-features = false, features = [
|
||||||
|
"alloc",
|
||||||
|
] }
|
||||||
|
scrypt = { version = "0.11", default-features = false, features = ["simple"] }
|
||||||
|
pbkdf2 = { version = "0.12", features = ["sha2"] }
|
||||||
|
sha2 = { version = "0.10", default-features = false }
|
||||||
|
generic-rt = { path = "generic-rt" }
|
||||||
|
chrono-tz = { version = "0.10", default-features = false }
|
||||||
|
chrono = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||||
|
libm = "0.2"
|
||||||
|
log = "0.4"
|
||||||
|
spin = "0.9.8"
|
||||||
|
argon2 = "0.5.3"
|
||||||
|
|
||||||
|
[dependencies.dlmalloc]
|
||||||
|
path = "dlmalloc-rs"
|
||||||
|
default-features = false
|
||||||
|
features = ["c_api"]
|
||||||
|
|
||||||
|
[dependencies.object]
|
||||||
|
version = "0.36.7"
|
||||||
|
git = "https://gitlab.redox-os.org/andypython/object"
|
||||||
|
default-features = false
|
||||||
|
features = ["elf", "read_core"]
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
sc = "0.2.7"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "redox")'.dependencies]
|
||||||
|
redox_syscall.workspace = true
|
||||||
|
redox-rt = { path = "redox-rt" }
|
||||||
|
redox-path.workspace = true
|
||||||
|
redox_event = { version = "0.4.6", default-features = false, features = [
|
||||||
|
"redox_syscall",
|
||||||
|
] }
|
||||||
|
ioslice.workspace = true
|
||||||
|
redox-ioctl = { path = "redox-ioctl" }
|
||||||
|
redox_protocols.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# to enable trace level, take out this `no_trace`
|
||||||
|
default = ["check_against_libc_crate", "ld_so_cache", "no_trace"]
|
||||||
|
check_against_libc_crate = ["__libc_only_for_layout_checks"]
|
||||||
|
ld_so_cache = []
|
||||||
|
math_libm = []
|
||||||
|
no_trace = ["log/release_max_level_debug"]
|
||||||
|
# for very verbose activity beyond trace level
|
||||||
|
trace_tls = []
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Red Bear OS Phase I: s2idle / Modern Standby support.
|
cc-11 = { git = "https://github.com/tea/cc-rs", branch = "riscv-abi-arch-fix", package = "cc" }
|
||||||
# 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" }
|
|
||||||
# Red Bear OS Phase J: libredox 0.1.17 has its own vendored
|
|
||||||
# redox_syscall dep. Without the libredox override here,
|
|
||||||
# libredox::error::Error is the upstream syscall::error::Error
|
|
||||||
# (a different compile-time type than the local fork's
|
|
||||||
# syscall::Error) and the conversion `?` operator in
|
|
||||||
# scheme-utils / daemon fails with E0277. Override libredox
|
|
||||||
# to use the local fork at ../libredox/ (which itself uses
|
|
||||||
# the local syscall fork). Now libredox::error::Error and
|
|
||||||
# syscall::Error are the same type.
|
|
||||||
libredox = { path = "../libredox" }
|
|
||||||
|
|
||||||
[patch."https://gitlab.redox-os.org/redox-os/relibc.git"]
|
|
||||||
#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2017 Redox OS
|
Copyright (c) 2018 Redox OS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,119 +1,226 @@
|
|||||||
TARGET ?= x86_64-unknown-redox
|
include config.mk
|
||||||
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
|
|
||||||
|
|
||||||
SRC_DIR ?= $(CURDIR)
|
CARGO?=cargo
|
||||||
BUILD_DIR ?= $(shell pwd)/target/$(TARGET)/build
|
CARGO_TEST?=$(CARGO)
|
||||||
DESTDIR ?= ./sysroot
|
CARGO_COMMON_FLAGS=-Z build-std=core,alloc,compiler_builtins
|
||||||
SYSROOT ?= $(shell pwd)/target/$(TARGET)/sysroot
|
CARGOFLAGS?=$(CARGO_COMMON_FLAGS)
|
||||||
TARGET_DIR = $(BUILD_DIR)/$(TARGET)/$(BUILD_TYPE)
|
CC_WRAPPER?=
|
||||||
BUILD_FLAGS += --target-dir $(BUILD_DIR)
|
RUSTCFLAGS?=
|
||||||
|
LINKFLAGS?=-lgcc
|
||||||
|
USE_RUST_LIBM?=
|
||||||
|
TESTBIN?=
|
||||||
|
export OBJCOPY?=objcopy
|
||||||
|
|
||||||
INITFS_BINS = init logd ramfs randd zerod \
|
export CARGO_TARGET_DIR?=$(shell pwd)/target
|
||||||
acpid fbbootlogd fbcond hwd inputd lived \
|
BUILD?=$(CARGO_TARGET_DIR)/$(TARGET)
|
||||||
pcid pcid-spawner rtcd vesad
|
CARGOFLAGS+=--target=$(TARGET)
|
||||||
INITFS_DRIVERS_BINS = nvmed virtio-blkd virtio-gpud
|
EXCEPT_MATH=-not -name "math"
|
||||||
BASE_BINS = inputd pcid pcid-spawner redoxerd audiod dhcpd ipcd ptyd netstack
|
FEATURE_MATH=
|
||||||
DRIVERS_BINS = e1000d ihdad ihdgd ixgbed rtl8139d rtl8168d \
|
ifneq ($(USE_RUST_LIBM),)
|
||||||
usbctl usbhidd usbhubd usbscsid virtio-netd xhcid
|
FEATURE_MATH=--features math_libm
|
||||||
|
EXCEPT_MATH=
|
||||||
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
|
endif
|
||||||
|
|
||||||
ifeq ($(TARGET),aarch64-unknown-redox)
|
TARGET_HEADERS?=$(BUILD)/include
|
||||||
ifeq ($(BOARD),raspi3b)
|
export CFLAGS=-I$(TARGET_HEADERS)
|
||||||
INITFS_BINS += bcm2835-sdhcid
|
|
||||||
endif
|
PROFILE?=release
|
||||||
|
|
||||||
|
HEADERS_UNPARSED=$(shell find src/header -mindepth 1 -maxdepth 1 -type d -not -name "_*" $(EXCEPT_MATH) -printf "%f\n")
|
||||||
|
HEADERS_DEPS=$(shell find src/header -type f \( -name "cbindgen.toml" -o -name "*.rs" \))
|
||||||
|
#HEADERS=$(patsubst %,%.h,$(subst _,/,$(HEADERS_UNPARSED)))
|
||||||
|
|
||||||
|
SRC=\
|
||||||
|
Cargo.* \
|
||||||
|
$(shell find src/ redox-rt/src/ ld_so/src/ redox-ioctl/src/ include/ -type f)
|
||||||
|
|
||||||
|
BUILTINS_VERSION=0.1.70
|
||||||
|
|
||||||
|
.PHONY: all clean fmt install install-libs install-headers install-tests libs headers submodules test
|
||||||
|
|
||||||
|
all: | headers libs
|
||||||
|
|
||||||
|
headers: $(HEADERS_DEPS)
|
||||||
|
rm -rf $(TARGET_HEADERS)
|
||||||
|
mkdir -p $(TARGET_HEADERS)
|
||||||
|
cp -r include/* $(TARGET_HEADERS)
|
||||||
|
ifeq ($(USE_RUST_LIBM),)
|
||||||
|
cp "openlibm/include"/*.h $(TARGET_HEADERS)
|
||||||
|
cp "openlibm/src"/*.h $(TARGET_HEADERS)
|
||||||
endif
|
endif
|
||||||
|
@set -e ; \
|
||||||
INITFS_CARGO_ARGS = $(foreach bin,$(INITFS_BINS),-p $(bin))
|
for header in $(HEADERS_UNPARSED); do \
|
||||||
INITFS_DRIVERS_CARGO_ARGS = $(foreach bin,$(INITFS_DRIVERS_BINS),-p $(bin))
|
if test -f "src/header/$$header/cbindgen.toml"; then \
|
||||||
BASE_CARGO_ARGS = $(foreach bin,$(BASE_BINS),-p $(bin))
|
echo -e "\033[0;36;49mWriting Header $$header\033[0m"; \
|
||||||
DRIVERS_CARGO_ARGS = $(foreach bin,$(DRIVERS_BINS),-p $(bin))
|
out=`echo "$$header" | sed 's/_/\//g'`; \
|
||||||
|
out="$(TARGET_HEADERS)/$$out.h"; \
|
||||||
.PHONY: all base install install-base test
|
cat "src/header/$$header/cbindgen.toml" cbindgen.globdefs.toml \
|
||||||
|
| cbindgen "src/header/$$header/mod.rs" --config=/dev/stdin --output "$$out" 2>/dev/null; \
|
||||||
all: base
|
fi \
|
||||||
install: install-base
|
done; echo -e "\033[0;36;49mAll headers written\033[0m";
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(SRC_DIR)/target $(SRC_DIR)/sysroot $(SYSROOT) $(TARGET_DIR)
|
$(CARGO) clean
|
||||||
|
$(MAKE) -C tests clean
|
||||||
|
rm -rf sysroot
|
||||||
|
|
||||||
# test if booting
|
check:
|
||||||
test: all
|
$(CARGO) check
|
||||||
$(MAKE) install
|
|
||||||
redoxer exec --folder ./sysroot/:/ true
|
|
||||||
|
|
||||||
# test with interactive gui
|
fmt:
|
||||||
test-gui: all
|
./fmt.sh
|
||||||
$(MAKE) install
|
|
||||||
redoxer exec --gui --folder ./sysroot/:/ ion
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
install-headers: headers libs
|
||||||
# base
|
mkdir -pv "$(DESTDIR)/include"
|
||||||
# -----------------------------------------------------------------------------
|
cp -rv "$(TARGET_HEADERS)"/* "$(DESTDIR)/include"
|
||||||
$(SYSROOT)/bin/redoxfs:
|
|
||||||
REDOXER_SYSROOT=$(SYSROOT) redoxer pkg redoxfs
|
|
||||||
|
|
||||||
base:
|
libs: \
|
||||||
@mkdir -pv "$(BUILD_DIR)"
|
$(BUILD)/$(PROFILE)/libc.a \
|
||||||
# Build daemons and drivers
|
$(BUILD)/$(PROFILE)/libc.so \
|
||||||
CARGO_PROFILE_RELEASE_OPT_LEVEL=s CARGO_PROFILE_RELEASE_PANIC=abort \
|
$(BUILD)/$(PROFILE)/crt0.o \
|
||||||
$(CARGO) build $(BUILD_FLAGS) \
|
$(BUILD)/$(PROFILE)/crti.o \
|
||||||
--manifest-path "$(SRC_DIR)/Cargo.toml" \
|
$(BUILD)/$(PROFILE)/crtn.o \
|
||||||
$(BASE_CARGO_ARGS) $(DRIVERS_CARGO_ARGS)
|
$(BUILD)/$(PROFILE)/ld.so
|
||||||
# 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" && $(CARGO) rustc $(BUILD_FLAGS) \
|
|
||||||
-- -Ctarget-feature=+crt-static -Clinker="$(LINKER)"
|
|
||||||
|
|
||||||
install-base: base $(SYSROOT)/bin/redoxfs
|
install-libs: headers libs
|
||||||
@mkdir -pv "$(DESTDIR)/usr/bin" "$(DESTDIR)/usr/lib/drivers"
|
mkdir -pv "$(DESTDIR)/lib"
|
||||||
@mkdir -pv "$(DESTDIR)/usr/lib/init.d/" "$(DESTDIR)/usr/lib/pcid.d"
|
cp -v "$(BUILD)/$(PROFILE)/libc.a" "$(DESTDIR)/lib"
|
||||||
# Distribute binaries
|
cp -v "$(BUILD)/$(PROFILE)/libc.so" "$(DESTDIR)/lib"
|
||||||
@for bin in $(BASE_BINS); do \
|
ln -vnfs libc.so "$(DESTDIR)/lib/libc.so.6"
|
||||||
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/bin"; \
|
cp -v "$(BUILD)/$(PROFILE)/crt0.o" "$(DESTDIR)/lib"
|
||||||
done
|
ln -vnfs crt0.o "$(DESTDIR)/lib/crt1.o"
|
||||||
@for bin in $(DRIVERS_BINS); do \
|
cp -v "$(BUILD)/$(PROFILE)/crti.o" "$(DESTDIR)/lib"
|
||||||
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/lib/drivers"; \
|
cp -v "$(BUILD)/$(PROFILE)/crtn.o" "$(DESTDIR)/lib"
|
||||||
done
|
cp -v "$(BUILD)/$(PROFILE)/ld.so" "$(DESTDIR)/$(LD_SO_PATH)"
|
||||||
# Copy configurations
|
ifeq ($(USE_RUST_LIBM),)
|
||||||
@cp -v "$(SRC_DIR)/init.d"/* "$(DESTDIR)/usr/lib/init.d/"
|
cp -v "$(BUILD)/openlibm/libopenlibm.a" "$(DESTDIR)/lib/libm.a"
|
||||||
@find "$(SRC_DIR)/drivers" -maxdepth 3 -type f -name 'config.toml' | while read -r conf; do \
|
endif
|
||||||
driver=$$(basename "$$(dirname "$$conf")"); \
|
# Empty libraries for dl, pthread, and rt
|
||||||
cp -v "$$conf" "$(DESTDIR)/usr/lib/pcid.d/$$driver.toml"; \
|
$(AR) -rcs "$(DESTDIR)/lib/libdl.a"
|
||||||
|
$(AR) -rcs "$(DESTDIR)/lib/libpthread.a"
|
||||||
|
$(AR) -rcs "$(DESTDIR)/lib/librt.a"
|
||||||
|
|
||||||
|
install-tests: tests
|
||||||
|
$(MAKE) -C tests
|
||||||
|
mkdir -p "$(DESTDIR)/relibc-tests"
|
||||||
|
cp -vr tests/build_$(TARGET)/* "$(DESTDIR)/relibc-tests/"
|
||||||
|
|
||||||
|
install: install-headers install-libs
|
||||||
|
|
||||||
|
submodules:
|
||||||
|
git submodule sync
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
sysroot:
|
||||||
|
@mkdir -p $@
|
||||||
|
|
||||||
|
.PHONY: sysroot/$(TARGET)
|
||||||
|
sysroot/$(TARGET): | sysroot
|
||||||
|
rm -rf $@
|
||||||
|
rm -rf $@.partial
|
||||||
|
mkdir -p $@.partial
|
||||||
|
$(MAKE) install DESTDIR=$(shell pwd)/$@.partial
|
||||||
|
mv $@.partial $@
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
test: sysroot/$(TARGET)
|
||||||
|
# TODO: Fix SIGILL when running cargo test
|
||||||
|
# $(CARGO_TEST) test
|
||||||
|
$(MAKE) -C tests run
|
||||||
|
|
||||||
|
test-once: sysroot/$(TARGET)
|
||||||
|
$(MAKE) -C tests run-once TESTBIN=$(TESTBIN)
|
||||||
|
|
||||||
|
|
||||||
|
$(BUILD)/$(PROFILE)/libc.so: $(BUILD)/$(PROFILE)/libc.a
|
||||||
|
$(CC) -nostdlib \
|
||||||
|
-shared \
|
||||||
|
-Wl,--gc-sections \
|
||||||
|
-Wl,-z,pack-relative-relocs \
|
||||||
|
-Wl,--sort-common \
|
||||||
|
-Wl,--whole-archive $^ -Wl,--no-whole-archive \
|
||||||
|
-Wl,-soname,libc.so.6 \
|
||||||
|
$(LINKFLAGS) \
|
||||||
|
-o $@
|
||||||
|
|
||||||
|
$(BUILD)/$(PROFILE)/ld.so: $(BUILD)/$(PROFILE)/ld_so.o $(BUILD)/$(PROFILE)/libc.a
|
||||||
|
# TODO: merge ld.so with libc.so: --dynamic-list=dynamic-list-file
|
||||||
|
$(LD) --shared -Bsymbolic --no-relax -T ld_so/ld_script/$(TARGET).ld --gc-sections $^ -o $@
|
||||||
|
|
||||||
|
$(BUILD)/$(PROFILE)/libc.a: $(BUILD)/$(PROFILE)/librelibc.a $(BUILD)/openlibm/libopenlibm.a
|
||||||
|
echo "create $@" > "$@.mri"
|
||||||
|
for lib in $^; do\
|
||||||
|
echo "addlib $$lib" >> "$@.mri"; \
|
||||||
done
|
done
|
||||||
|
echo "save" >> "$@.mri"
|
||||||
|
echo "end" >> "$@.mri"
|
||||||
|
$(AR) -M < "$@.mri"
|
||||||
|
|
||||||
rm -rf "$(BUILD_DIR)/initfs"
|
# Debug targets
|
||||||
# Distribute initfs binaries
|
|
||||||
@mkdir -pv "$(BUILD_DIR)/initfs/bin" "$(BUILD_DIR)/initfs/lib/drivers"
|
$(BUILD)/debug/librelibc.a: $(SRC)
|
||||||
for bin in $(INITFS_BINS); do \
|
$(CARGO) rustc $(CARGOFLAGS) $(FEATURE_MATH) -- --emit link=$@ -g -C debug-assertions=no $(RUSTCFLAGS)
|
||||||
cp -v "$(TARGET_DIR)/$$bin" "$(BUILD_DIR)/initfs/bin"; \
|
./renamesyms.sh "$@" "$(BUILD)/debug/deps/"
|
||||||
done
|
./stripcore.sh "$@"
|
||||||
for bin in $(INITFS_DRIVERS_BINS); do \
|
touch $@
|
||||||
cp -v "$(TARGET_DIR)/$$bin" "$(BUILD_DIR)/initfs/lib/drivers"; \
|
|
||||||
done
|
$(BUILD)/debug/crt0.o: $(SRC)
|
||||||
cp "$(SYSROOT)/bin/redoxfs" "$(BUILD_DIR)/initfs/bin"
|
$(CARGO) rustc --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
# Copy initfs config files
|
touch $@
|
||||||
@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/"
|
$(BUILD)/debug/crti.o: $(SRC)
|
||||||
cp "$(SRC_DIR)/drivers/initfs.toml" "$(BUILD_DIR)/initfs/lib/pcid.d/initfs.toml"
|
$(CARGO) rustc --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
# Build initfs
|
touch $@
|
||||||
$(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"
|
$(BUILD)/debug/crtn.o: $(SRC)
|
||||||
# Distribute initfs
|
$(CARGO) rustc --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
@mkdir -pv "$(DESTDIR)/usr/lib/boot"
|
touch $@
|
||||||
cp -v "$(BUILD_DIR)/initfs.img" "$(DESTDIR)/usr/lib/boot/initfs"
|
|
||||||
|
$(BUILD)/debug/ld_so.o: $(SRC)
|
||||||
|
$(CARGO) rustc --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort -g -C debug-assertions=no $(RUSTCFLAGS)
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
# Release targets
|
||||||
|
|
||||||
|
$(BUILD)/release/librelibc.a: $(SRC)
|
||||||
|
$(CARGO) rustc --release $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS)
|
||||||
|
@# TODO: Better to only allow a certain whitelisted set of symbols? Perhaps
|
||||||
|
@# use some cbindgen hook, specify them manually, or grep for #[unsafe(no_mangle)].
|
||||||
|
./renamesyms.sh "$@" "$(BUILD)/release/deps/"
|
||||||
|
./stripcore.sh "$@"
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(BUILD)/release/crt0.o: $(SRC)
|
||||||
|
$(CARGO) rustc --release --manifest-path src/crt0/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(BUILD)/release/crti.o: $(SRC)
|
||||||
|
$(CARGO) rustc --release --manifest-path src/crti/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(BUILD)/release/crtn.o: $(SRC)
|
||||||
|
$(CARGO) rustc --release --manifest-path src/crtn/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(BUILD)/release/ld_so.o: $(SRC)
|
||||||
|
$(CARGO) rustc --release --manifest-path ld_so/Cargo.toml $(CARGOFLAGS) -- --emit obj=$@ -C panic=abort $(RUSTCFLAGS)
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
# Other targets
|
||||||
|
|
||||||
|
$(BUILD)/openlibm: openlibm
|
||||||
|
rm -rf $@ $@.partial
|
||||||
|
mkdir -p $(BUILD)
|
||||||
|
cp -r $< $@.partial
|
||||||
|
mv $@.partial $@
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
ifeq ($(USE_RUST_LIBM),)
|
||||||
|
$(BUILD)/openlibm/libopenlibm.a: $(BUILD)/openlibm $(BUILD)/$(PROFILE)/librelibc.a
|
||||||
|
$(MAKE) -s AR=$(AR) CC="$(CC_WRAPPER) $(CC)" LD=$(LD) CPPFLAGS="$(CPPFLAGS) -fno-stack-protector -I$(shell pwd)/include -I$(TARGET_HEADERS)" -C $< libopenlibm.a
|
||||||
|
./renamesyms.sh "$@" "$(BUILD)/release/deps/"
|
||||||
|
else
|
||||||
|
$(BUILD)/openlibm/libopenlibm.a:
|
||||||
|
mkdir -p "$(BUILD)/openlibm"
|
||||||
|
$(AR) -rcs "$(BUILD)/openlibm/libopenlibm.a"
|
||||||
|
endif
|
||||||
|
|||||||
@@ -1,43 +1,173 @@
|
|||||||
# Base
|
# Redox C Library (relibc)
|
||||||
|
|
||||||
Repository containing various system daemons, that are considered fundamental for the OS.
|
relibc is a portable C standard library written in Rust and is under heavy development, this library contain the following items:
|
||||||
|
|
||||||
You can see what each component does in the following list:
|
- C, Linux, BSD functions and extensions
|
||||||
|
- POSIX compatibility layer
|
||||||
|
- Interfaces for system components
|
||||||
|
|
||||||
- audiod : Daemon used to process the sound drivers audio
|
The motivation for this project is twofold: Reduce issues that the Redox developers were having with [newlib](https://sourceware.org/newlib/), and create a more stable and safe alternative to C standard libraries written in C. It is mainly designed to be used under Redox, as an alternative to newlib, but it also supports Linux via the [sc](https://crates.io/crates/sc) crate.
|
||||||
- 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
|
|
||||||
|
|
||||||
## How To Contribute
|
Currently Redox and Linux are supported.
|
||||||
|
|
||||||
To learn how to contribute you need to read the following document:
|
## `redox-rt`
|
||||||
|
|
||||||
- [CONTRIBUTING.md](https://gitlab.redox-os.org/redox-os/redox/-/blob/master/CONTRIBUTING.md)
|
`redox-rt` is a runtime library that provides much of the code that enables POSIX on Redox, like `fork`, `exec`, signal handling, etc.
|
||||||
|
Relibc uses it as backend in `src/platform/redox`, and it's intended to eventually be usable independently, without relibc.
|
||||||
|
|
||||||
If you want to contribute to drivers read its [README](drivers/README.md)
|
## Repository Layout
|
||||||
|
|
||||||
## Development
|
- `include` - Header files (mostly macros and variadic functions `cbindgen` can't generate)
|
||||||
|
- `src` - Source files
|
||||||
|
- `src/c` - C code
|
||||||
|
- `src/crt0` - Runtime code
|
||||||
|
- `src/crti` - Runtime code
|
||||||
|
- `src/crtn` - Runtime code
|
||||||
|
- `src/header` - Header files implementation
|
||||||
|
- `src/header/*` - Each folder has a `cbindgen.toml` file, it generates a C-to-Rust interface and header files
|
||||||
|
- `src/ld_so` - Dynamic loader code
|
||||||
|
- `src/platform` - Platform-specific and common code
|
||||||
|
- `src/platform/redox` - Redox-specific code
|
||||||
|
- `src/platform/linux` - Linux-specific code
|
||||||
|
- `src/pthread` - pthread implementation
|
||||||
|
- `src/sync` - Synchronization primitives
|
||||||
|
- `tests` - C tests (each MR needs to give success in all of them)
|
||||||
|
|
||||||
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.
|
## Download the sources
|
||||||
|
|
||||||
### How To Build
|
To download the relibc sources run the following command:
|
||||||
|
|
||||||
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.
|
```sh
|
||||||
|
git clone --recursive https://gitlab.redox-os.org/redox-os/relibc
|
||||||
|
```
|
||||||
|
|
||||||
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:
|
## Build Instructions
|
||||||
- `./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 build relibc out of the Redox build system, do the following steps:
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Install `cbindgen`
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo install cbindgen
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Install the `expect` tool
|
||||||
|
|
||||||
|
- Debian, Ubuntu and PopOS:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install expect
|
||||||
|
```
|
||||||
|
|
||||||
|
- Fedora:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo dnf install expect
|
||||||
|
```
|
||||||
|
|
||||||
|
- Arch Linux:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo pacman -S expect
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Relibc
|
||||||
|
|
||||||
|
To build the relibc library objects, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make all
|
||||||
|
```
|
||||||
|
|
||||||
|
- Clean old library objects and tests
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build relibc inside the Redox build system
|
||||||
|
|
||||||
|
Inside of your Redox build system, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make prefix
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to rebuild `relibc` for testing a Cookbook recipe, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
touch relibc
|
||||||
|
make prefix r.recipe-name
|
||||||
|
```
|
||||||
|
|
||||||
|
Touching (changing the "last modified time" of) the `relibc` folder is needed to trigger recompilation for `make prefix`. Replace `recipe-name` with your desired recipe name.
|
||||||
|
|
||||||
|
Note: Do not edit `relibc` inside `prefix` folder! Do your work on `relibc` folder directly inside your Redox build system instead.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Relibc has a test suite that also runs every time a new commit get pushed. You can see `.gitlab-ci.yml` to see how it's being executed. That being said, `./check.sh` is the recommended way to run tests. Here's few examples:
|
||||||
|
|
||||||
|
+ `./check.sh` - Run build, without running the test
|
||||||
|
+ `./check.sh --test` - Run all tests in x86_64 Redox using Redoxer
|
||||||
|
+ `./check.sh --test --host` - Run all tests in host (Linux)
|
||||||
|
+ `./check.sh --test --arch=aarch64` - Run all tests in specified arch
|
||||||
|
- Arch can be `x86_64`, `aarch64`, `i586`, or `riscv64gc`
|
||||||
|
+ `./check.sh --test=stdio/printf` - Run a single test
|
||||||
|
- Can be combined with `--host` or `--arch`
|
||||||
|
- Will run statically linked test in Linux, dynamically linked in Redox
|
||||||
|
|
||||||
|
Couple of notes:
|
||||||
|
|
||||||
|
- Relibc and its tests will rebuild if files changed, however switching between arch or host requires you to run `make clean`
|
||||||
|
- Redoxer is needed to run tests for Redox without `--host`. You can install it using `cargo install redoxer`
|
||||||
|
- Tests can hangs, the test runner can anticipate this, assuming the kernel doesn't hang too.
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
#### I'm building for my own platform which I run, and am getting `x86_64-linux-gnu-ar: command not found` (or similar)
|
||||||
|
|
||||||
|
The Makefile expects GNU compiler tools prefixed with the platform specifier, as would be present when you installed a cross compiler. Since you are building for your own platform, some Linux distributions (like Manjaro) don't install/symlink the prefixed executables.
|
||||||
|
|
||||||
|
An easy fix would be to replace the corresponding lines in `config.mk`, e.g.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
ifeq ($(TARGET),x86_64-unknown-linux-gnu)
|
||||||
|
- export CC=x86_64-linux-gnu-gcc
|
||||||
|
- export LD=x86_64-linux-gnu-ld
|
||||||
|
- export AR=x86_64-linux-gnu-ar
|
||||||
|
- export NM=x86_64-linux-gnu-nm
|
||||||
|
+ export CC=gcc
|
||||||
|
+ export LD=ld
|
||||||
|
+ export AR=ar
|
||||||
|
+ export NM=nm
|
||||||
|
export OBJCOPY=objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/ld64.so.1
|
||||||
|
endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Before starting to contribute, read [this](CONTRIBUTING.md) document.
|
||||||
|
|
||||||
|
## Supported OSes
|
||||||
|
|
||||||
|
- Redox OS
|
||||||
|
- Linux
|
||||||
|
|
||||||
|
## Supported architectures
|
||||||
|
|
||||||
|
- i586 (Intel/AMD)
|
||||||
|
- x86_64 (Intel/AMD)
|
||||||
|
- aarch64 (ARM64)
|
||||||
|
- riscv64gc (RISC-V)
|
||||||
|
|
||||||
|
## Funding - _Unix-style Signals and Process Management_
|
||||||
|
|
||||||
|
This project is funded through [NGI Zero Core](https://nlnet.nl/core), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/project/RedoxOS-Signals).
|
||||||
|
|
||||||
|
[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)
|
||||||
|
[<img src="https://nlnet.nl/image/logos/NGI0_tag.svg" alt="NGI Zero Logo" width="20%" />](https://nlnet.nl/core)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -1,94 +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 = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[unstable]
|
|
||||||
build-std = ["core", "alloc", "compiler_builtins"]
|
|
||||||
build-std-features = ["compiler-builtins-mem"]
|
|
||||||
Generated
-241
@@ -1,241 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arrayvec"
|
|
||||||
version = "0.7.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "2.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bootstrap"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"arrayvec",
|
|
||||||
"hashbrown",
|
|
||||||
"libredox",
|
|
||||||
"linked_list_allocator",
|
|
||||||
"log",
|
|
||||||
"plain",
|
|
||||||
"redox-initfs",
|
|
||||||
"redox-path",
|
|
||||||
"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"
|
|
||||||
source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#2b69838a481b7be9826b41e06a0b8f1346b80f9e"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "goblin"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9"
|
|
||||||
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.181"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libredox"
|
|
||||||
version = "0.1.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"libc",
|
|
||||||
"plain",
|
|
||||||
"redox_syscall",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked_list_allocator"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
|
|
||||||
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.29"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
|
||||||
|
|
||||||
[[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.44"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
|
||||||
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-rt"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#2b69838a481b7be9826b41e06a0b8f1346b80f9e"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"generic-rt",
|
|
||||||
"goblin",
|
|
||||||
"ioslice",
|
|
||||||
"libredox",
|
|
||||||
"plain",
|
|
||||||
"redox-path",
|
|
||||||
"redox_syscall",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox-scheme"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e58cb7dee1058575f320f9bfa32c7c0eda857e285c2a163a0eb595d37add09cd"
|
|
||||||
dependencies = [
|
|
||||||
"libredox",
|
|
||||||
"redox_syscall",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.7.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
|
|
||||||
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.114"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.23"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bootstrap"
|
|
||||||
description = "Userspace bootstrapper"
|
|
||||||
version = "0.0.0"
|
|
||||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
|
||||||
edition = "2024"
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
hashbrown = { version = "0.15", default-features = false, features = [
|
|
||||||
"inline-more",
|
|
||||||
"default-hasher",
|
|
||||||
] }
|
|
||||||
linked_list_allocator = "0.10"
|
|
||||||
libredox = { version = "0.1.16", default-features = false, features = ["protocol"] }
|
|
||||||
log = { version = "0.4", default-features = false }
|
|
||||||
plain = "0.2"
|
|
||||||
redox-initfs = { path = "../initfs", default-features = false }
|
|
||||||
redox_syscall = "0.7.4"
|
|
||||||
redox-scheme = { version = "0.11.0", default-features = false }
|
|
||||||
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 = { git = "https://gitlab.redox-os.org/redox-os/relibc.git", default-features = false }
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
panic = "abort"
|
|
||||||
lto = "fat"
|
|
||||||
opt-level = "s"
|
|
||||||
|
|
||||||
[profile.dev]
|
|
||||||
panic = "abort"
|
|
||||||
opt-level = "s"
|
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
@@ -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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
@@ -1,356 +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 syscall::CallFlags;
|
|
||||||
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
|
|
||||||
use syscall::flag::{O_CLOEXEC, O_RDONLY, O_STAT};
|
|
||||||
use syscall::{EINTR, Error};
|
|
||||||
|
|
||||||
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 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 = auth
|
|
||||||
.dup(b"cur-context")
|
|
||||||
.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(
|
|
||||||
syscall::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 cwd_fd = FdGuard::new(
|
|
||||||
syscall::openat(initns_fd.as_raw_fd(), "/scheme/initfs", O_STAT, 0)
|
|
||||||
.expect("failed to open cwd fd"),
|
|
||||||
)
|
|
||||||
.to_upper()
|
|
||||||
.unwrap();
|
|
||||||
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()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = "/scheme/initfs/bin/init";
|
|
||||||
|
|
||||||
let image_file = FdGuard::new(
|
|
||||||
syscall::openat(extrainfo.ns_fd.unwrap(), path, O_RDONLY | O_CLOEXEC, 0)
|
|
||||||
.expect("failed to open init"),
|
|
||||||
)
|
|
||||||
.to_upper()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let exe_path = alloc::format!("/scheme/initfs{}", path);
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
.expect("failed to execute init");
|
|
||||||
|
|
||||||
// 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_file = FdGuard::new(
|
|
||||||
syscall::openat(
|
|
||||||
extrainfo.ns_fd.unwrap(), // initns, not initfs!
|
|
||||||
interp_cstr.to_str().expect("interpreter not UTF-8"),
|
|
||||||
O_RDONLY | O_CLOEXEC,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.expect("failed to open dynamic linker"),
|
|
||||||
)
|
|
||||||
.to_upper()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
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(
|
|
||||||
syscall::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(
|
|
||||||
syscall::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 syscall::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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
@@ -1,487 +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
|
|
||||||
) {
|
|
||||||
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 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 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
|
|
||||||
}
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
use alloc::rc::Rc;
|
|
||||||
use alloc::string::{String, ToString};
|
|
||||||
use alloc::sync::Arc;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::cell::RefCell;
|
|
||||||
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;
|
|
||||||
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(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct NamespaceAccess {
|
|
||||||
namespace: Rc<RefCell<Namespace>>,
|
|
||||||
permission: NsPermissions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NamespaceAccess {
|
|
||||||
fn has_permission(&self, permission: NsPermissions) -> bool {
|
|
||||||
self.permission.contains(permission)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct SchemeRegister {
|
|
||||||
target_namespace: Rc<RefCell<Namespace>>,
|
|
||||||
scheme_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemeRegister {
|
|
||||||
fn register(&self, fd: FdGuard) -> Result<()> {
|
|
||||||
let mut ns = self.target_namespace.borrow_mut();
|
|
||||||
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(Debug, 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: Rc::new(RefCell::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(syscall::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_with_filter(
|
|
||||||
cap_fd.as_raw_fd(),
|
|
||||||
reference,
|
|
||||||
flags,
|
|
||||||
fcntl_flags as usize,
|
|
||||||
ctx.uid,
|
|
||||||
ctx.gid,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(scheme_fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fork_namespace(&mut self, namespace: Rc<RefCell<Namespace>>, names: &[u8]) -> Result<usize> {
|
|
||||||
let new_id = self.next_id;
|
|
||||||
let new_namespace = namespace.borrow().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(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => self.open_scheme_resource(
|
|
||||||
&ns_access.namespace.borrow(),
|
|
||||||
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.borrow_mut();
|
|
||||||
|
|
||||||
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_with_filter(cap_fd.as_raw_fd(), reference, flags, ctx.uid, ctx.gid)?;
|
|
||||||
|
|
||||||
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.borrow();
|
|
||||||
|
|
||||||
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!()
|
|
||||||
}
|
|
||||||
@@ -1,154 +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;
|
|
||||||
|
|
||||||
struct Writer;
|
|
||||||
|
|
||||||
impl Write for Writer {
|
|
||||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
|
||||||
syscall::write(1, s.as_bytes())
|
|
||||||
.map_err(|_| core::fmt::Error)
|
|
||||||
.map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = writeln!(&mut Writer, "{}", 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
@@ -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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
use syscall::flag::MapFlags;
|
|
||||||
|
|
||||||
mod offsets {
|
|
||||||
unsafe extern "C" {
|
|
||||||
// text (R-X)
|
|
||||||
static __text_start: u8;
|
|
||||||
static __text_end: u8;
|
|
||||||
// rodata (R--)
|
|
||||||
static __rodata_start: u8;
|
|
||||||
static __rodata_end: u8;
|
|
||||||
// data+bss (RW-)
|
|
||||||
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() -> ! {
|
|
||||||
// Remap self, from the previous RWX
|
|
||||||
|
|
||||||
let (text_start, text_end) = offsets::text();
|
|
||||||
let (rodata_start, rodata_end) = offsets::rodata();
|
|
||||||
let (data_start, data_end) = offsets::data_and_bss();
|
|
||||||
|
|
||||||
// NOTE: Assuming the debug scheme root fd is always placed at this position
|
|
||||||
let debug_fd = syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
|
|
||||||
let _ = syscall::openat(debug_fd, "", syscall::O_RDONLY, 0); // stdin
|
|
||||||
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stdout
|
|
||||||
let _ = syscall::openat(debug_fd, "", syscall::O_WRONLY, 0); // stderr
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
@@ -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*)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
extern crate cc;
|
||||||
|
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _crate_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
|
||||||
|
let target = env::var("TARGET").unwrap();
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=src/c");
|
||||||
|
|
||||||
|
let mut cc_builder = &mut cc::Build::new();
|
||||||
|
|
||||||
|
cc_builder = cc_builder.flag("-nostdinc").flag("-nostdlib");
|
||||||
|
|
||||||
|
if target.starts_with("aarch64") {
|
||||||
|
cc_builder = cc_builder.flag("-mno-outline-atomics")
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_builder
|
||||||
|
.flag("-fno-stack-protector")
|
||||||
|
.flag("-Wno-expansion-to-defined")
|
||||||
|
.files(
|
||||||
|
fs::read_dir("src/c")
|
||||||
|
.expect("src/c directory missing")
|
||||||
|
.map(|res| res.expect("read_dir error").path()),
|
||||||
|
)
|
||||||
|
.compile("relibc_c");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-lib=static=relibc_c");
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
# needs a leading newline
|
||||||
|
[defines]
|
||||||
|
"target_os=redox" = "__redox__"
|
||||||
|
"target_os=linux" = "__linux__"
|
||||||
|
"target_pointer_width=64" = "__LP64__"
|
||||||
|
"target_pointer_width=32" = "__ILP32__"
|
||||||
|
"target_arch=x86" = "__i386__"
|
||||||
|
"target_arch=x86_64" = "__x86_64__"
|
||||||
|
"target_arch=aarch64" = "__aarch64__"
|
||||||
|
# This is not exact. It should be `defined(__riscv) && defined(__LP64__)`, or `defined(__riscv) && __riscv_xlen==64`
|
||||||
|
# This will do however, as long as we only support riscv64 and not riscv32
|
||||||
|
"target_arch=riscv64" = "__riscv"
|
||||||
|
|
||||||
|
# XXX: silences a warning
|
||||||
|
"feature = no_std" = "__relibc__"
|
||||||
|
|
||||||
|
# Ensure attributes are passed down from Rust
|
||||||
|
# <features.h> must be included where attributes are used in relibc
|
||||||
|
[fn]
|
||||||
|
must_use = "__nodiscard"
|
||||||
|
deprecated = "__deprecated"
|
||||||
|
deprecated_with_note = "__deprecatedNote({})"
|
||||||
|
no_return = "__noreturn"
|
||||||
@@ -8,10 +8,14 @@ show_help() {
|
|||||||
echo "Usage: $(basename "$0") [OPTIONS]"
|
echo "Usage: $(basename "$0") [OPTIONS]"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Description:"
|
echo "Description:"
|
||||||
echo " Wrapper for redoxer to run checks or tests on Redox OS targets."
|
echo " Wrapper for Makefile / Cargo to run checks or tests on Redox OS targets."
|
||||||
echo ""
|
echo ""
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --test Run 'cargo test' instead of 'cargo check'"
|
echo " --test Run 'make test' instead of 'make all'"
|
||||||
|
echo " --test= Run single 'make test'"
|
||||||
|
echo " --cargo Run 'cargo check' / 'cargo test' instead"
|
||||||
|
echo " (note: cargo test is currently not maintained for relibc)"
|
||||||
|
echo " --host Run the command on host (linux) target"
|
||||||
echo " --all-target Run the command on all supported Redox architectures"
|
echo " --all-target Run the command on all supported Redox architectures"
|
||||||
echo " --target=<target> Override the target architecture (e.g., i586-unknown-redox)"
|
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 " --arch=<arch> Override the target architecture using arch (e.g., i586)"
|
||||||
@@ -21,14 +25,15 @@ show_help() {
|
|||||||
for t in "${SUPPORTED_TARGETS[@]}"; do
|
for t in "${SUPPORTED_TARGETS[@]}"; do
|
||||||
echo " - $t"
|
echo " - $t"
|
||||||
done
|
done
|
||||||
|
echo " - $(uname -m)-unknown-linux-gnu"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Environment:"
|
echo "Environment:"
|
||||||
echo " TARGET Sets the default target (overridden by --target)"
|
echo " TARGET Sets the default target (overridden by --target)"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ! command -v redoxer &> /dev/null; then
|
if ! command -v cbindgen &> /dev/null; then
|
||||||
echo "Error: 'redoxer' CLI not found."
|
echo "Error: 'cbindgen' CLI not found."
|
||||||
echo "Please install it: cargo install redoxer"
|
echo "Please install it: cargo install cbindgen"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -41,14 +46,31 @@ SUPPORTED_TARGETS=(
|
|||||||
|
|
||||||
CURRENT_TARGET="${TARGET:-x86_64-unknown-redox}"
|
CURRENT_TARGET="${TARGET:-x86_64-unknown-redox}"
|
||||||
CHECK_ALL=false
|
CHECK_ALL=false
|
||||||
CMD_ACTION="all"
|
CMD_ACTION="make"
|
||||||
|
CARGO_ACTION="check"
|
||||||
|
MAKE_ACTION="all"
|
||||||
|
TEST_BIN=""
|
||||||
|
IS_HOST=0
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--all-target)
|
--all-target)
|
||||||
CHECK_ALL=true
|
CHECK_ALL=true
|
||||||
;;
|
;;
|
||||||
--test)
|
--test)
|
||||||
CMD_ACTION="test"
|
MAKE_ACTION="test"
|
||||||
|
CARGO_ACTION="test"
|
||||||
|
;;
|
||||||
|
--test=*)
|
||||||
|
TEST_BIN="${1#*=}"
|
||||||
|
MAKE_ACTION="test-once"
|
||||||
|
;;
|
||||||
|
--cargo)
|
||||||
|
CMD_ACTION="cargo"
|
||||||
|
;;
|
||||||
|
--host)
|
||||||
|
CURRENT_TARGET="$(uname -m)-unknown-linux-gnu"
|
||||||
|
IS_HOST=1
|
||||||
;;
|
;;
|
||||||
--target=*)
|
--target=*)
|
||||||
CURRENT_TARGET="${1#*=}"
|
CURRENT_TARGET="${1#*=}"
|
||||||
@@ -69,32 +91,65 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$IS_HOST" -eq 0 ]; then
|
||||||
|
if ! command -v redoxer &> /dev/null; then
|
||||||
|
echo "Error: 'redoxer' CLI not found."
|
||||||
|
echo "Please install it: cargo install redoxer"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
run_redoxer() {
|
run_redoxer() {
|
||||||
export TARGET=$1
|
export TARGET=$1
|
||||||
redoxer toolchain || { echo -e "${RED}Fail: redoxer toolchain for: $target.${NC}" && exit 1; }
|
REDOXER_ENV="redoxer env"
|
||||||
|
if [ "$IS_HOST" -eq 0 ]; then
|
||||||
|
redoxer toolchain || { echo -e "${RED}Fail: redoxer toolchain for: $target.${NC}" && exit 1; }
|
||||||
|
export CARGO_TEST="redoxer"
|
||||||
|
export TEST_RUNNER="redoxer exec --folder ../../sysroot/$TARGET/:/usr --folder . --"
|
||||||
|
# TODO: Identify hang issue with pthread/barrier and pthread/once tests in multi core to get rid of this limit
|
||||||
|
export REDOXER_QEMU_ARGS="-smp 1"
|
||||||
|
|
||||||
|
MAKE_ACTION="$MAKE_ACTION IS_REDOX=1"
|
||||||
|
else
|
||||||
|
REDOXER_ENV=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TEST_BIN" != "" ]; then
|
||||||
|
if [ "$IS_HOST" -eq 0 ]; then
|
||||||
|
MAKE_ACTION="$MAKE_ACTION TESTBIN=bins_dynamic/$TEST_BIN"
|
||||||
|
else
|
||||||
|
MAKE_ACTION="$MAKE_ACTION TESTBIN=bins_static/$TEST_BIN"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$CMD_ACTION" == "make" ]; then
|
||||||
|
CMD_OPT="-j $(nproc) $MAKE_ACTION"
|
||||||
|
else
|
||||||
|
CMD_OPT="$CARGO_ACTION"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
echo "Running make $CMD_ACTION for: $TARGET"
|
echo "Running $REDOXER_ENV $CMD_ACTION $CMD_OPT for: $TARGET"
|
||||||
|
|
||||||
if make "$CMD_ACTION"; then
|
if $REDOXER_ENV $CMD_ACTION $CMD_OPT; then
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
echo -e "${RED}Fail: $CMD_ACTION $TARGET failed.${NC}"
|
echo -e "${RED}Fail: $CMD_ACTION $CMD_OPT for $TARGET failed.${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$CHECK_ALL" = true ]; then
|
if [ "$CHECK_ALL" = true ]; then
|
||||||
echo "Running $CMD_ACTION for all supported Redox targets..."
|
echo "Running $CMD_ACTION for all supported Redox targets..."
|
||||||
|
|
||||||
has_error=false
|
has_error=false
|
||||||
|
|
||||||
for target in "${SUPPORTED_TARGETS[@]}"; do
|
for target in "${SUPPORTED_TARGETS[@]}"; do
|
||||||
if ! run_redoxer "$target"; then
|
if ! run_redoxer "$target"; then
|
||||||
has_error=true
|
has_error=true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "----------------------------------------"
|
echo "----------------------------------------"
|
||||||
if [ "$has_error" = true ]; then
|
if [ "$has_error" = true ]; then
|
||||||
echo -e "${RED}Summary: One or more targets failed.${NC}"
|
echo -e "${RED}Summary: One or more targets failed.${NC}"
|
||||||
@@ -105,7 +160,7 @@ if [ "$CHECK_ALL" = true ]; then
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if run_redoxer "$CURRENT_TARGET"; then
|
if run_redoxer "$CURRENT_TARGET"; then
|
||||||
echo -e "${GREEN}Success: $CMD_ACTION $CURRENT_TARGET passed.${NC}"
|
echo -e "${GREEN}Success: $CARGO_ACTION for $CURRENT_TARGET passed.${NC}"
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
ifndef TARGET
|
||||||
|
export TARGET:=$(shell rustc -Z unstable-options --print target-spec-json | grep llvm-target | cut -d '"' -f4)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),aarch64-unknown-linux-gnu)
|
||||||
|
export CC=aarch64-linux-gnu-gcc
|
||||||
|
export LD=aarch64-linux-gnu-ld
|
||||||
|
export AR=aarch64-linux-gnu-ar
|
||||||
|
export NM=aarch64-linux-gnu-nm
|
||||||
|
export OBJCOPY=aarch64-linux-gnu-objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/ld.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),aarch64-unknown-redox)
|
||||||
|
export CC=aarch64-unknown-redox-gcc
|
||||||
|
export LD=aarch64-unknown-redox-ld
|
||||||
|
export AR=aarch64-unknown-redox-ar
|
||||||
|
export NM=aarch64-unknown-redox-nm
|
||||||
|
export OBJCOPY=aarch64-unknown-redox-objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/ld.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),i586-unknown-redox)
|
||||||
|
export CC=i586-unknown-redox-gcc
|
||||||
|
export LD=i586-unknown-redox-ld
|
||||||
|
export AR=i586-unknown-redox-ar
|
||||||
|
export NM=i586-unknown-redox-nm
|
||||||
|
export OBJCOPY=i586-unknown-redox-objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/libc.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),i686-unknown-redox)
|
||||||
|
export CC=i686-unknown-redox-gcc
|
||||||
|
export LD=i686-unknown-redox-ld
|
||||||
|
export AR=i686-unknown-redox-ar
|
||||||
|
export NM=i686-unknown-redox-nm
|
||||||
|
export OBJCOPY=i686-unknown-redox-objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/libc.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),x86_64-unknown-linux-gnu)
|
||||||
|
export CC=x86_64-linux-gnu-gcc
|
||||||
|
export LD=x86_64-linux-gnu-ld
|
||||||
|
export AR=x86_64-linux-gnu-ar
|
||||||
|
export NM=x86_64-linux-gnu-nm
|
||||||
|
export OBJCOPY=objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/ld64.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),x86_64-unknown-redox)
|
||||||
|
export CC=x86_64-unknown-redox-gcc
|
||||||
|
export LD=x86_64-unknown-redox-ld
|
||||||
|
export AR=x86_64-unknown-redox-ar
|
||||||
|
export NM=x86_64-unknown-redox-nm
|
||||||
|
export OBJCOPY=x86_64-unknown-redox-objcopy
|
||||||
|
export CPPFLAGS=
|
||||||
|
LD_SO_PATH=lib/ld64.so.1
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(TARGET),riscv64gc-unknown-redox)
|
||||||
|
export CC=riscv64-unknown-redox-gcc
|
||||||
|
export LD=riscv64-unknown-redox-ld
|
||||||
|
export AR=riscv64-unknown-redox-ar
|
||||||
|
export NM=riscv64-unknown-redox-nm
|
||||||
|
export OBJCOPY=riscv64-unknown-redox-objcopy
|
||||||
|
export CPPFLAGS=-march=rv64gc -mabi=lp64d
|
||||||
|
LD_SO_PATH=lib/ld.so.1
|
||||||
|
endif
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "config"
|
|
||||||
description = "Configuration override library"
|
|
||||||
version = "0.0.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -1,139 +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()) };
|
|
||||||
|
|
||||||
if let Err(err) = cmd.spawn() {
|
|
||||||
eprintln!("daemon: failed to execute {cmd:?}: {err}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut data = [0];
|
|
||||||
match read_pipe.read_exact(&mut data) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
|
||||||
eprintln!("daemon: {cmd:?} exited without notifying readiness");
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("daemon: failed to wait for {cmd:?}: {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<()> {
|
|
||||||
syscall::call_wo(
|
|
||||||
self.write_pipe.as_raw_fd() as usize,
|
|
||||||
&cap_fd.into_raw().to_ne_bytes(),
|
|
||||||
syscall::CallFlags::FD,
|
|
||||||
&[],
|
|
||||||
)?;
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +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],
|
|
||||||
}
|
|
||||||
@@ -1,497 +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;
|
|
||||||
|
|
||||||
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: {} Current IP: {}",
|
|
||||||
current_mac.to_string(),
|
|
||||||
current_ip.trim()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 udp"
|
|
||||||
);
|
|
||||||
try_fmt!(
|
|
||||||
socket.set_read_timeout(Some(Duration::new(30, 0))),
|
|
||||||
"failed to set read timeout"
|
|
||||||
);
|
|
||||||
try_fmt!(
|
|
||||||
socket.set_write_timeout(Some(Duration::new(30, 0))),
|
|
||||||
"failed to set write timeout"
|
|
||||||
);
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut discover = Dhcp {
|
|
||||||
op: 1,
|
|
||||||
htype: 1,
|
|
||||||
hlen: 6,
|
|
||||||
hops: 0,
|
|
||||||
tid,
|
|
||||||
secs: 0,
|
|
||||||
flags: 0x8000u16.to_be(),
|
|
||||||
ciaddr: [0, 0, 0, 0],
|
|
||||||
yiaddr: [0, 0, 0, 0],
|
|
||||||
siaddr: [0, 0, 0, 0],
|
|
||||||
giaddr: [0, 0, 0, 0],
|
|
||||||
chaddr: [
|
|
||||||
current_mac.bytes[0],
|
|
||||||
current_mac.bytes[1],
|
|
||||||
current_mac.bytes[2],
|
|
||||||
current_mac.bytes[3],
|
|
||||||
current_mac.bytes[4],
|
|
||||||
current_mac.bytes[5],
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
],
|
|
||||||
sname: [0; 64],
|
|
||||||
file: [0; 128],
|
|
||||||
magic: 0x63825363u32.to_be(),
|
|
||||||
options: [0; 308],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (s, d) in [
|
|
||||||
// DHCP Message Type (Discover)
|
|
||||||
53, 1, 1, // End
|
|
||||||
255,
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.zip(discover.options.iter_mut())
|
|
||||||
{
|
|
||||||
*d = *s;
|
|
||||||
}
|
|
||||||
|
|
||||||
let discover_data = unsafe {
|
|
||||||
std::slice::from_raw_parts(
|
|
||||||
(&discover as *const Dhcp) as *const u8,
|
|
||||||
std::mem::size_of::<Dhcp>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let _sent = try_fmt!(socket.send(discover_data), "failed to send discover");
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Sent Discover");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: {:?}, Server IP: {:?}",
|
|
||||||
offer.yiaddr, offer.siaddr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut subnet_option = None;
|
|
||||||
let mut router_option = None;
|
|
||||||
let mut dns_option = None;
|
|
||||||
let mut server_id_option = None;
|
|
||||||
{
|
|
||||||
let mut options = offer.options.iter();
|
|
||||||
while let Some(option) = options.next() {
|
|
||||||
match *option {
|
|
||||||
0 => (),
|
|
||||||
255 => break,
|
|
||||||
_ => {
|
|
||||||
if let Some(len) = options.next() {
|
|
||||||
if *len as usize <= options.as_slice().len() {
|
|
||||||
let data = &options.as_slice()[..*len as usize];
|
|
||||||
for _data_i in 0..*len {
|
|
||||||
options.next();
|
|
||||||
}
|
|
||||||
match *option {
|
|
||||||
1 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Subnet Mask: {data:?}");
|
|
||||||
}
|
|
||||||
if data.len() == 4 && subnet_option.is_none() {
|
|
||||||
subnet_option = Some(Vec::from(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Router: {data:?}");
|
|
||||||
}
|
|
||||||
if data.len() == 4 && router_option.is_none() {
|
|
||||||
router_option = Some(Vec::from(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Domain Name Server: {data:?}");
|
|
||||||
}
|
|
||||||
if data.len() == 4 && dns_option.is_none() {
|
|
||||||
dns_option = Some(Vec::from(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Lease Time: {data:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Message Type: {data:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54 => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Server ID: {data:?}");
|
|
||||||
}
|
|
||||||
if data.len() == 4 {
|
|
||||||
// Store the server ID
|
|
||||||
server_id_option =
|
|
||||||
Some([data[0], data[1], data[2], data[3]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: {option}: {data:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mask_len = if let Some(subnet) = subnet_option {
|
|
||||||
let mut subnet: u32 = (subnet[0] as u32) << 24
|
|
||||||
| (subnet[1] as u32) << 16
|
|
||||||
| (subnet[2] as u32) << 8
|
|
||||||
| subnet[3] as u32;
|
|
||||||
subnet = !subnet;
|
|
||||||
subnet.leading_zeros()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
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"
|
|
||||||
);
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
let new_ip = try_fmt!(get_iface_cfg_value(iface, "addr/list"), "failed to get ip");
|
|
||||||
println!("DHCP: New IP: {}", new_ip.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(router) = router_option {
|
|
||||||
let default_route = format!(
|
|
||||||
"default via {}.{}.{}.{}",
|
|
||||||
router[0], router[1], router[2], router[3]
|
|
||||||
);
|
|
||||||
|
|
||||||
try_fmt!(
|
|
||||||
set_cfg_value("route/add", &default_route),
|
|
||||||
"failed to set default route"
|
|
||||||
);
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
let new_router = try_fmt!(get_cfg_value("route/list"), "failed to get ip router");
|
|
||||||
println!("DHCP: New Router: {}", new_router.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut dns) = dns_option {
|
|
||||||
if dns[0] == 127 {
|
|
||||||
let quad9 = [9, 9, 9, 9].to_vec();
|
|
||||||
if verbose {
|
|
||||||
println!(
|
|
||||||
"DHCP: Received sarcastic DNS suggestion {}.{}.{}.{}, using {}.{}.{}.{} instead",
|
|
||||||
dns[0], dns[1], dns[2], dns[3], quad9[0], quad9[1], quad9[2], quad9[3]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
dns = quad9;
|
|
||||||
}
|
|
||||||
|
|
||||||
let nameserver = format!("{}.{}.{}.{}", dns[0], dns[1], dns[2], dns[3]);
|
|
||||||
|
|
||||||
try_fmt!(
|
|
||||||
set_cfg_value("resolv/nameserver", &nameserver),
|
|
||||||
"failed to set name server"
|
|
||||||
);
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
let new_dns = try_fmt!(get_cfg_value("resolv/nameserver"), "failed to get dns");
|
|
||||||
println!("DHCP: New DNS: {}", new_dns.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut request = Dhcp {
|
|
||||||
op: 1,
|
|
||||||
htype: 1,
|
|
||||||
hlen: 6,
|
|
||||||
hops: 0,
|
|
||||||
tid,
|
|
||||||
secs: 0,
|
|
||||||
flags: 0,
|
|
||||||
ciaddr: [0; 4],
|
|
||||||
yiaddr: [0; 4],
|
|
||||||
siaddr: [0; 4],
|
|
||||||
giaddr: [0; 4],
|
|
||||||
chaddr: [
|
|
||||||
current_mac.bytes[0],
|
|
||||||
current_mac.bytes[1],
|
|
||||||
current_mac.bytes[2],
|
|
||||||
current_mac.bytes[3],
|
|
||||||
current_mac.bytes[4],
|
|
||||||
current_mac.bytes[5],
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
0x00,
|
|
||||||
],
|
|
||||||
sname: [0; 64],
|
|
||||||
file: [0; 128],
|
|
||||||
magic: 0x63825363u32.to_be(),
|
|
||||||
options: [0; 308],
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the server_id_option was None, use "0.0.0.0"
|
|
||||||
let server_id = server_id_option.unwrap_or([0, 0, 0, 0]);
|
|
||||||
|
|
||||||
for (s, d) in [
|
|
||||||
// DHCP Message Type (Request)
|
|
||||||
53,
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
// Requested IP Address
|
|
||||||
50,
|
|
||||||
4,
|
|
||||||
offer.yiaddr[0],
|
|
||||||
offer.yiaddr[1],
|
|
||||||
offer.yiaddr[2],
|
|
||||||
offer.yiaddr[3],
|
|
||||||
// Server Identifier - use Option 54 from the Offer
|
|
||||||
54,
|
|
||||||
4,
|
|
||||||
server_id[0],
|
|
||||||
server_id[1],
|
|
||||||
server_id[2],
|
|
||||||
server_id[3],
|
|
||||||
// End
|
|
||||||
255,
|
|
||||||
]
|
|
||||||
.iter()
|
|
||||||
.zip(request.options.iter_mut())
|
|
||||||
{
|
|
||||||
*d = *s;
|
|
||||||
}
|
|
||||||
|
|
||||||
let request_data = unsafe {
|
|
||||||
std::slice::from_raw_parts(
|
|
||||||
(&request as *const Dhcp) as *const u8,
|
|
||||||
std::mem::size_of::<Dhcp>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let _sent = try_fmt!(socket.send(request_data), "failed to send request");
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
println!("DHCP: Sent Request");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut ack_data = [0; 65536];
|
|
||||||
try_fmt!(socket.recv(&mut ack_data), "failed to receive ack");
|
|
||||||
let ack = unsafe { &*(ack_data.as_ptr() as *const Dhcp) };
|
|
||||||
if verbose {
|
|
||||||
println!(
|
|
||||||
"DHCP: Ack IP: {:?}, Server IP: {:?}",
|
|
||||||
ack.yiaddr, ack.siaddr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut verbose = false;
|
|
||||||
let iface = "eth0";
|
|
||||||
|
|
||||||
//TODO: parse iface from the args
|
|
||||||
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!(empty_mac, MacAddr::from_str("01:23-45-67-89-ab"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("01-23-45-67-89-ag"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("01.23.45.67.89.ab"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("01234-23-45-67-89-ab"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("01--23-45-67-89-ab"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("12"));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str("0:0:0:0:0:0"));
|
|
||||||
|
|
||||||
assert_eq!(mac, MacAddr::from_str(&mac.to_string()));
|
|
||||||
assert_eq!(empty_mac, MacAddr::from_str(&empty_mac.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
- os: ubuntu-latest
|
||||||
|
rust: beta
|
||||||
|
- os: ubuntu-latest
|
||||||
|
rust: nightly
|
||||||
|
- os: macos-latest
|
||||||
|
rust: stable
|
||||||
|
- os: windows-latest
|
||||||
|
rust: stable
|
||||||
|
- os: ubuntu-latest
|
||||||
|
rust: stable
|
||||||
|
target: wasm32-wasip1
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
# Configure cross-builds by adding the rustup target and configuring future
|
||||||
|
# cargo invocations.
|
||||||
|
- run: |
|
||||||
|
rustup target add ${{ matrix.target }}
|
||||||
|
echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV
|
||||||
|
if: matrix.target != ''
|
||||||
|
|
||||||
|
# For wasm install wasmtime as a test runner and configure it with Cargo.
|
||||||
|
- name: Setup `wasmtime`
|
||||||
|
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||||
|
if: matrix.target == 'wasm32-wasip1'
|
||||||
|
- run: echo CARGO_TARGET_WASM32_WASIP1_RUNNER=wasmtime >> $GITHUB_ENV
|
||||||
|
if: matrix.target == 'wasm32-wasip1'
|
||||||
|
|
||||||
|
- run: cargo test
|
||||||
|
- run: cargo test --features debug
|
||||||
|
- run: cargo test --features global
|
||||||
|
- run: cargo test --release
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true
|
||||||
|
- run: cargo test --release
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: false
|
||||||
|
- run: cargo test --features debug --release
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true
|
||||||
|
- run: RUSTFLAGS='--cfg test_lots' cargo test --release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true
|
||||||
|
- run: RUSTFLAGS='--cfg test_lots' cargo test --release --features debug
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CARGO_PROFILE_RELEASE_DEBUG_ASSERTIONS: true
|
||||||
|
|
||||||
|
rustfmt:
|
||||||
|
name: Rustfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
run: rustup update stable && rustup default stable && rustup component add rustfmt
|
||||||
|
- run: cargo fmt -- --check
|
||||||
|
|
||||||
|
wasm:
|
||||||
|
name: WebAssembly
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
run: rustup update stable && rustup default stable && rustup target add wasm32-unknown-unknown
|
||||||
|
- run: cargo build --target wasm32-unknown-unknown
|
||||||
|
- run: cargo build --target wasm32-unknown-unknown --release
|
||||||
|
|
||||||
|
external-platform:
|
||||||
|
name: external-platform
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
run: rustup update stable && rustup default stable && rustup target add x86_64-fortanix-unknown-sgx
|
||||||
|
- run: cargo build --target x86_64-fortanix-unknown-sgx
|
||||||
|
|
||||||
|
fuzz:
|
||||||
|
name: Build Fuzzers
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Rust
|
||||||
|
run: rustup update nightly && rustup default nightly
|
||||||
|
- run: cargo install cargo-fuzz
|
||||||
|
- run: cargo fuzz build --dev
|
||||||
|
|
||||||
|
miri:
|
||||||
|
name: Miri
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install Miri
|
||||||
|
run: |
|
||||||
|
rustup toolchain install nightly --component miri
|
||||||
|
rustup override set nightly
|
||||||
|
cargo miri setup
|
||||||
|
- name: Test with Miri Stack Borrows
|
||||||
|
run: cargo miri test
|
||||||
|
- name: Test with Miri Tree Borrows
|
||||||
|
run: cargo miri test
|
||||||
|
env:
|
||||||
|
MIRIFLAGS: -Zmiri-tree-borrows
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/target/
|
||||||
|
**/*.rs.bk
|
||||||
|
Cargo.lock
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
[package]
|
||||||
|
name = "dlmalloc"
|
||||||
|
version = "0.2.8"
|
||||||
|
authors = ["Alex Crichton <alex@alexcrichton.com>"]
|
||||||
|
license = "MIT/Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/alexcrichton/dlmalloc-rs"
|
||||||
|
homepage = "https://github.com/alexcrichton/dlmalloc-rs"
|
||||||
|
documentation = "https://docs.rs/dlmalloc"
|
||||||
|
description = """
|
||||||
|
A Rust port of the dlmalloc allocator
|
||||||
|
"""
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ['fuzz']
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
edition = '2021'
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ['global']
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[target.'cfg(all(unix, not(target_arch = "wasm32")))'.dependencies]
|
||||||
|
libc = { version = "0.2", default-features = false, optional = true }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# For more information on these dependencies see rust-lang/rust's
|
||||||
|
# `src/tools/rustc-std-workspace` folder
|
||||||
|
core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core' }
|
||||||
|
compiler_builtins = { version = '0.1.0', optional = true }
|
||||||
|
cfg-if = "1.0"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||||
|
version = ">=0.52.0, <=0.59.*"
|
||||||
|
features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_System_Memory",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_System_SystemInformation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
arbitrary = "1.3.2"
|
||||||
|
rand = { version = "0.8", features = ['small_rng'] }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug-assertions = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Enable implementations of the `GlobalAlloc` standard library API, exporting a
|
||||||
|
# new `GlobalDlmalloc` as well which implements this trait.
|
||||||
|
global = ["system", "rust_api"]
|
||||||
|
|
||||||
|
# Enable very expensive debug checks in this crate
|
||||||
|
debug = []
|
||||||
|
|
||||||
|
# Enables OS APIs based on the current target, can be implemented manually
|
||||||
|
# otherwise.
|
||||||
|
system = ["libc"]
|
||||||
|
|
||||||
|
rustc-dep-of-std = ['core', 'compiler_builtins/rustc-dep-of-std']
|
||||||
|
|
||||||
|
c_api = []
|
||||||
|
rust_api = []
|
||||||
|
|
||||||
|
default = ["global", "rust_api"]
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
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.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2014 Alex Crichton
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# dlmalloc-rs
|
||||||
|
|
||||||
|
A port of [dlmalloc] to Rust.
|
||||||
|
|
||||||
|
[Documentation](https://docs.rs/dlmalloc)
|
||||||
|
|
||||||
|
[dlmalloc]: https://gee.cs.oswego.edu/dl/html/malloc.html
|
||||||
|
|
||||||
|
## Why dlmalloc?
|
||||||
|
|
||||||
|
This crate is a port of [dlmalloc] to Rust, and doesn't rely on C. The primary
|
||||||
|
purpose of this crate is to serve as the default allocator for Rust on the
|
||||||
|
`wasm32-unknown-unknown` target. At the time this was written the wasm target
|
||||||
|
didn't support C code, so it was required to have a Rust-only solution.
|
||||||
|
|
||||||
|
This allocator is not the most performant by a longshot. It is primarily, I
|
||||||
|
think, intended for being easy to port and easy to learn. I didn't dive too deep
|
||||||
|
into the implementation when writing it, it's just a straight port of the C
|
||||||
|
version.
|
||||||
|
|
||||||
|
It's unlikely that Rust code needs to worry/interact with this allocator in
|
||||||
|
general. Most of the time you'll be manually switching to a different allocator
|
||||||
|
:)
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
This project is licensed under either of
|
||||||
|
|
||||||
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in this project by you, as defined in the Apache-2.0 license,
|
||||||
|
shall be dual licensed as above, without any additional terms or conditions.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
corpus
|
||||||
|
artifacts
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "dlmalloc-fuzz"
|
||||||
|
version = "0.0.1"
|
||||||
|
publish = false
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
arbitrary = "1.3.2"
|
||||||
|
dlmalloc = { path = '..' }
|
||||||
|
libfuzzer-sys = "0.4.7"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "alloc"
|
||||||
|
path = "fuzz_targets/alloc.rs"
|
||||||
|
test = false
|
||||||
|
bench = false
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use arbitrary::Unstructured;
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
fuzz_target!(|bytes: &[u8]| {
|
||||||
|
let _ = dlmalloc_fuzz::run(&mut Unstructured::new(bytes));
|
||||||
|
});
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
use arbitrary::{Result, Unstructured};
|
||||||
|
use dlmalloc::Dlmalloc;
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
const MAX_ALLOCATED: usize = 100 << 20; // 100 MB
|
||||||
|
|
||||||
|
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
|
||||||
|
let mut a = Dlmalloc::new();
|
||||||
|
let mut ptrs = Vec::new();
|
||||||
|
let mut allocated = 0;
|
||||||
|
unsafe {
|
||||||
|
while u.arbitrary()? {
|
||||||
|
// If there are pointers to free then have a chance of deallocating
|
||||||
|
// a pointer. Try not to deallocate things until there's a "large"
|
||||||
|
// working set but afterwards give it a 50/50 chance of allocating
|
||||||
|
// or deallocating.
|
||||||
|
let free = match ptrs.len() {
|
||||||
|
0 => false,
|
||||||
|
0..=10_000 => u.ratio(1, 3)?,
|
||||||
|
_ => u.arbitrary()?,
|
||||||
|
};
|
||||||
|
if free {
|
||||||
|
let idx = u.choose_index(ptrs.len())?;
|
||||||
|
let (ptr, size, align) = ptrs.swap_remove(idx);
|
||||||
|
allocated -= size;
|
||||||
|
a.free(ptr, size, align);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1/100 chance of reallocating a pointer to a different size.
|
||||||
|
if ptrs.len() > 0 && u.ratio(1, 100)? {
|
||||||
|
let idx = u.choose_index(ptrs.len())?;
|
||||||
|
let (ptr, size, align) = ptrs.swap_remove(idx);
|
||||||
|
|
||||||
|
// Arbitrarily choose whether to make this allocation either
|
||||||
|
// twice as large or half as small.
|
||||||
|
let new_size = if u.arbitrary()? {
|
||||||
|
u.int_in_range(size..=size * 2)?
|
||||||
|
} else if size > 10 {
|
||||||
|
u.int_in_range(size / 2..=size)?
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if allocated + new_size - size > MAX_ALLOCATED {
|
||||||
|
ptrs.push((ptr, size, align));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allocated -= size;
|
||||||
|
allocated += new_size;
|
||||||
|
|
||||||
|
// Perform the `realloc` and assert that all bytes were copied.
|
||||||
|
let mut tmp = Vec::new();
|
||||||
|
for i in 0..cmp::min(size, new_size) {
|
||||||
|
tmp.push(*ptr.offset(i as isize));
|
||||||
|
}
|
||||||
|
let ptr = a.realloc(ptr, size, align, new_size);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
for (i, byte) in tmp.iter().enumerate() {
|
||||||
|
assert_eq!(*byte, *ptr.offset(i as isize));
|
||||||
|
}
|
||||||
|
ptrs.push((ptr, new_size, align));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aribtrarily choose a size to allocate as well as an alignment.
|
||||||
|
// Enable small sizes with standard alignment happening a fair bit.
|
||||||
|
let size = if u.arbitrary()? {
|
||||||
|
u.int_in_range(1..=128)?
|
||||||
|
} else {
|
||||||
|
u.int_in_range(1..=128 * 1024)?
|
||||||
|
};
|
||||||
|
let align = if u.ratio(1, 10)? {
|
||||||
|
1 << u.int_in_range(3..=8)?
|
||||||
|
} else {
|
||||||
|
8
|
||||||
|
};
|
||||||
|
|
||||||
|
if size + allocated > MAX_ALLOCATED {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
allocated += size;
|
||||||
|
|
||||||
|
// Choose arbitrarily between a zero-allocated chunk and a normal
|
||||||
|
// allocated chunk.
|
||||||
|
let zero = u.ratio(1, 50)?;
|
||||||
|
let ptr = if zero {
|
||||||
|
a.calloc(size, align)
|
||||||
|
} else {
|
||||||
|
a.malloc(size, align)
|
||||||
|
};
|
||||||
|
for i in 0..size {
|
||||||
|
if zero {
|
||||||
|
assert_eq!(*ptr.offset(i as isize), 0);
|
||||||
|
}
|
||||||
|
*ptr.offset(i as isize) = 0xce;
|
||||||
|
}
|
||||||
|
ptrs.push((ptr, size, align));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate everythign when we're done.
|
||||||
|
for (ptr, size, align) in ptrs {
|
||||||
|
a.free(ptr, size, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::Allocator;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
pub struct System {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
pub const fn new() -> System {
|
||||||
|
System { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Allocator for System {
|
||||||
|
fn alloc(&self, _size: usize) -> (*mut u8, usize, u32) {
|
||||||
|
(ptr::null_mut(), 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_release_part(&self, _flags: u32) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_zeros(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use crate::Dlmalloc;
|
||||||
|
use core::alloc::{GlobalAlloc, Layout};
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
pub use crate::sys::enable_alloc_after_fork;
|
||||||
|
|
||||||
|
/// An instance of a "global allocator" backed by `Dlmalloc`
|
||||||
|
///
|
||||||
|
/// This API requires the `global` feature is activated, and this type
|
||||||
|
/// implements the `GlobalAlloc` trait in the standard library.
|
||||||
|
pub struct GlobalDlmalloc;
|
||||||
|
|
||||||
|
static mut DLMALLOC: Dlmalloc = Dlmalloc::new();
|
||||||
|
|
||||||
|
unsafe impl GlobalAlloc for GlobalDlmalloc {
|
||||||
|
#[inline]
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let _guard = lock();
|
||||||
|
let dlmalloc = ptr::addr_of_mut!(DLMALLOC);
|
||||||
|
(*dlmalloc).malloc(layout.size(), layout.align())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
|
let _guard = lock();
|
||||||
|
let dlmalloc = ptr::addr_of_mut!(DLMALLOC);
|
||||||
|
(*dlmalloc).free(ptr, layout.size(), layout.align())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let _guard = lock();
|
||||||
|
let dlmalloc = ptr::addr_of_mut!(DLMALLOC);
|
||||||
|
(*dlmalloc).calloc(layout.size(), layout.align())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
||||||
|
let _guard = lock();
|
||||||
|
let dlmalloc = ptr::addr_of_mut!(DLMALLOC);
|
||||||
|
(*dlmalloc).realloc(ptr, layout.size(), layout.align(), new_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn lock() -> impl Drop {
|
||||||
|
crate::sys::acquire_global_lock();
|
||||||
|
|
||||||
|
struct Guard;
|
||||||
|
impl Drop for Guard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
crate::sys::release_global_lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Guard
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
//! A Rust port of the `dlmalloc` allocator.
|
||||||
|
//!
|
||||||
|
//! The `dlmalloc` allocator is described at
|
||||||
|
//! <https://gee.cs.oswego.edu/dl/html/malloc.html> and this Rust crate is a straight
|
||||||
|
//! port of the C code for the allocator into Rust. The implementation is
|
||||||
|
//! wrapped up in a `Dlmalloc` type and has support for Linux, OSX, and Wasm
|
||||||
|
//! currently.
|
||||||
|
//!
|
||||||
|
//! The primary purpose of this crate is that it serves as the default memory
|
||||||
|
//! allocator for the `wasm32-unknown-unknown` target in the standard library.
|
||||||
|
//! Support for other platforms is largely untested and unused, but is used when
|
||||||
|
//! testing this crate.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![no_std]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
#[cfg(feature = "rust_api")]
|
||||||
|
use core::{cmp, ptr};
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
|
use sys::System;
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub use self::global::{enable_alloc_after_fork, GlobalDlmalloc};
|
||||||
|
|
||||||
|
mod dlmalloc;
|
||||||
|
|
||||||
|
#[cfg(feature = "c_api")]
|
||||||
|
pub use dlmalloc::Dlmalloc as DlmallocCApi;
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
mod global;
|
||||||
|
|
||||||
|
/// In order for this crate to efficiently manage memory, it needs a way to communicate with the
|
||||||
|
/// underlying platform. This `Allocator` trait provides an interface for this communication.
|
||||||
|
pub unsafe trait Allocator: Send {
|
||||||
|
/// Allocates system memory region of at least `size` bytes
|
||||||
|
/// Returns a triple of `(base, size, flags)` where `base` is a pointer to the beginning of the
|
||||||
|
/// allocated memory region. `size` is the actual size of the region while `flags` specifies
|
||||||
|
/// properties of the allocated region. If `EXTERN_BIT` (bit 0) set in flags, then we did not
|
||||||
|
/// allocate this segment and so should not try to deallocate or merge with others.
|
||||||
|
/// This function can return a `std::ptr::null_mut()` when allocation fails (other values of
|
||||||
|
/// the triple will be ignored).
|
||||||
|
fn alloc(&self, size: usize) -> (*mut u8, usize, u32);
|
||||||
|
|
||||||
|
/// Remaps system memory region at `ptr` with size `oldsize` to a potential new location with
|
||||||
|
/// size `newsize`. `can_move` indicates if the location is allowed to move to a completely new
|
||||||
|
/// location, or that it is only allowed to change in size. Returns a pointer to the new
|
||||||
|
/// location in memory.
|
||||||
|
/// This function can return a `std::ptr::null_mut()` to signal an error.
|
||||||
|
fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8;
|
||||||
|
|
||||||
|
/// Frees a part of a memory chunk. The original memory chunk starts at `ptr` with size `oldsize`
|
||||||
|
/// and is turned into a memory region starting at the same address but with `newsize` bytes.
|
||||||
|
/// Returns `true` iff the access memory region could be freed.
|
||||||
|
fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool;
|
||||||
|
|
||||||
|
/// Frees an entire memory region. Returns `true` iff the operation succeeded. When `false` is
|
||||||
|
/// returned, the `dlmalloc` may re-use the location on future allocation requests
|
||||||
|
fn free(&self, ptr: *mut u8, size: usize) -> bool;
|
||||||
|
|
||||||
|
/// Indicates if the system can release a part of memory. For the `flags` argument, see
|
||||||
|
/// `Allocator::alloc`
|
||||||
|
fn can_release_part(&self, flags: u32) -> bool;
|
||||||
|
|
||||||
|
/// Indicates whether newly allocated regions contain zeros.
|
||||||
|
fn allocates_zeros(&self) -> bool;
|
||||||
|
|
||||||
|
/// Returns the page size. Must be a power of two
|
||||||
|
fn page_size(&self) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An allocator instance
|
||||||
|
///
|
||||||
|
/// Instances of this type are used to allocate blocks of memory. For best
|
||||||
|
/// results only use one of these. Currently doesn't implement `Drop` to release
|
||||||
|
/// lingering memory back to the OS. That may happen eventually though!
|
||||||
|
#[cfg(feature = "rust_api")]
|
||||||
|
pub struct Dlmalloc<
|
||||||
|
#[cfg(feature = "system")]
|
||||||
|
A = System,
|
||||||
|
#[cfg(not(feature = "system"))]
|
||||||
|
A,
|
||||||
|
>(dlmalloc::Dlmalloc<A>);
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(all(feature = "system", target_family = "wasm"))] {
|
||||||
|
#[path = "wasm.rs"]
|
||||||
|
mod sys;
|
||||||
|
} else if #[cfg(all(feature = "system", target_os = "windows"))] {
|
||||||
|
#[path = "windows.rs"]
|
||||||
|
mod sys;
|
||||||
|
} else if #[cfg(all(feature = "system", target_os = "xous"))] {
|
||||||
|
#[path = "xous.rs"]
|
||||||
|
mod sys;
|
||||||
|
} else if #[cfg(all(feature = "system", any(target_os = "linux", target_os = "macos", target_os = "redox")))] {
|
||||||
|
#[path = "unix.rs"]
|
||||||
|
mod sys;
|
||||||
|
} else {
|
||||||
|
#[path = "dummy.rs"]
|
||||||
|
mod sys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "system")]
|
||||||
|
#[cfg(feature = "rust_api")]
|
||||||
|
impl Dlmalloc<System> {
|
||||||
|
/// Creates a new instance of an allocator
|
||||||
|
pub const fn new() -> Dlmalloc<System> {
|
||||||
|
Dlmalloc(dlmalloc::Dlmalloc::new(System::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rust_api")]
|
||||||
|
impl<A> Dlmalloc<A> {
|
||||||
|
/// Creates a new instance of an allocator
|
||||||
|
pub const fn new_with_allocator(sys_allocator: A) -> Dlmalloc<A> {
|
||||||
|
Dlmalloc(dlmalloc::Dlmalloc::new(sys_allocator))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rust_api")]
|
||||||
|
impl<A: Allocator> Dlmalloc<A> {
|
||||||
|
/// Allocates `size` bytes with `align` align.
|
||||||
|
///
|
||||||
|
/// Returns a null pointer if allocation fails. Returns a valid pointer
|
||||||
|
/// otherwise.
|
||||||
|
///
|
||||||
|
/// Safety and contracts are largely governed by the `GlobalAlloc::alloc`
|
||||||
|
/// method contracts.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn malloc(&mut self, size: usize, align: usize) -> *mut u8 {
|
||||||
|
if align <= self.0.malloc_alignment() {
|
||||||
|
self.0.malloc(size)
|
||||||
|
} else {
|
||||||
|
self.0.memalign(align, size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `malloc`, except if the allocation succeeds it's guaranteed to
|
||||||
|
/// point to `size` bytes of zeros.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn calloc(&mut self, size: usize, align: usize) -> *mut u8 {
|
||||||
|
let ptr = self.malloc(size, align);
|
||||||
|
if !ptr.is_null() && self.0.calloc_must_clear(ptr) {
|
||||||
|
ptr::write_bytes(ptr, 0, size);
|
||||||
|
}
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deallocates a `ptr` with `size` and `align` as the previous request used
|
||||||
|
/// to allocate it.
|
||||||
|
///
|
||||||
|
/// Safety and contracts are largely governed by the `GlobalAlloc::dealloc`
|
||||||
|
/// method contracts.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn free(&mut self, ptr: *mut u8, size: usize, align: usize) {
|
||||||
|
let _ = align;
|
||||||
|
self.0.validate_size(ptr, size);
|
||||||
|
self.0.free(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reallocates `ptr`, a previous allocation with `old_size` and
|
||||||
|
/// `old_align`, to have `new_size` and the same alignment as before.
|
||||||
|
///
|
||||||
|
/// Returns a null pointer if the memory couldn't be reallocated, but `ptr`
|
||||||
|
/// is still valid. Returns a valid pointer and frees `ptr` if the request
|
||||||
|
/// is satisfied.
|
||||||
|
///
|
||||||
|
/// Safety and contracts are largely governed by the `GlobalAlloc::realloc`
|
||||||
|
/// method contracts.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn realloc(
|
||||||
|
&mut self,
|
||||||
|
ptr: *mut u8,
|
||||||
|
old_size: usize,
|
||||||
|
old_align: usize,
|
||||||
|
new_size: usize,
|
||||||
|
) -> *mut u8 {
|
||||||
|
self.0.validate_size(ptr, old_size);
|
||||||
|
|
||||||
|
if old_align <= self.0.malloc_alignment() {
|
||||||
|
self.0.realloc(ptr, new_size)
|
||||||
|
} else {
|
||||||
|
let res = self.malloc(new_size, old_align);
|
||||||
|
if !res.is_null() {
|
||||||
|
let size = cmp::min(old_size, new_size);
|
||||||
|
ptr::copy_nonoverlapping(ptr, res, size);
|
||||||
|
self.free(ptr, old_size, old_align);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If possible, gives memory back to the system if there is unused memory
|
||||||
|
/// at the high end of the malloc pool or in unused segments.
|
||||||
|
///
|
||||||
|
/// You can call this after freeing large blocks of memory to potentially
|
||||||
|
/// reduce the system-level memory requirements of a program. However, it
|
||||||
|
/// cannot guarantee to reduce memory. Under some allocation patterns, some
|
||||||
|
/// large free blocks of memory will be locked between two used chunks, so
|
||||||
|
/// they cannot be given back to the system.
|
||||||
|
///
|
||||||
|
/// The `pad` argument represents the amount of free trailing space to
|
||||||
|
/// leave untrimmed. If this argument is zero, only the minimum amount of
|
||||||
|
/// memory to maintain internal data structures will be left. Non-zero
|
||||||
|
/// arguments can be supplied to maintain enough trailing space to service
|
||||||
|
/// future expected allocations without having to re-obtain memory from the
|
||||||
|
/// system.
|
||||||
|
///
|
||||||
|
/// Returns `true` if it actually released any memory, else `false`.
|
||||||
|
pub unsafe fn trim(&mut self, pad: usize) -> bool {
|
||||||
|
self.0.trim(pad)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases all allocations in this allocator back to the system,
|
||||||
|
/// consuming self and preventing further use.
|
||||||
|
///
|
||||||
|
/// Returns the number of bytes released to the system.
|
||||||
|
pub unsafe fn destroy(self) -> usize {
|
||||||
|
self.0.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference the underlying [`Allocator`] that this `Dlmalloc` was
|
||||||
|
/// constructed with.
|
||||||
|
pub fn allocator(&self) -> &A {
|
||||||
|
self.0.allocator()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
use crate::Allocator;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
/// System setting for Linux
|
||||||
|
pub struct System {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
pub const fn new() -> System {
|
||||||
|
System { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
static mut LOCK: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
unsafe impl Allocator for System {
|
||||||
|
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
|
||||||
|
let addr = unsafe {
|
||||||
|
libc::mmap(
|
||||||
|
ptr::null_mut(),
|
||||||
|
size,
|
||||||
|
libc::PROT_WRITE | libc::PROT_READ,
|
||||||
|
libc::MAP_ANON | libc::MAP_PRIVATE,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if addr == libc::MAP_FAILED {
|
||||||
|
(ptr::null_mut(), 0, 0)
|
||||||
|
} else {
|
||||||
|
(addr.cast(), size, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn remap(&self, ptr: *mut u8, oldsize: usize, newsize: usize, can_move: bool) -> *mut u8 {
|
||||||
|
let flags = if can_move { libc::MREMAP_MAYMOVE } else { 0 };
|
||||||
|
let ptr = unsafe { libc::mremap(ptr.cast(), oldsize, newsize, flags) };
|
||||||
|
if ptr == libc::MAP_FAILED {
|
||||||
|
ptr::null_mut()
|
||||||
|
} else {
|
||||||
|
ptr.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "redox", target_os = "macos"))]
|
||||||
|
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool {
|
||||||
|
unsafe {
|
||||||
|
let rc = libc::mremap(ptr.cast(), oldsize, newsize, 0);
|
||||||
|
if rc != libc::MAP_FAILED {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "redox", target_os = "macos"))]
|
||||||
|
fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool {
|
||||||
|
unsafe { libc::munmap(ptr.add(newsize).cast(), oldsize - newsize) == 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&self, ptr: *mut u8, size: usize) -> bool {
|
||||||
|
unsafe { libc::munmap(ptr.cast(), size) == 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_release_part(&self, _flags: u32) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_zeros(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
4096
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn acquire_global_lock() {
|
||||||
|
unsafe { assert_eq!(libc::pthread_mutex_lock(ptr::addr_of_mut!(LOCK)), 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn release_global_lock() {
|
||||||
|
unsafe { assert_eq!(libc::pthread_mutex_unlock(ptr::addr_of_mut!(LOCK)), 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
/// allows the allocator to remain unsable in the child process,
|
||||||
|
/// after a call to `fork(2)`
|
||||||
|
///
|
||||||
|
/// #Safety
|
||||||
|
///
|
||||||
|
/// if used, this function must be called,
|
||||||
|
/// before any allocations are made with the global allocator.
|
||||||
|
pub unsafe fn enable_alloc_after_fork() {
|
||||||
|
// atfork must only be called once, to avoid a deadlock,
|
||||||
|
// where the handler attempts to acquire the global lock twice
|
||||||
|
static mut FORK_PROTECTED: bool = false;
|
||||||
|
|
||||||
|
unsafe extern "C" fn _acquire_global_lock() {
|
||||||
|
acquire_global_lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn _release_global_lock() {
|
||||||
|
release_global_lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
acquire_global_lock();
|
||||||
|
// if a process forks,
|
||||||
|
// it will acquire the lock before any other thread,
|
||||||
|
// protecting it from deadlock,
|
||||||
|
// due to the child being created with only the calling thread.
|
||||||
|
if !FORK_PROTECTED {
|
||||||
|
libc::pthread_atfork(
|
||||||
|
Some(_acquire_global_lock),
|
||||||
|
Some(_release_global_lock),
|
||||||
|
Some(_release_global_lock),
|
||||||
|
);
|
||||||
|
FORK_PROTECTED = true;
|
||||||
|
}
|
||||||
|
release_global_lock();
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::Allocator;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
use core::arch::wasm32 as wasm;
|
||||||
|
#[cfg(target_arch = "wasm64")]
|
||||||
|
use core::arch::wasm64 as wasm;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
/// System setting for Wasm
|
||||||
|
pub struct System {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
pub const fn new() -> System {
|
||||||
|
System { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Allocator for System {
|
||||||
|
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
|
||||||
|
let pages = size / self.page_size();
|
||||||
|
let prev = wasm::memory_grow(0, pages);
|
||||||
|
if prev == usize::max_value() {
|
||||||
|
return (ptr::null_mut(), 0, 0);
|
||||||
|
}
|
||||||
|
(
|
||||||
|
(prev * self.page_size()) as *mut u8,
|
||||||
|
pages * self.page_size(),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
|
||||||
|
// TODO: I think this can be implemented near the end?
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_release_part(&self, _flags: u32) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_zeros(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
64 * 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn acquire_global_lock() {
|
||||||
|
// single threaded, no need!
|
||||||
|
assert!(!cfg!(target_feature = "atomics"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn release_global_lock() {
|
||||||
|
// single threaded, no need!
|
||||||
|
assert!(!cfg!(target_feature = "atomics"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub unsafe fn enable_alloc_after_fork() {
|
||||||
|
// single threaded, no need!
|
||||||
|
assert!(!cfg!(target_feature = "atomics"));
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
use crate::Allocator;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use core::ptr;
|
||||||
|
use windows_sys::Win32::System::Memory::*;
|
||||||
|
use windows_sys::Win32::System::SystemInformation::*;
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
use windows_sys::Win32::System::Threading::*;
|
||||||
|
|
||||||
|
pub struct System {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
pub const fn new() -> System {
|
||||||
|
System { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Allocator for System {
|
||||||
|
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
|
||||||
|
let addr = unsafe {
|
||||||
|
VirtualAlloc(
|
||||||
|
ptr::null_mut(),
|
||||||
|
size,
|
||||||
|
MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_READWRITE,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if addr.is_null() {
|
||||||
|
(ptr::null_mut(), 0, 0)
|
||||||
|
} else {
|
||||||
|
(addr.cast(), size, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_part(&self, ptr: *mut u8, oldsize: usize, newsize: usize) -> bool {
|
||||||
|
unsafe { VirtualFree(ptr.add(newsize).cast(), oldsize - newsize, MEM_DECOMMIT) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&self, ptr: *mut u8, _size: usize) -> bool {
|
||||||
|
unsafe { VirtualFree(ptr.cast(), 0, MEM_DECOMMIT) != 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_release_part(&self, _flags: u32) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_zeros(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
unsafe {
|
||||||
|
let mut info = MaybeUninit::uninit();
|
||||||
|
GetSystemInfo(info.as_mut_ptr());
|
||||||
|
info.assume_init_ref().dwPageSize as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: `SRWLOCK_INIT` doesn't appear to be in `windows-sys`
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
static mut LOCK: SRWLOCK = SRWLOCK {
|
||||||
|
Ptr: ptr::null_mut(),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn acquire_global_lock() {
|
||||||
|
unsafe {
|
||||||
|
AcquireSRWLockExclusive(ptr::addr_of_mut!(LOCK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn release_global_lock() {
|
||||||
|
unsafe {
|
||||||
|
ReleaseSRWLockExclusive(ptr::addr_of_mut!(LOCK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Not needed on Windows
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub unsafe fn enable_alloc_after_fork() {}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
use crate::Allocator;
|
||||||
|
use core::ptr;
|
||||||
|
|
||||||
|
pub struct System {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl System {
|
||||||
|
pub const fn new() -> System {
|
||||||
|
System { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "riscv32")]
|
||||||
|
mod sys {
|
||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
pub fn increase_heap(length: usize) -> Result<(usize, usize), ()> {
|
||||||
|
let syscall_no_increase_heap = 10usize;
|
||||||
|
let memory_flags_read_write = 2usize | 4usize;
|
||||||
|
|
||||||
|
let mut a0 = syscall_no_increase_heap;
|
||||||
|
let mut a1 = length;
|
||||||
|
let mut a2 = memory_flags_read_write;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
asm!(
|
||||||
|
"ecall",
|
||||||
|
inlateout("a0") a0,
|
||||||
|
inlateout("a1") a1,
|
||||||
|
inlateout("a2") a2,
|
||||||
|
out("a3") _,
|
||||||
|
out("a4") _,
|
||||||
|
out("a5") _,
|
||||||
|
out("a6") _,
|
||||||
|
out("a7") _,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = a0;
|
||||||
|
let address = a1;
|
||||||
|
let length = a2;
|
||||||
|
|
||||||
|
// 3 is the "MemoryRange" type, and the result is only valid
|
||||||
|
// if we get nonzero address and length.
|
||||||
|
if result == 3 && address != 0 && length != 0 {
|
||||||
|
Ok((address, length))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Allocator for System {
|
||||||
|
/// Allocate an additional `size` bytes on the heap, and return a new
|
||||||
|
/// chunk of memory, as well as the size of the allocation and some
|
||||||
|
/// flags. Since flags are unused on this platform, they will always
|
||||||
|
/// be `0`.
|
||||||
|
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
|
||||||
|
let size = if size == 0 {
|
||||||
|
4096
|
||||||
|
} else if size & 4095 == 0 {
|
||||||
|
size
|
||||||
|
} else {
|
||||||
|
size + (4096 - (size & 4095))
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok((address, length)) = sys::increase_heap(size) {
|
||||||
|
let start = address - size + length;
|
||||||
|
(start as *mut u8, size, 0)
|
||||||
|
} else {
|
||||||
|
(ptr::null_mut(), 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
|
||||||
|
// TODO
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_release_part(&self, _flags: u32) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocates_zeros(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_size(&self) -> usize {
|
||||||
|
4 * 1024
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn acquire_global_lock() {
|
||||||
|
// global feature should not be enabled
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub fn release_global_lock() {
|
||||||
|
// global feature should not be enabled
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
pub unsafe fn enable_alloc_after_fork() {
|
||||||
|
// platform does not support `fork()` call
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
extern crate dlmalloc;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
#[cfg(feature = "global")]
|
||||||
|
static A: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn foo() {
|
||||||
|
println!("hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map() {
|
||||||
|
let mut m = HashMap::new();
|
||||||
|
m.insert(1, 2);
|
||||||
|
m.insert(5, 3);
|
||||||
|
drop(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strings() {
|
||||||
|
format!("foo, bar, {}", "baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
fn threads() {
|
||||||
|
assert!(thread::spawn(|| panic!()).join().is_err());
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
use arbitrary::Unstructured;
|
||||||
|
use dlmalloc::Dlmalloc;
|
||||||
|
use rand::{rngs::SmallRng, RngCore, SeedableRng};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn smoke() {
|
||||||
|
let mut a = Dlmalloc::new();
|
||||||
|
unsafe {
|
||||||
|
let ptr = a.malloc(1, 1);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
*ptr = 9;
|
||||||
|
assert_eq!(*ptr, 9);
|
||||||
|
a.free(ptr, 1, 1);
|
||||||
|
|
||||||
|
let ptr = a.malloc(1, 1);
|
||||||
|
assert!(!ptr.is_null());
|
||||||
|
*ptr = 10;
|
||||||
|
assert_eq!(*ptr, 10);
|
||||||
|
a.free(ptr, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[path = "../fuzz/src/lib.rs"]
|
||||||
|
mod fuzz;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stress() {
|
||||||
|
let mut rng = SmallRng::seed_from_u64(0);
|
||||||
|
let mut buf = vec![0; 4096];
|
||||||
|
let iters = if cfg!(miri) { 5 } else { 2000 };
|
||||||
|
for _ in 0..iters {
|
||||||
|
rng.fill_bytes(&mut buf);
|
||||||
|
let mut u = Unstructured::new(&buf);
|
||||||
|
let _ = fuzz::run(&mut u);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 |
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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(_)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.workspace = true
|
|
||||||
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
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -1,557 +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 ty = u16::from_ne_bytes(type_bytes);
|
|
||||||
let len = u16::from_ne_bytes(len_bytes);
|
|
||||||
|
|
||||||
let len = usize::try_from(len).expect("expected u16 to fit within usize");
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,455 +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>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AmlMutexState {
|
|
||||||
next_id: u32,
|
|
||||||
held: FxHashSet<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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>>) -> 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: FxHashSet::default(),
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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> {
|
|
||||||
let deadline = std::time::Instant::now()
|
|
||||||
+ std::time::Duration::from_millis(u64::from(timeout).saturating_mul(1000));
|
|
||||||
loop {
|
|
||||||
{
|
|
||||||
let mut state = self.mutex_state.lock().unwrap();
|
|
||||||
if !state.held.contains(&mutex.0) {
|
|
||||||
state.held.insert(mutex.0);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if std::time::Instant::now() >= deadline {
|
|
||||||
return Err(AmlError::MutexAcquireTimeout);
|
|
||||||
}
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn release(&self, mutex: Handle) {
|
|
||||||
self.mutex_state.lock().unwrap().held.remove(&mutex.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,256 +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");
|
|
||||||
}
|
|
||||||
//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> {
|
|
||||||
warn!("Got u16 EC read from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
|
|
||||||
warn!("Got u32 EC read from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
|
|
||||||
warn!("Got u64 EC read from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
fn write_u16(
|
|
||||||
&self,
|
|
||||||
_region: &OpRegion,
|
|
||||||
_offset: usize,
|
|
||||||
_value: u16,
|
|
||||||
) -> Result<(), acpi::aml::AmlError> {
|
|
||||||
warn!("Got u16 EC write from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
fn write_u32(
|
|
||||||
&self,
|
|
||||||
_region: &OpRegion,
|
|
||||||
_offset: usize,
|
|
||||||
_value: u32,
|
|
||||||
) -> Result<(), acpi::aml::AmlError> {
|
|
||||||
warn!("Got u32 EC write from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
fn write_u64(
|
|
||||||
&self,
|
|
||||||
_region: &OpRegion,
|
|
||||||
_offset: usize,
|
|
||||||
_value: u64,
|
|
||||||
) -> Result<(), acpi::aml::AmlError> {
|
|
||||||
warn!("Got u64 EC write from AML!");
|
|
||||||
Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
|
|
||||||
RegionSpace::EmbeddedControl,
|
|
||||||
)) // TODO proper error type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,200 +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 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);
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
register_sync_scheme(&socket, "acpi", &mut scheme)
|
|
||||||
.expect("acpid: failed to register acpi scheme to namespace");
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,778 +0,0 @@
|
|||||||
use acpi::aml::namespace::AmlName;
|
|
||||||
use amlserde::aml_serde_name::to_aml_format;
|
|
||||||
use amlserde::AmlSerdeValue;
|
|
||||||
use core::str;
|
|
||||||
use libredox::Fd;
|
|
||||||
use parking_lot::RwLockReadGuard;
|
|
||||||
use redox_scheme::scheme::SchemeSync;
|
|
||||||
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
|
|
||||||
use syscall::flag::CallFlags;
|
|
||||||
use syscall::flag::AcpiVerb;
|
|
||||||
use ron::de::SpannedError;
|
|
||||||
use scheme_utils::HandleMap;
|
|
||||||
use std::convert::{TryFrom, TryInto};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
|
|
||||||
use syscall::schemev2::NewFdFlags;
|
|
||||||
use syscall::FobtainFdFlags;
|
|
||||||
|
|
||||||
use syscall::data::Stat;
|
|
||||||
use syscall::error::{Error, Result};
|
|
||||||
use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
|
|
||||||
use syscall::flag::{MODE_DIR, MODE_FILE};
|
|
||||||
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
|
|
||||||
use syscall::{EOVERFLOW, EPERM};
|
|
||||||
|
|
||||||
use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
|
|
||||||
use crate::dmi::DMI_FIELDS;
|
|
||||||
|
|
||||||
pub struct AcpiScheme<'acpi, 'sock> {
|
|
||||||
ctx: &'acpi AcpiContext,
|
|
||||||
handles: HandleMap<Handle<'acpi>>,
|
|
||||||
pci_fd: Option<Fd>,
|
|
||||||
socket: &'sock Socket,
|
|
||||||
/// Phase I.5: the kstop handle fd. Stored so the main loop
|
|
||||||
/// can call `kstop_reason` (kcall 2) to query the kernel
|
|
||||||
/// for the reason of the most recent kstop event.
|
|
||||||
kstop_fd: Option<Fd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Handle<'a> {
|
|
||||||
kind: HandleKind<'a>,
|
|
||||||
stat: bool,
|
|
||||||
allowed_to_eval: bool,
|
|
||||||
}
|
|
||||||
enum HandleKind<'a> {
|
|
||||||
TopLevel,
|
|
||||||
Tables,
|
|
||||||
Table(SdtSignature),
|
|
||||||
Symbols(RwLockReadGuard<'a, AmlSymbols>),
|
|
||||||
Symbol { name: String, description: String },
|
|
||||||
SchemeRoot,
|
|
||||||
RegisterPci,
|
|
||||||
/// `/scheme/acpi/thermal` -- entries are children of `\_TZ` from
|
|
||||||
/// the AML namespace (e.g. `\_TZ.TZ0`). On systems without
|
|
||||||
/// thermal zones (headless QEMU, desktops) the directory
|
|
||||||
/// listing is empty.
|
|
||||||
Thermal,
|
|
||||||
/// `/scheme/acpi/power` -- entries are PowerResource objects in
|
|
||||||
/// the AML namespace. On laptops these are AC adapters and
|
|
||||||
/// battery controllers. On desktops and QEMU the listing is
|
|
||||||
/// empty.
|
|
||||||
Power,
|
|
||||||
/// `/scheme/acpi/dmi` -- key=value text dump of the SMBIOS identity
|
|
||||||
/// fields (consumed by `redox-driver-sys` quirks loader).
|
|
||||||
Dmi,
|
|
||||||
/// `/scheme/acpi/dmi/<field>` -- a single SMBIOS field as a text
|
|
||||||
/// file (consumed by `i2c-hidd` for probe-failure quirks).
|
|
||||||
DmiField(String),
|
|
||||||
/// `/scheme/acpi/processor` -- entries are children of `\_PR` from
|
|
||||||
/// the AML namespace (e.g. `CPU0`, `CPU1`). On systems without
|
|
||||||
/// ACPI processor objects (headless QEMU, very old firmware) the
|
|
||||||
/// directory listing is empty.
|
|
||||||
Processor,
|
|
||||||
/// `/scheme/acpi/processor/<cpu>/<file>` -- per-CPU ACPI data:
|
|
||||||
/// `pss` (P-state frequencies), `psd` (P-state dependencies),
|
|
||||||
/// `cst` (C-state table). On QEMU these are typically empty.
|
|
||||||
/// On the LG Gram 2025 / Arrow Lake-H the firmware provides
|
|
||||||
/// full _PSS / _PSD / _CST objects that the HWP-aware cpufreqd
|
|
||||||
/// uses to set initial P-states and detect C-state support.
|
|
||||||
ProcFile { cpu: u32, kind: ProcFileKind },
|
|
||||||
DmiDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ProcFileKind {
|
|
||||||
Pss,
|
|
||||||
Psd,
|
|
||||||
Cst,
|
|
||||||
Cpc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HandleKind<'_> {
|
|
||||||
fn is_dir(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::TopLevel => true,
|
|
||||||
Self::Tables => true,
|
|
||||||
Self::Table(_) => false,
|
|
||||||
Self::Symbols(_) => true,
|
|
||||||
Self::Symbol { .. } => false,
|
|
||||||
Self::SchemeRoot => false,
|
|
||||||
Self::RegisterPci => false,
|
|
||||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => true,
|
|
||||||
Self::Dmi => true,
|
|
||||||
Self::DmiField(_) => false,
|
|
||||||
Self::ProcFile { .. } => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
|
|
||||||
Ok(match self {
|
|
||||||
// Files
|
|
||||||
Self::Table(signature) => acpi_ctx
|
|
||||||
.sdt_from_signature(signature)
|
|
||||||
.ok_or(Error::new(EBADFD))?
|
|
||||||
.length(),
|
|
||||||
Self::Symbol { description, .. } => description.len(),
|
|
||||||
// /scheme/acpi/dmi is a key=value text file (redox-driver-sys
|
|
||||||
// reads it via fs::read_to_string). The size depends on how
|
|
||||||
// many fields are populated.
|
|
||||||
Self::Dmi => acpi_ctx
|
|
||||||
.dmi_info()
|
|
||||||
.map(|info| info.to_match_lines().len())
|
|
||||||
.unwrap_or(0),
|
|
||||||
Self::DmiField(field) => dmi_field_contents(acpi_ctx.dmi_info(), field)
|
|
||||||
.map(|s| s.len())
|
|
||||||
.unwrap_or(0),
|
|
||||||
// Directories
|
|
||||||
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
|
|
||||||
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => 0,
|
|
||||||
// ProcFile contents (e.g. PSS table) are bounded by the
|
|
||||||
// platform's ACPI table sizes; the maximum reasonable size
|
|
||||||
// is one page (4096 bytes). Report the file as a fixed
|
|
||||||
// size so the kernel-side read can mmap it.
|
|
||||||
Self::ProcFile { .. } => 4096,
|
|
||||||
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
|
|
||||||
pub fn new(ctx: &'acpi AcpiContext, socket: &'sock Socket) -> Self {
|
|
||||||
Self {
|
|
||||||
ctx,
|
|
||||||
handles: HandleMap::new(),
|
|
||||||
pci_fd: None,
|
|
||||||
socket,
|
|
||||||
kstop_fd: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase I.5: register the kstop handle fd. Called by the
|
|
||||||
/// main loop right after opening the kstop handle.
|
|
||||||
pub fn set_kstop_fd(&mut self, fd: Fd) {
|
|
||||||
self.kstop_fd = Some(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase I.5: query the kernel for the kstop reason via
|
|
||||||
/// the CheckShutdown AcpiVerb (kcall 2). Returns the u8
|
|
||||||
/// reason: 0=idle, 1=shutdown (S5), 2=s2idle wake,
|
|
||||||
/// 3=s3 wake. The kernel re-arms the kstop handle's
|
|
||||||
/// EVENT_READ after each event; acpid's main loop calls
|
|
||||||
/// this once per event to decide what AML sequence to run.
|
|
||||||
///
|
|
||||||
/// Mirrors Linux 7.1 `acpi_s2idle_wake` returning the
|
|
||||||
/// wake reason in `drivers/acpi/sleep.c:758`. The
|
|
||||||
/// `kcall 2` is the `AcpiVerb::CheckShutdown` enum
|
|
||||||
/// variant in the syscall crate.
|
|
||||||
///
|
|
||||||
/// Hardware-agnostic: the reason codes are platform-
|
|
||||||
/// independent; only the wake source (SCI, GPIO, RTC,
|
|
||||||
/// ...) varies per OEM.
|
|
||||||
pub fn kstop_reason(&mut self) -> syscall::Result<u64> {
|
|
||||||
let handle = self.kstop_fd.as_ref().ok_or(syscall::error::Error::new(syscall::error::EBADF))?;
|
|
||||||
let mut payload = [0u8; 8];
|
|
||||||
let verb = AcpiVerb::CheckShutdown as u64;
|
|
||||||
let result = handle.call_ro(&mut payload, CallFlags::empty(), &[verb])?;
|
|
||||||
Ok(u64::from_ne_bytes(payload))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase J: ask the kernel to enter s2idle (Modern
|
|
||||||
/// Standby / S0ix). This is the typed-AcpiVerb equivalent
|
|
||||||
/// of writing "s2idle" to /scheme/sys/kstop — the kstop
|
|
||||||
/// string-arg path was Phase I.5's fallback while we
|
|
||||||
/// couldn't extend the syscall crate due to the libredox
|
|
||||||
/// cross-version issue. Phase J: with the local libredox
|
|
||||||
/// fork (which uses the local syscall fork with
|
|
||||||
/// EnterS2Idle/ExitS2Idle), this typed path is the
|
|
||||||
/// preferred API. The kstop string-arg path remains for
|
|
||||||
/// backward compatibility with older acpid builds.
|
|
||||||
///
|
|
||||||
/// Hardware-agnostic: works for any platform with Modern
|
|
||||||
/// Standby firmware (Dell, HP, Lenovo, LG Gram, etc.).
|
|
||||||
/// Mirrors Linux 7.1 `acpi_s2idle_begin` in
|
|
||||||
/// `kernel/power/suspend.c:91`.
|
|
||||||
pub fn kstop_enter_s2idle(&self) -> syscall::Result<()> {
|
|
||||||
let handle = self.kstop_fd.as_ref().ok_or(syscall::error::Error::new(syscall::error::EBADF))?;
|
|
||||||
let verb = AcpiVerb::EnterS2Idle as u64;
|
|
||||||
// AcpiVerb::EnterS2Idle doesn't need a write payload;
|
|
||||||
// the verb code itself is the signal. The kernel
|
|
||||||
// sets S2IDLE_REQUESTED + signals the kstop handle's
|
|
||||||
// EVENT_READ.
|
|
||||||
handle.call_wo(&[], CallFlags::empty(), &[verb])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Phase II.X.W: write the kernel's S3 resume
|
|
||||||
/// trampoline address to FACS.xfirmware_waking_vector so
|
|
||||||
/// the platform firmware jumps to it on S3 wake.
|
|
||||||
///
|
|
||||||
/// `trampoline_addr` is the address of the kernel's
|
|
||||||
/// `s3_resume::s3_trampoline` function. The kernel
|
|
||||||
/// writes this to FACS via the `SetS3WakingVector`
|
|
||||||
/// AcPiVerb (verb 5).
|
|
||||||
pub fn kstop_enter_s3(&self, trampoline_addr: u64) -> syscall::Result<()> {
|
|
||||||
let handle = self.kstop_fd.as_ref().ok_or(syscall::error::Error::new(syscall::error::EBADF))?;
|
|
||||||
let verb = AcpiVerb::SetS3WakingVector as u64;
|
|
||||||
// Payload: 8-byte little-endian u64 (the trampoline
|
|
||||||
// address). The kernel's `SetS3WakingVector` handler
|
|
||||||
// requires the payload to be exactly 8 bytes.
|
|
||||||
let payload = trampoline_addr.to_ne_bytes();
|
|
||||||
handle.call_wo(&payload, CallFlags::empty(), &[verb])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_hex_digit(hex: u8) -> Option<u8> {
|
|
||||||
let hex = hex.to_ascii_lowercase();
|
|
||||||
|
|
||||||
if hex >= b'a' && hex <= b'f' {
|
|
||||||
Some(hex - b'a' + 10)
|
|
||||||
} else if hex >= b'0' && hex <= b'9' {
|
|
||||||
Some(hex - b'0')
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_hex_2digit(hex: &[u8]) -> Option<u8> {
|
|
||||||
parse_hex_digit(hex[0])
|
|
||||||
.and_then(|most_significant| Some((most_significant << 4) | parse_hex_digit(hex[1])?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_oem_id(hex: [u8; 12]) -> Option<[u8; 6]> {
|
|
||||||
Some([
|
|
||||||
parse_hex_2digit(&hex[0..2])?,
|
|
||||||
parse_hex_2digit(&hex[2..4])?,
|
|
||||||
parse_hex_2digit(&hex[4..6])?,
|
|
||||||
parse_hex_2digit(&hex[6..8])?,
|
|
||||||
parse_hex_2digit(&hex[8..10])?,
|
|
||||||
parse_hex_2digit(&hex[10..12])?,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
fn parse_oem_table_id(hex: [u8; 16]) -> Option<[u8; 8]> {
|
|
||||||
Some([
|
|
||||||
parse_hex_2digit(&hex[0..2])?,
|
|
||||||
parse_hex_2digit(&hex[2..4])?,
|
|
||||||
parse_hex_2digit(&hex[4..6])?,
|
|
||||||
parse_hex_2digit(&hex[6..8])?,
|
|
||||||
parse_hex_2digit(&hex[8..10])?,
|
|
||||||
parse_hex_2digit(&hex[10..12])?,
|
|
||||||
parse_hex_2digit(&hex[12..14])?,
|
|
||||||
parse_hex_2digit(&hex[14..16])?,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Look up the contents of `/scheme/acpi/dmi/<field>` for the given
|
|
||||||
/// field name. Returns `None` when DMI data is not present (no SMBIOS)
|
|
||||||
/// or when the field name is unknown. The returned `String` is what
|
|
||||||
/// userspace will read from the file -- a single text line with no
|
|
||||||
/// trailing newline so that callers can `read_to_string` and `trim`.
|
|
||||||
fn dmi_field_contents(
|
|
||||||
info: Option<&crate::dmi::DmiInfo>,
|
|
||||||
field: &str,
|
|
||||||
) -> Option<String> {
|
|
||||||
crate::dmi::read_field(info, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_table(table: &[u8]) -> Option<SdtSignature> {
|
|
||||||
let signature_part = table.get(..4)?;
|
|
||||||
let first_hyphen = table.get(4)?;
|
|
||||||
let oem_id_part = table.get(5..17)?;
|
|
||||||
let second_hyphen = table.get(17)?;
|
|
||||||
let oem_table_part = table.get(18..34)?;
|
|
||||||
|
|
||||||
if *first_hyphen != b'-' {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if *second_hyphen != b'-' {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if table.len() > 34 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(SdtSignature {
|
|
||||||
signature: <[u8; 4]>::try_from(signature_part)
|
|
||||||
.expect("expected 4-byte slice to be convertible into [u8; 4]"),
|
|
||||||
oem_id: {
|
|
||||||
let hex = <[u8; 12]>::try_from(oem_id_part)
|
|
||||||
.expect("expected 12-byte slice to be convertible into [u8; 12]");
|
|
||||||
parse_oem_id(hex)?
|
|
||||||
},
|
|
||||||
oem_table_id: {
|
|
||||||
let hex = <[u8; 16]>::try_from(oem_table_part)
|
|
||||||
.expect("expected 16-byte slice to be convertible into [u8; 16]");
|
|
||||||
parse_oem_table_id(hex)?
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemeSync for AcpiScheme<'_, '_> {
|
|
||||||
fn scheme_root(&mut self) -> Result<usize> {
|
|
||||||
Ok(self.handles.insert(Handle {
|
|
||||||
stat: false,
|
|
||||||
kind: HandleKind::SchemeRoot,
|
|
||||||
allowed_to_eval: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
fn openat(
|
|
||||||
&mut self,
|
|
||||||
dirfd: usize,
|
|
||||||
path: &str,
|
|
||||||
flags: usize,
|
|
||||||
_fcntl_flags: u32,
|
|
||||||
ctx: &CallerCtx,
|
|
||||||
) -> Result<OpenResult> {
|
|
||||||
let handle = self.handles.get(dirfd)?;
|
|
||||||
|
|
||||||
let path = path.trim_start_matches('/');
|
|
||||||
|
|
||||||
let flag_stat = flags & O_STAT == O_STAT;
|
|
||||||
let flag_dir = flags & O_DIRECTORY == O_DIRECTORY;
|
|
||||||
|
|
||||||
let kind = match handle.kind {
|
|
||||||
HandleKind::SchemeRoot => {
|
|
||||||
// TODO: arrayvec
|
|
||||||
let components = {
|
|
||||||
let mut v = arrayvec::ArrayVec::<&str, 4>::new();
|
|
||||||
let it = path.split('/');
|
|
||||||
for component in it.take(4) {
|
|
||||||
v.push(component);
|
|
||||||
}
|
|
||||||
|
|
||||||
v
|
|
||||||
};
|
|
||||||
|
|
||||||
match &*components {
|
|
||||||
[""] => HandleKind::TopLevel,
|
|
||||||
["register_pci"] => HandleKind::RegisterPci,
|
|
||||||
["tables"] => HandleKind::Tables,
|
|
||||||
["thermal"] => HandleKind::Thermal,
|
|
||||||
["power"] => HandleKind::Power,
|
|
||||||
["dmi"] => HandleKind::Dmi,
|
|
||||||
["processor"] => HandleKind::Processor,
|
|
||||||
|
|
||||||
["tables", table] => {
|
|
||||||
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
|
|
||||||
HandleKind::Table(signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
["symbols"] => {
|
|
||||||
if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
|
|
||||||
HandleKind::Symbols(aml_symbols)
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(EIO));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
["symbols", symbol] => {
|
|
||||||
if let Some(description) = self.ctx.aml_lookup(symbol) {
|
|
||||||
HandleKind::Symbol {
|
|
||||||
name: (*symbol).to_owned(),
|
|
||||||
description,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
["dmi", field] => {
|
|
||||||
// Reject unknown fields explicitly so consumers
|
|
||||||
// see ENOENT rather than reading an empty file.
|
|
||||||
// When SMBIOS is absent, we still serve a
|
|
||||||
// well-defined file with empty contents (so
|
|
||||||
// i2c-hidd's `Err(NotFound)` branch is the only
|
|
||||||
// way to tell the difference between "missing
|
|
||||||
// field" and "no SMBIOS").
|
|
||||||
if DMI_FIELDS.iter().any(|f| *f == *field) {
|
|
||||||
HandleKind::DmiField((*field).to_owned())
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
["processor", cpu_str, file] => {
|
|
||||||
// /scheme/acpi/processor/<cpu>/{pss,psd,cst,cpc}
|
|
||||||
let cpu: u32 = cpu_str
|
|
||||||
.strip_prefix("CPU")
|
|
||||||
.and_then(|rest| rest.parse().ok())
|
|
||||||
.ok_or(Error::new(EINVAL))?;
|
|
||||||
let kind = match *file {
|
|
||||||
"pss" => ProcFileKind::Pss,
|
|
||||||
"psd" => ProcFileKind::Psd,
|
|
||||||
"cst" => ProcFileKind::Cst,
|
|
||||||
"cpc" => ProcFileKind::Cpc,
|
|
||||||
_ => return Err(Error::new(ENOENT)),
|
|
||||||
};
|
|
||||||
HandleKind::ProcFile { cpu, kind }
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => return Err(Error::new(ENOENT)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Symbols(ref aml_symbols) => {
|
|
||||||
if let Some(description) = aml_symbols.lookup(path) {
|
|
||||||
HandleKind::Symbol {
|
|
||||||
name: (*path).to_owned(),
|
|
||||||
description,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(EACCES)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if kind.is_dir() && !flag_dir && !flag_stat {
|
|
||||||
return Err(Error::new(EISDIR));
|
|
||||||
} else if !kind.is_dir() && flag_dir && !flag_stat {
|
|
||||||
return Err(Error::new(ENOTDIR));
|
|
||||||
}
|
|
||||||
|
|
||||||
let allowed_to_eval = if flags & O_ACCMODE == O_RDONLY || flag_stat {
|
|
||||||
false
|
|
||||||
} else if ctx.uid == 0 {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
};
|
|
||||||
|
|
||||||
if flags & O_SYMLINK == O_SYMLINK && !flag_stat {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
let fd = self.handles.insert(Handle {
|
|
||||||
stat: flag_stat,
|
|
||||||
kind,
|
|
||||||
allowed_to_eval,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(OpenResult::ThisScheme {
|
|
||||||
number: fd,
|
|
||||||
flags: NewFdFlags::POSITIONED,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
|
||||||
let handle = self.handles.get(id)?;
|
|
||||||
|
|
||||||
stat.st_size = handle
|
|
||||||
.kind
|
|
||||||
.len(self.ctx)?
|
|
||||||
.try_into()
|
|
||||||
.unwrap_or(u64::max_value());
|
|
||||||
|
|
||||||
if handle.kind.is_dir() {
|
|
||||||
stat.st_mode = MODE_DIR;
|
|
||||||
} else {
|
|
||||||
stat.st_mode = MODE_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
buf: &mut [u8],
|
|
||||||
offset: u64,
|
|
||||||
_fcntl: u32,
|
|
||||||
_ctx: &CallerCtx,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?;
|
|
||||||
|
|
||||||
let handle = self.handles.get_mut(id)?;
|
|
||||||
|
|
||||||
if handle.stat {
|
|
||||||
return Err(Error::new(EBADF));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build an owned buffer for DMI handles so the borrow does not
|
|
||||||
// escape the match arm scope.
|
|
||||||
let dmi_buf;
|
|
||||||
let proc_buf;
|
|
||||||
let src_buf: &[u8] = match &handle.kind {
|
|
||||||
HandleKind::Table(ref signature) => self
|
|
||||||
.ctx
|
|
||||||
.sdt_from_signature(signature)
|
|
||||||
.ok_or(Error::new(EBADFD))?
|
|
||||||
.as_slice(),
|
|
||||||
HandleKind::Symbol { description, .. } => description.as_bytes(),
|
|
||||||
HandleKind::Dmi => {
|
|
||||||
dmi_buf = self
|
|
||||||
.ctx
|
|
||||||
.dmi_info()
|
|
||||||
.map(|info| info.to_match_lines())
|
|
||||||
.unwrap_or_default();
|
|
||||||
dmi_buf.as_bytes()
|
|
||||||
}
|
|
||||||
HandleKind::DmiField(ref field) => {
|
|
||||||
dmi_buf = dmi_field_contents(self.ctx.dmi_info(), field)
|
|
||||||
.unwrap_or_default();
|
|
||||||
dmi_buf.as_bytes()
|
|
||||||
}
|
|
||||||
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
|
||||||
return Err(Error::new(EISDIR));
|
|
||||||
}
|
|
||||||
HandleKind::ProcFile { cpu, kind } => {
|
|
||||||
let method = match kind {
|
|
||||||
ProcFileKind::Pss => "_PSS",
|
|
||||||
ProcFileKind::Psd => "_PSD",
|
|
||||||
ProcFileKind::Cst => "_CST",
|
|
||||||
ProcFileKind::Cpc => "_CPC",
|
|
||||||
};
|
|
||||||
let cpu_segment = format!("CPU{}", cpu);
|
|
||||||
proc_buf = self
|
|
||||||
.ctx
|
|
||||||
.processor_method_text(&cpu_segment, method)
|
|
||||||
.into_bytes();
|
|
||||||
proc_buf.as_slice()
|
|
||||||
}
|
|
||||||
HandleKind::Tables => return Err(Error::new(EISDIR)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let offset = std::cmp::min(src_buf.len(), offset);
|
|
||||||
let src_buf = &src_buf[offset..];
|
|
||||||
|
|
||||||
let to_copy = std::cmp::min(src_buf.len(), buf.len());
|
|
||||||
|
|
||||||
buf[..to_copy].copy_from_slice(&src_buf[..to_copy]);
|
|
||||||
|
|
||||||
Ok(to_copy)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getdents<'buf>(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
mut buf: DirentBuf<&'buf mut [u8]>,
|
|
||||||
opaque_offset: u64,
|
|
||||||
) -> Result<DirentBuf<&'buf mut [u8]>> {
|
|
||||||
let handle = self.handles.get_mut(id)?;
|
|
||||||
|
|
||||||
match &handle.kind {
|
|
||||||
HandleKind::TopLevel => {
|
|
||||||
const TOPLEVEL_ENTRIES: &[&str] = &[
|
|
||||||
"tables", "symbols", "thermal", "power", "dmi", "processor",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (idx, name) in TOPLEVEL_ENTRIES
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.skip(opaque_offset as usize)
|
|
||||||
{
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name,
|
|
||||||
kind: DirentKind::Directory,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Symbols(aml_symbols) => {
|
|
||||||
for (idx, (symbol_name, _value)) in aml_symbols
|
|
||||||
.symbols_cache()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.skip(opaque_offset as usize)
|
|
||||||
{
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: symbol_name.as_str(),
|
|
||||||
kind: DirentKind::Regular,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Tables => {
|
|
||||||
for (idx, table) in self
|
|
||||||
.ctx
|
|
||||||
.tables()
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.skip(opaque_offset as usize)
|
|
||||||
{
|
|
||||||
let utf8_or_eio = |bytes| str::from_utf8(bytes).map_err(|_| Error::new(EIO));
|
|
||||||
|
|
||||||
let mut name = String::new();
|
|
||||||
name.push_str(utf8_or_eio(&table.signature[..])?);
|
|
||||||
name.push('-');
|
|
||||||
for byte in table.oem_id.iter() {
|
|
||||||
std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap();
|
|
||||||
}
|
|
||||||
name.push('-');
|
|
||||||
for byte in table.oem_table_id.iter() {
|
|
||||||
std::fmt::write(&mut name, format_args!("{:>02X}", byte)).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: &name,
|
|
||||||
kind: DirentKind::Regular,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Thermal => {
|
|
||||||
// Enumerate \_TZ.<zone> entries from the AML namespace.
|
|
||||||
// Returns Ok with no entries on systems with no zones
|
|
||||||
// (headless QEMU, desktops) so consumers see an
|
|
||||||
// empty-but-existing directory.
|
|
||||||
let zones = self.ctx.thermal_zones();
|
|
||||||
for (idx, zone) in zones.iter().enumerate().skip(opaque_offset as usize) {
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: zone.as_str(),
|
|
||||||
kind: DirentKind::Directory,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Processor => {
|
|
||||||
// Enumerate \_PR.<cpu> entries from the AML namespace.
|
|
||||||
// Returns Ok with no entries on systems with no
|
|
||||||
// processors (headless QEMU with no DSDT) so consumers
|
|
||||||
// see an empty-but-existing directory. The directory
|
|
||||||
// entry names use the short CPU segment (e.g. "CPU0")
|
|
||||||
// so that `processor/CPU0/pss` is a valid sub-path.
|
|
||||||
let cpus = self.ctx.cpu_names();
|
|
||||||
for (idx, cpu_path) in cpus.iter().enumerate().skip(opaque_offset as usize) {
|
|
||||||
let short = cpu_path.strip_prefix("\\_PR.").unwrap_or(cpu_path);
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: short,
|
|
||||||
kind: DirentKind::Directory,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Power => {
|
|
||||||
// Enumerate PowerResource entries. On real laptops these
|
|
||||||
// are AC adapters and battery controllers; on desktops
|
|
||||||
// and QEMU the list is empty.
|
|
||||||
let adapters = self.ctx.power_adapters();
|
|
||||||
for (idx, adapter) in adapters.iter().enumerate().skip(opaque_offset as usize) {
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: adapter.as_str(),
|
|
||||||
kind: DirentKind::Directory,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::Dmi => {
|
|
||||||
// Consumers should `read_to_string("/scheme/acpi/dmi")`
|
|
||||||
// rather than iterating, but we still surface the field
|
|
||||||
// list so that ls /scheme/acpi/dmi/ produces a useful
|
|
||||||
// diagnostic on a live system. We always list the same
|
|
||||||
// set of fields regardless of whether SMBIOS data is
|
|
||||||
// present -- empty entries just produce empty reads.
|
|
||||||
for (idx, field) in DMI_FIELDS
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.skip(opaque_offset as usize)
|
|
||||||
{
|
|
||||||
buf.entry(DirEntry {
|
|
||||||
inode: 0,
|
|
||||||
next_opaque_id: idx as u64 + 1,
|
|
||||||
name: field,
|
|
||||||
kind: DirentKind::Regular,
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HandleKind::ProcFile { .. } | HandleKind::DmiDir => {
|
|
||||||
// No children; reads/writes go through the
|
|
||||||
// HandleKind match in kread/kwriteoff.
|
|
||||||
}
|
|
||||||
_ => return Err(Error::new(EIO)),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
payload: &mut [u8],
|
|
||||||
_metadata: &[u64],
|
|
||||||
_ctx: &CallerCtx,
|
|
||||||
) -> Result<usize> {
|
|
||||||
let handle = self.handles.get_mut(id)?;
|
|
||||||
if !handle.allowed_to_eval {
|
|
||||||
return Err(Error::new(EPERM));
|
|
||||||
}
|
|
||||||
|
|
||||||
let Ok(args): Result<Vec<AmlSerdeValue>, SpannedError> = ron::de::from_bytes(payload)
|
|
||||||
else {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
};
|
|
||||||
|
|
||||||
let HandleKind::Symbol { name, .. } = &handle.kind else {
|
|
||||||
return Err(Error::new(EBADF));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(aml_name) = AmlName::from_str(&to_aml_format(name)) else {
|
|
||||||
log::error!("Failed to convert symbol name: \"{name}\" to aml name!");
|
|
||||||
return Err(Error::new(EBADF));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(result) = self.ctx.aml_eval(aml_name, args) else {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(serialized_result) = ron::ser::to_string(&result) else {
|
|
||||||
log::error!("Failed to serialize aml result!");
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
};
|
|
||||||
|
|
||||||
let byte_result = serialized_result.as_bytes();
|
|
||||||
let result_len = byte_result.len();
|
|
||||||
|
|
||||||
if result_len > payload.len() {
|
|
||||||
return Err(Error::new(EOVERFLOW));
|
|
||||||
}
|
|
||||||
|
|
||||||
payload[..result_len].copy_from_slice(byte_result);
|
|
||||||
|
|
||||||
Ok(result_len)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
|
|
||||||
let id = sendfd_request.id();
|
|
||||||
let num_fds = sendfd_request.num_fds();
|
|
||||||
|
|
||||||
let handle = self.handles.get(id)?;
|
|
||||||
if !matches!(handle.kind, HandleKind::RegisterPci) {
|
|
||||||
return Err(Error::new(EACCES));
|
|
||||||
}
|
|
||||||
|
|
||||||
if num_fds == 0 {
|
|
||||||
return Ok(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if num_fds > 1 {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
}
|
|
||||||
let mut new_fd = usize::MAX;
|
|
||||||
if let Err(e) = sendfd_request.obtain_fd(
|
|
||||||
&self.socket,
|
|
||||||
FobtainFdFlags::UPPER_TBL,
|
|
||||||
std::slice::from_mut(&mut new_fd),
|
|
||||||
) {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
let new_fd = libredox::Fd::new(new_fd);
|
|
||||||
|
|
||||||
if self.pci_fd.is_some() {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
} else {
|
|
||||||
self.pci_fd = Some(new_fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(num_fds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_close(&mut self, id: usize) {
|
|
||||||
self.handles.remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "amlserde"
|
|
||||||
description = "Library for serializing AML symbols"
|
|
||||||
version = "0.0.1"
|
|
||||||
authors = ["Ron Williams"]
|
|
||||||
repository = "https://gitlab.redox-os.org/redox-os/drivers"
|
|
||||||
categories = ["hardware-support"]
|
|
||||||
license = "MIT/Apache-2.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
acpi.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
toml.workspace = true
|
|
||||||
@@ -1,484 +0,0 @@
|
|||||||
use acpi::{
|
|
||||||
aml::{
|
|
||||||
namespace::AmlName,
|
|
||||||
object::{
|
|
||||||
FieldAccessType, FieldFlags, FieldUnit, FieldUnitKind, FieldUpdateRule, MethodFlags,
|
|
||||||
Object, ReferenceKind, WrappedObject,
|
|
||||||
},
|
|
||||||
op_region::{OpRegion, RegionSpace},
|
|
||||||
Interpreter,
|
|
||||||
},
|
|
||||||
Handle, Handler,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
|
||||||
ops::{Deref, Shl},
|
|
||||||
str::FromStr,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicU64, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct AmlSerde {
|
|
||||||
pub name: String,
|
|
||||||
pub value: AmlSerdeValue,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum AmlSerdeValue {
|
|
||||||
Uninitialized,
|
|
||||||
Integer(u64),
|
|
||||||
String(String),
|
|
||||||
OpRegion {
|
|
||||||
region: AmlSerdeRegionSpace,
|
|
||||||
offset: u64,
|
|
||||||
length: u64,
|
|
||||||
parent_device: String,
|
|
||||||
},
|
|
||||||
Field {
|
|
||||||
kind: AmlSerdeFieldKind,
|
|
||||||
flags: AmlSerdeFieldFlags,
|
|
||||||
offset: u64,
|
|
||||||
length: u64,
|
|
||||||
},
|
|
||||||
Device,
|
|
||||||
Event(u64),
|
|
||||||
Method {
|
|
||||||
arg_count: usize,
|
|
||||||
serialize: bool,
|
|
||||||
sync_level: u8,
|
|
||||||
},
|
|
||||||
Buffer(Vec<u8>),
|
|
||||||
BufferField {
|
|
||||||
offset: u64,
|
|
||||||
length: u64,
|
|
||||||
data: Box<AmlSerdeValue>,
|
|
||||||
},
|
|
||||||
Processor {
|
|
||||||
id: u8,
|
|
||||||
pblk_address: u32,
|
|
||||||
pblk_len: u8,
|
|
||||||
},
|
|
||||||
Mutex {
|
|
||||||
mutex: u32,
|
|
||||||
sync_level: u8,
|
|
||||||
},
|
|
||||||
Reference {
|
|
||||||
kind: AmlSerdeReferenceKind,
|
|
||||||
inner: Box<AmlSerdeValue>,
|
|
||||||
},
|
|
||||||
Package {
|
|
||||||
contents: Vec<AmlSerdeValue>,
|
|
||||||
},
|
|
||||||
PowerResource {
|
|
||||||
system_level: u8,
|
|
||||||
resource_order: u16,
|
|
||||||
},
|
|
||||||
RawDataBuffer,
|
|
||||||
ThermalZone,
|
|
||||||
Debug,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum AmlSerdeRegionSpace {
|
|
||||||
SystemMemory,
|
|
||||||
SystemIo,
|
|
||||||
PciConfig,
|
|
||||||
EmbeddedControl,
|
|
||||||
SMBus,
|
|
||||||
SystemCmos,
|
|
||||||
PciBarTarget,
|
|
||||||
IPMI,
|
|
||||||
GeneralPurposeIo,
|
|
||||||
GenericSerialBus,
|
|
||||||
Pcc,
|
|
||||||
OemDefined(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum AmlSerdeFieldKind {
|
|
||||||
Normal {
|
|
||||||
region: Box<AmlSerdeValue>,
|
|
||||||
},
|
|
||||||
Bank {
|
|
||||||
region: Box<AmlSerdeValue>,
|
|
||||||
bank: Box<AmlSerdeValue>,
|
|
||||||
bank_value: u64,
|
|
||||||
},
|
|
||||||
Index {
|
|
||||||
index: Box<AmlSerdeValue>,
|
|
||||||
data: Box<AmlSerdeValue>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct AmlSerdeFieldFlags {
|
|
||||||
pub access_type: AmlSerdeFieldAccessType,
|
|
||||||
pub lock_rule: bool, // bit 4
|
|
||||||
pub update_rule: AmlSerdeFieldUpdateRule,
|
|
||||||
}
|
|
||||||
impl Into<u8> for AmlSerdeFieldFlags {
|
|
||||||
fn into(self) -> u8 {
|
|
||||||
// bits 0..4
|
|
||||||
(self.access_type as u8) +
|
|
||||||
// bit 4
|
|
||||||
(self.lock_rule as u8).shl(4) +
|
|
||||||
// bits 5..7
|
|
||||||
(self.update_rule as u8).shl(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum AmlSerdeFieldAccessType {
|
|
||||||
Any = 0,
|
|
||||||
Byte = 1,
|
|
||||||
Word = 2,
|
|
||||||
DWord = 3,
|
|
||||||
QWord = 4,
|
|
||||||
Buffer = 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum AmlSerdeFieldUpdateRule {
|
|
||||||
Preserve = 0,
|
|
||||||
WriteAsOnes = 1,
|
|
||||||
WriteAsZeros = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub enum AmlSerdeReferenceKind {
|
|
||||||
RefOf,
|
|
||||||
Local,
|
|
||||||
Arg,
|
|
||||||
Index,
|
|
||||||
Named,
|
|
||||||
Unresolved,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AmlSerde {
|
|
||||||
pub fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: "name".to_owned(),
|
|
||||||
value: AmlSerdeValue::String(String::default()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_aml<H: Handler>(aml_context: &Interpreter<H>, aml_name: &AmlName) -> Option<Self> {
|
|
||||||
//TODO: why does namespace.get not take a reference to aml_name
|
|
||||||
let aml_value = if let Ok(aml_value) = aml_context.namespace.lock().get(aml_name.clone()) {
|
|
||||||
aml_value
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if let Some(value) = AmlSerdeValue::from_aml_value(aml_value.deref()) {
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(AmlSerde {
|
|
||||||
name: aml_name.to_string(),
|
|
||||||
value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AmlSerdeValue {
|
|
||||||
pub fn default() -> Self {
|
|
||||||
AmlSerdeValue::String("".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_aml_value(aml_value: &Object) -> Option<Self> {
|
|
||||||
Some(match aml_value {
|
|
||||||
Object::Uninitialized => AmlSerdeValue::Uninitialized,
|
|
||||||
Object::Integer(n) => AmlSerdeValue::Integer(n.to_owned()),
|
|
||||||
Object::String(s) => AmlSerdeValue::String(s.to_owned()),
|
|
||||||
Object::OpRegion(region) => AmlSerdeValue::OpRegion {
|
|
||||||
region: match region.space {
|
|
||||||
RegionSpace::SystemMemory => AmlSerdeRegionSpace::SystemMemory,
|
|
||||||
RegionSpace::SystemIO => AmlSerdeRegionSpace::SystemIo,
|
|
||||||
RegionSpace::PciConfig => AmlSerdeRegionSpace::PciConfig,
|
|
||||||
RegionSpace::EmbeddedControl => AmlSerdeRegionSpace::EmbeddedControl,
|
|
||||||
RegionSpace::SmBus => AmlSerdeRegionSpace::SMBus,
|
|
||||||
RegionSpace::SystemCmos => AmlSerdeRegionSpace::SystemCmos,
|
|
||||||
RegionSpace::PciBarTarget => AmlSerdeRegionSpace::PciBarTarget,
|
|
||||||
RegionSpace::Ipmi => AmlSerdeRegionSpace::IPMI,
|
|
||||||
RegionSpace::GeneralPurposeIo => AmlSerdeRegionSpace::GeneralPurposeIo,
|
|
||||||
RegionSpace::GenericSerialBus => AmlSerdeRegionSpace::GenericSerialBus,
|
|
||||||
RegionSpace::Pcc => AmlSerdeRegionSpace::Pcc,
|
|
||||||
RegionSpace::Oem(n) => AmlSerdeRegionSpace::OemDefined(n.to_owned()),
|
|
||||||
},
|
|
||||||
offset: region.base,
|
|
||||||
length: region.length,
|
|
||||||
parent_device: region.parent_device_path.to_string(),
|
|
||||||
},
|
|
||||||
Object::FieldUnit(field) => AmlSerdeValue::Field {
|
|
||||||
kind: match &field.kind {
|
|
||||||
FieldUnitKind::Normal { region } => AmlSerdeFieldKind::Normal {
|
|
||||||
region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?,
|
|
||||||
},
|
|
||||||
FieldUnitKind::Bank {
|
|
||||||
region,
|
|
||||||
bank,
|
|
||||||
bank_value,
|
|
||||||
} => AmlSerdeFieldKind::Bank {
|
|
||||||
region: AmlSerdeValue::from_aml_value(region.deref()).map(Box::new)?,
|
|
||||||
bank: AmlSerdeValue::from_aml_value(bank.deref()).map(Box::new)?,
|
|
||||||
bank_value: bank_value.to_owned(),
|
|
||||||
},
|
|
||||||
FieldUnitKind::Index { index, data } => AmlSerdeFieldKind::Index {
|
|
||||||
index: AmlSerdeValue::from_aml_value(index.deref()).map(Box::new)?,
|
|
||||||
data: AmlSerdeValue::from_aml_value(data.deref()).map(Box::new)?,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
flags: AmlSerdeFieldFlags {
|
|
||||||
access_type: match field.flags.access_type() {
|
|
||||||
Ok(FieldAccessType::Any) => AmlSerdeFieldAccessType::Any,
|
|
||||||
Ok(FieldAccessType::Byte) => AmlSerdeFieldAccessType::Byte,
|
|
||||||
Ok(FieldAccessType::Word) => AmlSerdeFieldAccessType::Word,
|
|
||||||
Ok(FieldAccessType::DWord) => AmlSerdeFieldAccessType::DWord,
|
|
||||||
Ok(FieldAccessType::QWord) => AmlSerdeFieldAccessType::QWord,
|
|
||||||
Ok(FieldAccessType::Buffer) => AmlSerdeFieldAccessType::Buffer,
|
|
||||||
_ => return None,
|
|
||||||
},
|
|
||||||
lock_rule: field.flags.lock_rule(),
|
|
||||||
update_rule: match field.flags.update_rule() {
|
|
||||||
FieldUpdateRule::Preserve => AmlSerdeFieldUpdateRule::Preserve,
|
|
||||||
FieldUpdateRule::WriteAsOnes => AmlSerdeFieldUpdateRule::WriteAsOnes,
|
|
||||||
FieldUpdateRule::WriteAsZeros => AmlSerdeFieldUpdateRule::WriteAsZeros,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
offset: field.bit_index as u64,
|
|
||||||
length: field.bit_length as u64,
|
|
||||||
},
|
|
||||||
Object::Device => AmlSerdeValue::Device,
|
|
||||||
Object::Event(event) => AmlSerdeValue::Event(event.load(Ordering::Relaxed)),
|
|
||||||
Object::Method { flags, code: _ } => AmlSerdeValue::Method {
|
|
||||||
arg_count: flags.arg_count(),
|
|
||||||
serialize: flags.serialize(),
|
|
||||||
sync_level: flags.sync_level(),
|
|
||||||
},
|
|
||||||
//TODO: distinguish from Method?
|
|
||||||
Object::NativeMethod { f: _, flags } => AmlSerdeValue::Method {
|
|
||||||
arg_count: flags.arg_count(),
|
|
||||||
serialize: flags.serialize(),
|
|
||||||
sync_level: flags.sync_level(),
|
|
||||||
},
|
|
||||||
Object::Buffer(buffer_data) => AmlSerdeValue::Buffer(buffer_data.to_owned()),
|
|
||||||
Object::BufferField {
|
|
||||||
buffer,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
} => AmlSerdeValue::BufferField {
|
|
||||||
offset: offset.to_owned() as u64,
|
|
||||||
length: length.to_owned() as u64,
|
|
||||||
data: AmlSerdeValue::from_aml_value(buffer.deref()).map(Box::new)?,
|
|
||||||
},
|
|
||||||
Object::Processor {
|
|
||||||
proc_id,
|
|
||||||
pblk_address,
|
|
||||||
pblk_length,
|
|
||||||
} => AmlSerdeValue::Processor {
|
|
||||||
id: proc_id.to_owned(),
|
|
||||||
pblk_address: pblk_address.to_owned(),
|
|
||||||
pblk_len: pblk_length.to_owned(),
|
|
||||||
},
|
|
||||||
Object::Mutex { mutex, sync_level } => AmlSerdeValue::Mutex {
|
|
||||||
mutex: mutex.0,
|
|
||||||
sync_level: sync_level.to_owned(),
|
|
||||||
},
|
|
||||||
Object::Reference { kind, inner } => AmlSerdeValue::Reference {
|
|
||||||
kind: match kind {
|
|
||||||
ReferenceKind::RefOf => AmlSerdeReferenceKind::RefOf,
|
|
||||||
ReferenceKind::Local => AmlSerdeReferenceKind::Local,
|
|
||||||
ReferenceKind::Arg => AmlSerdeReferenceKind::Arg,
|
|
||||||
ReferenceKind::Index => AmlSerdeReferenceKind::Index,
|
|
||||||
ReferenceKind::Named => AmlSerdeReferenceKind::Named,
|
|
||||||
ReferenceKind::Unresolved => AmlSerdeReferenceKind::Unresolved,
|
|
||||||
},
|
|
||||||
inner: AmlSerdeValue::from_aml_value(inner.deref()).map(Box::new)?,
|
|
||||||
},
|
|
||||||
Object::Package(aml_contents) => AmlSerdeValue::Package {
|
|
||||||
contents: aml_contents
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| AmlSerdeValue::from_aml_value(item))
|
|
||||||
.collect(),
|
|
||||||
},
|
|
||||||
Object::PowerResource {
|
|
||||||
system_level,
|
|
||||||
resource_order,
|
|
||||||
} => AmlSerdeValue::PowerResource {
|
|
||||||
system_level: system_level.to_owned(),
|
|
||||||
resource_order: resource_order.to_owned(),
|
|
||||||
},
|
|
||||||
Object::RawDataBuffer => AmlSerdeValue::RawDataBuffer,
|
|
||||||
Object::ThermalZone => AmlSerdeValue::ThermalZone,
|
|
||||||
Object::Debug => AmlSerdeValue::Debug,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
pub fn to_aml_object(self) -> Option<Object> {
|
|
||||||
Some(match self {
|
|
||||||
AmlSerdeValue::Uninitialized => Object::Uninitialized,
|
|
||||||
AmlSerdeValue::Integer(n) => Object::Integer(n),
|
|
||||||
AmlSerdeValue::String(s) => Object::String(s),
|
|
||||||
AmlSerdeValue::OpRegion {
|
|
||||||
region,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
parent_device,
|
|
||||||
} => Object::OpRegion(OpRegion {
|
|
||||||
space: match region {
|
|
||||||
AmlSerdeRegionSpace::PciConfig => RegionSpace::PciConfig,
|
|
||||||
AmlSerdeRegionSpace::EmbeddedControl => RegionSpace::EmbeddedControl,
|
|
||||||
AmlSerdeRegionSpace::SMBus => RegionSpace::SmBus,
|
|
||||||
AmlSerdeRegionSpace::SystemCmos => RegionSpace::SystemCmos,
|
|
||||||
AmlSerdeRegionSpace::PciBarTarget => RegionSpace::PciBarTarget,
|
|
||||||
AmlSerdeRegionSpace::IPMI => RegionSpace::Ipmi,
|
|
||||||
AmlSerdeRegionSpace::GeneralPurposeIo => RegionSpace::GeneralPurposeIo,
|
|
||||||
AmlSerdeRegionSpace::GenericSerialBus => RegionSpace::GenericSerialBus,
|
|
||||||
AmlSerdeRegionSpace::SystemMemory => RegionSpace::SystemMemory,
|
|
||||||
AmlSerdeRegionSpace::SystemIo => RegionSpace::SystemIO,
|
|
||||||
AmlSerdeRegionSpace::Pcc => RegionSpace::Pcc,
|
|
||||||
AmlSerdeRegionSpace::OemDefined(n) => RegionSpace::Oem(n),
|
|
||||||
},
|
|
||||||
base: offset,
|
|
||||||
length,
|
|
||||||
//
|
|
||||||
parent_device_path: AmlName::from_str(&parent_device).ok()?, // TODO: Error value hidden
|
|
||||||
}),
|
|
||||||
AmlSerdeValue::Field {
|
|
||||||
kind,
|
|
||||||
flags,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
} => Object::FieldUnit(FieldUnit {
|
|
||||||
kind: match kind {
|
|
||||||
AmlSerdeFieldKind::Normal { region } => FieldUnitKind::Normal {
|
|
||||||
region: region.to_aml_object()?.wrap(),
|
|
||||||
},
|
|
||||||
AmlSerdeFieldKind::Bank {
|
|
||||||
region,
|
|
||||||
bank,
|
|
||||||
bank_value,
|
|
||||||
} => FieldUnitKind::Bank {
|
|
||||||
region: region.to_aml_object()?.wrap(),
|
|
||||||
bank: bank.to_aml_object()?.wrap(),
|
|
||||||
bank_value: bank_value.to_owned(),
|
|
||||||
},
|
|
||||||
AmlSerdeFieldKind::Index { index, data } => FieldUnitKind::Index {
|
|
||||||
index: index.to_aml_object()?.wrap(),
|
|
||||||
data: data.to_aml_object()?.wrap(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
flags: FieldFlags(flags.into()),
|
|
||||||
bit_index: offset as usize,
|
|
||||||
bit_length: length as usize,
|
|
||||||
}),
|
|
||||||
AmlSerdeValue::Device => Object::Device,
|
|
||||||
AmlSerdeValue::Event(event) => Object::Event(Arc::new(AtomicU64::new(event))),
|
|
||||||
AmlSerdeValue::Method {
|
|
||||||
arg_count,
|
|
||||||
serialize,
|
|
||||||
sync_level,
|
|
||||||
} => Object::Method {
|
|
||||||
code: (return None), //TODO figure out what to do here
|
|
||||||
//TODO check specs to see if all bit patterns are allowed
|
|
||||||
flags: MethodFlags(
|
|
||||||
(arg_count as u8).clamp(0, 7)
|
|
||||||
+ (serialize as u8).shl(3)
|
|
||||||
+ sync_level.clamp(0, 15).shl(4),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
//TODO: handle native method?
|
|
||||||
AmlSerdeValue::Buffer(buffer_data) => Object::Buffer(buffer_data),
|
|
||||||
AmlSerdeValue::BufferField {
|
|
||||||
data,
|
|
||||||
offset,
|
|
||||||
length,
|
|
||||||
} => Object::BufferField {
|
|
||||||
offset: offset as usize,
|
|
||||||
length: length as usize,
|
|
||||||
buffer: data.to_aml_object()?.wrap(),
|
|
||||||
},
|
|
||||||
AmlSerdeValue::Processor {
|
|
||||||
id,
|
|
||||||
pblk_address,
|
|
||||||
pblk_len,
|
|
||||||
} => Object::Processor {
|
|
||||||
proc_id: id,
|
|
||||||
pblk_address,
|
|
||||||
pblk_length: pblk_len,
|
|
||||||
},
|
|
||||||
AmlSerdeValue::Mutex { mutex, sync_level } => Object::Mutex {
|
|
||||||
mutex: Handle(mutex),
|
|
||||||
sync_level: sync_level,
|
|
||||||
},
|
|
||||||
AmlSerdeValue::Reference { kind, inner } => Object::Reference {
|
|
||||||
kind: match kind {
|
|
||||||
AmlSerdeReferenceKind::RefOf => ReferenceKind::RefOf,
|
|
||||||
AmlSerdeReferenceKind::Local => ReferenceKind::Local,
|
|
||||||
AmlSerdeReferenceKind::Arg => ReferenceKind::Arg,
|
|
||||||
AmlSerdeReferenceKind::Index => ReferenceKind::Index,
|
|
||||||
AmlSerdeReferenceKind::Named => ReferenceKind::Named,
|
|
||||||
AmlSerdeReferenceKind::Unresolved => ReferenceKind::Unresolved,
|
|
||||||
},
|
|
||||||
inner: inner.to_aml_object()?.wrap(),
|
|
||||||
},
|
|
||||||
AmlSerdeValue::Package { contents } => Object::Package(
|
|
||||||
contents
|
|
||||||
.into_iter()
|
|
||||||
.map(|item| item.to_aml_object().map(Object::wrap)) // TODO: see if errors should be ignored here
|
|
||||||
.collect::<Option<Vec<WrappedObject>>>()?,
|
|
||||||
),
|
|
||||||
AmlSerdeValue::PowerResource {
|
|
||||||
system_level,
|
|
||||||
resource_order,
|
|
||||||
} => Object::PowerResource {
|
|
||||||
system_level: system_level.to_owned(),
|
|
||||||
resource_order: resource_order.to_owned(),
|
|
||||||
},
|
|
||||||
AmlSerdeValue::RawDataBuffer => Object::RawDataBuffer,
|
|
||||||
AmlSerdeValue::ThermalZone => Object::ThermalZone,
|
|
||||||
AmlSerdeValue::Debug => Object::Debug,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod aml_serde_name {
|
|
||||||
use acpi::aml::namespace::AmlName;
|
|
||||||
|
|
||||||
/// Add a leading backslash to make the name a valid
|
|
||||||
/// namespace reference
|
|
||||||
pub fn to_aml_format(pretty_name: &String) -> String {
|
|
||||||
format!("\\{}", pretty_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// convert a string from AML namespace style to
|
|
||||||
/// acpi symbol style
|
|
||||||
pub fn to_symbol(aml_style_name: &String) -> String {
|
|
||||||
let mut name = aml_style_name.to_owned();
|
|
||||||
|
|
||||||
// remove leading slash
|
|
||||||
name = name.trim_start_matches("\\").to_owned();
|
|
||||||
// remove unnecessary underscores
|
|
||||||
while let Some(index) = name.find("_.") {
|
|
||||||
name.remove(index);
|
|
||||||
}
|
|
||||||
while name.len() > 0 && &name[name.len() - 1..] == "_" {
|
|
||||||
name.pop();
|
|
||||||
}
|
|
||||||
name.shrink_to_fit();
|
|
||||||
name
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert to string and remove
|
|
||||||
/// trailing underscores from each name segment
|
|
||||||
pub fn aml_to_symbol(aml_name: &AmlName) -> String {
|
|
||||||
to_symbol(&aml_name.as_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "ac97d"
|
|
||||||
description = "AC'97 driver"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
common = { path = "../../common" }
|
|
||||||
libredox.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
redox_event.workspace = true
|
|
||||||
redox_syscall.workspace = true
|
|
||||||
spin.workspace = true
|
|
||||||
|
|
||||||
daemon = { path = "../../../daemon" }
|
|
||||||
pcid = { path = "../../pcid" }
|
|
||||||
redox-scheme.workspace = true
|
|
||||||
scheme-utils = { path = "../../../scheme-utils" }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[[drivers]]
|
|
||||||
name = "AC97 Audio"
|
|
||||||
class = 0x04
|
|
||||||
subclass = 0x01
|
|
||||||
command = ["ac97d"]
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
use common::io::Pio;
|
|
||||||
use redox_scheme::scheme::SchemeSync;
|
|
||||||
use redox_scheme::CallerCtx;
|
|
||||||
use redox_scheme::OpenResult;
|
|
||||||
use scheme_utils::{FpathWriter, HandleMap};
|
|
||||||
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT};
|
|
||||||
use syscall::schemev2::NewFdFlags;
|
|
||||||
use syscall::EWOULDBLOCK;
|
|
||||||
|
|
||||||
use common::{
|
|
||||||
dma::Dma,
|
|
||||||
io::{Io, Mmio},
|
|
||||||
};
|
|
||||||
use spin::Mutex;
|
|
||||||
|
|
||||||
const NUM_SUB_BUFFS: usize = 32;
|
|
||||||
const SUB_BUFF_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
enum Handle {
|
|
||||||
Todo,
|
|
||||||
SchemeRoot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct MixerRegs {
|
|
||||||
/* 0x00 */ reset: Pio<u16>,
|
|
||||||
/* 0x02 */ master_volume: Pio<u16>,
|
|
||||||
/* 0x04 */ aux_out_volume: Pio<u16>,
|
|
||||||
/* 0x06 */ mono_volume: Pio<u16>,
|
|
||||||
/* 0x08 */ master_tone: Pio<u16>,
|
|
||||||
/* 0x0A */ pc_beep_volume: Pio<u16>,
|
|
||||||
/* 0x0C */ phone_volume: Pio<u16>,
|
|
||||||
/* 0x0E */ mic_volume: Pio<u16>,
|
|
||||||
/* 0x10 */ line_in_volume: Pio<u16>,
|
|
||||||
/* 0x12 */ cd_volume: Pio<u16>,
|
|
||||||
/* 0x14 */ video_volume: Pio<u16>,
|
|
||||||
/* 0x16 */ aux_in_volume: Pio<u16>,
|
|
||||||
/* 0x18 */ pcm_out_volume: Pio<u16>,
|
|
||||||
/* 0x1A */ record_select: Pio<u16>,
|
|
||||||
/* 0x1C */ record_gain: Pio<u16>,
|
|
||||||
/* 0x1E */ record_gain_mic: Pio<u16>,
|
|
||||||
/* 0x20 */ general_purpose: Pio<u16>,
|
|
||||||
/* 0x22 */ control_3d: Pio<u16>,
|
|
||||||
/* 0x24 */ audio_int_paging: Pio<u16>,
|
|
||||||
/* 0x26 */ powerdown: Pio<u16>,
|
|
||||||
/* 0x28 */ extended_id: Pio<u16>,
|
|
||||||
/* 0x2A */ extended_ctrl: Pio<u16>,
|
|
||||||
/* 0x2C */ vra_pcm_front: Pio<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MixerRegs {
|
|
||||||
fn new(bar0: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
reset: Pio::new(bar0 + 0x00),
|
|
||||||
master_volume: Pio::new(bar0 + 0x02),
|
|
||||||
aux_out_volume: Pio::new(bar0 + 0x04),
|
|
||||||
mono_volume: Pio::new(bar0 + 0x06),
|
|
||||||
master_tone: Pio::new(bar0 + 0x08),
|
|
||||||
pc_beep_volume: Pio::new(bar0 + 0x0A),
|
|
||||||
phone_volume: Pio::new(bar0 + 0x0C),
|
|
||||||
mic_volume: Pio::new(bar0 + 0x0E),
|
|
||||||
line_in_volume: Pio::new(bar0 + 0x10),
|
|
||||||
cd_volume: Pio::new(bar0 + 0x12),
|
|
||||||
video_volume: Pio::new(bar0 + 0x14),
|
|
||||||
aux_in_volume: Pio::new(bar0 + 0x16),
|
|
||||||
pcm_out_volume: Pio::new(bar0 + 0x18),
|
|
||||||
record_select: Pio::new(bar0 + 0x1A),
|
|
||||||
record_gain: Pio::new(bar0 + 0x1C),
|
|
||||||
record_gain_mic: Pio::new(bar0 + 0x1E),
|
|
||||||
general_purpose: Pio::new(bar0 + 0x20),
|
|
||||||
control_3d: Pio::new(bar0 + 0x22),
|
|
||||||
audio_int_paging: Pio::new(bar0 + 0x24),
|
|
||||||
powerdown: Pio::new(bar0 + 0x26),
|
|
||||||
extended_id: Pio::new(bar0 + 0x28),
|
|
||||||
extended_ctrl: Pio::new(bar0 + 0x2A),
|
|
||||||
vra_pcm_front: Pio::new(bar0 + 0x2C),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct BusBoxRegs {
|
|
||||||
/// Buffer descriptor list base address
|
|
||||||
/* 0x00 */
|
|
||||||
bdbar: Pio<u32>,
|
|
||||||
/// Current index value
|
|
||||||
/* 0x04 */
|
|
||||||
civ: Pio<u8>,
|
|
||||||
/// Last valid index
|
|
||||||
/* 0x05 */
|
|
||||||
lvi: Pio<u8>,
|
|
||||||
/// Status
|
|
||||||
/* 0x06 */
|
|
||||||
sr: Pio<u16>,
|
|
||||||
/// Position in current buffer
|
|
||||||
/* 0x08 */
|
|
||||||
picb: Pio<u16>,
|
|
||||||
/// Prefetched index value
|
|
||||||
/* 0x0A */
|
|
||||||
piv: Pio<u8>,
|
|
||||||
/// Control
|
|
||||||
/* 0x0B */
|
|
||||||
cr: Pio<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusBoxRegs {
|
|
||||||
fn new(base: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
bdbar: Pio::new(base + 0x00),
|
|
||||||
civ: Pio::new(base + 0x04),
|
|
||||||
lvi: Pio::new(base + 0x05),
|
|
||||||
sr: Pio::new(base + 0x06),
|
|
||||||
picb: Pio::new(base + 0x08),
|
|
||||||
piv: Pio::new(base + 0x0A),
|
|
||||||
cr: Pio::new(base + 0x0B),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct BusRegs {
|
|
||||||
/// PCM in register box
|
|
||||||
/* 0x00 */
|
|
||||||
pi: BusBoxRegs,
|
|
||||||
/// PCM out register box
|
|
||||||
/* 0x10 */
|
|
||||||
po: BusBoxRegs,
|
|
||||||
/// Microphone register box
|
|
||||||
/* 0x20 */
|
|
||||||
mc: BusBoxRegs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BusRegs {
|
|
||||||
fn new(bar1: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
pi: BusBoxRegs::new(bar1 + 0x00),
|
|
||||||
po: BusBoxRegs::new(bar1 + 0x10),
|
|
||||||
mc: BusBoxRegs::new(bar1 + 0x20),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct BufferDescriptor {
|
|
||||||
/* 0x00 */ addr: Mmio<u32>,
|
|
||||||
/* 0x04 */ samples: Mmio<u16>,
|
|
||||||
/* 0x06 */ flags: Mmio<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ac97 {
|
|
||||||
mixer: MixerRegs,
|
|
||||||
bus: BusRegs,
|
|
||||||
bdl: Dma<[BufferDescriptor; NUM_SUB_BUFFS]>,
|
|
||||||
buf: Dma<[u8; NUM_SUB_BUFFS * SUB_BUFF_SIZE]>,
|
|
||||||
handles: Mutex<HandleMap<Handle>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ac97 {
|
|
||||||
pub unsafe fn new(bar0: u16, bar1: u16) -> Result<Self> {
|
|
||||||
let mut module = Ac97 {
|
|
||||||
mixer: MixerRegs::new(bar0),
|
|
||||||
bus: BusRegs::new(bar1),
|
|
||||||
bdl: Dma::zeroed(
|
|
||||||
//TODO: PhysBox::new_in_32bit_space(bdl_size)?
|
|
||||||
)?
|
|
||||||
.assume_init(),
|
|
||||||
buf: Dma::zeroed(
|
|
||||||
//TODO: PhysBox::new_in_32bit_space(buf_size)?
|
|
||||||
)?
|
|
||||||
.assume_init(),
|
|
||||||
handles: Mutex::new(HandleMap::new()),
|
|
||||||
};
|
|
||||||
|
|
||||||
module.init()?;
|
|
||||||
|
|
||||||
Ok(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()> {
|
|
||||||
//TODO: support other sample rates, or just the default of 48000 Hz
|
|
||||||
{
|
|
||||||
// Check if VRA is supported
|
|
||||||
if !self.mixer.extended_id.readf(1 << 0) {
|
|
||||||
println!("ac97d: VRA not supported and is currently required");
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable VRA
|
|
||||||
self.mixer.extended_ctrl.writef(1 << 0, true);
|
|
||||||
|
|
||||||
// Attempt to set sample rate for PCM front to 44100 Hz
|
|
||||||
let desired_sample_rate = 44100;
|
|
||||||
self.mixer.vra_pcm_front.write(desired_sample_rate);
|
|
||||||
|
|
||||||
// Read back real sample rate
|
|
||||||
let real_sample_rate = self.mixer.vra_pcm_front.read();
|
|
||||||
println!("ac97d: set sample rate to {}", real_sample_rate);
|
|
||||||
|
|
||||||
// Error if we cannot set the sample rate as desired
|
|
||||||
if real_sample_rate != desired_sample_rate {
|
|
||||||
println!(
|
|
||||||
"ac97d: sample rate is {} but only {} is supported",
|
|
||||||
real_sample_rate, desired_sample_rate
|
|
||||||
);
|
|
||||||
return Err(Error::new(ENOENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure PCM out is stopped
|
|
||||||
self.bus.po.cr.writef(1, false);
|
|
||||||
|
|
||||||
// Reset PCM out
|
|
||||||
self.bus.po.cr.writef(1 << 1, true);
|
|
||||||
while self.bus.po.cr.readf(1 << 1) {
|
|
||||||
// Spinning on resetting PCM out
|
|
||||||
//TODO: relax
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize BDL for PCM out
|
|
||||||
for i in 0..NUM_SUB_BUFFS {
|
|
||||||
self.bdl[i]
|
|
||||||
.addr
|
|
||||||
.write((self.buf.physical() + i * SUB_BUFF_SIZE) as u32);
|
|
||||||
self.bdl[i]
|
|
||||||
.samples
|
|
||||||
.write((SUB_BUFF_SIZE / 2/* Each sample is i16 or 2 bytes */) as u16);
|
|
||||||
self.bdl[i]
|
|
||||||
.flags
|
|
||||||
.write(1 << 15 /* Interrupt on completion */);
|
|
||||||
}
|
|
||||||
self.bus.po.bdbar.write(self.bdl.physical() as u32);
|
|
||||||
|
|
||||||
// Enable interrupt on completion
|
|
||||||
self.bus.po.cr.writef(1 << 4, true);
|
|
||||||
|
|
||||||
// Start bus master
|
|
||||||
self.bus.po.cr.writef(1 << 0, true);
|
|
||||||
|
|
||||||
// Set master volume to 0 db (loudest output, DANGER!)
|
|
||||||
self.mixer.master_volume.write(0);
|
|
||||||
|
|
||||||
// Set PCM output volume to 0 db (medium)
|
|
||||||
self.mixer.pcm_out_volume.write(0x808);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn irq(&mut self) -> bool {
|
|
||||||
let ints = self.bus.po.sr.read() & 0b11100;
|
|
||||||
if ints != 0 {
|
|
||||||
self.bus.po.sr.write(ints);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemeSync for Ac97 {
|
|
||||||
fn scheme_root(&mut self) -> Result<usize> {
|
|
||||||
Ok(self.handles.lock().insert(Handle::SchemeRoot))
|
|
||||||
}
|
|
||||||
fn openat(
|
|
||||||
&mut self,
|
|
||||||
dirfd: usize,
|
|
||||||
_path: &str,
|
|
||||||
_flags: usize,
|
|
||||||
_fcntl_flags: u32,
|
|
||||||
ctx: &CallerCtx,
|
|
||||||
) -> Result<OpenResult> {
|
|
||||||
{
|
|
||||||
let handles = self.handles.lock();
|
|
||||||
let handle = handles.get(dirfd)?;
|
|
||||||
if !matches!(handle, Handle::SchemeRoot) {
|
|
||||||
return Err(Error::new(EACCES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ctx.uid == 0 {
|
|
||||||
let id = self.handles.lock().insert(Handle::Todo);
|
|
||||||
Ok(OpenResult::ThisScheme {
|
|
||||||
number: id,
|
|
||||||
flags: NewFdFlags::empty(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Error::new(EACCES))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(
|
|
||||||
&mut self,
|
|
||||||
id: usize,
|
|
||||||
buf: &[u8],
|
|
||||||
_offset: u64,
|
|
||||||
_flags: u32,
|
|
||||||
_ctx: &CallerCtx,
|
|
||||||
) -> Result<usize> {
|
|
||||||
{
|
|
||||||
let mut handles = self.handles.lock();
|
|
||||||
let handle = handles.get_mut(id)?;
|
|
||||||
if !matches!(handle, Handle::Todo) {
|
|
||||||
return Err(Error::new(EBADF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if buf.len() != SUB_BUFF_SIZE {
|
|
||||||
return Err(Error::new(EINVAL));
|
|
||||||
}
|
|
||||||
|
|
||||||
let civ = self.bus.po.civ.read() as usize;
|
|
||||||
let mut lvi = self.bus.po.lvi.read() as usize;
|
|
||||||
if lvi == (civ + 3) % NUM_SUB_BUFFS {
|
|
||||||
// Block if we already are 3 buffers ahead
|
|
||||||
Err(Error::new(EWOULDBLOCK))
|
|
||||||
} else {
|
|
||||||
// Fill next buffer
|
|
||||||
lvi = (lvi + 1) % NUM_SUB_BUFFS;
|
|
||||||
for i in 0..SUB_BUFF_SIZE {
|
|
||||||
self.buf[lvi * SUB_BUFF_SIZE + i] = buf[i];
|
|
||||||
}
|
|
||||||
self.bus.po.lvi.write(lvi as u8);
|
|
||||||
|
|
||||||
Ok(SUB_BUFF_SIZE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
|
||||||
FpathWriter::with(buf, "audiohw", |_| Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_close(&mut self, id: usize) {
|
|
||||||
self.handles.lock().remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
use std::io::{Read, Write};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
use event::{user_data, EventQueue};
|
|
||||||
use pcid_interface::PciFunctionHandle;
|
|
||||||
use redox_scheme::scheme::register_sync_scheme;
|
|
||||||
use redox_scheme::Socket;
|
|
||||||
use scheme_utils::ReadinessBased;
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
pub mod device;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
pcid_interface::pci_daemon(daemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
|
||||||
let pci_config = pcid_handle.config();
|
|
||||||
|
|
||||||
let mut name = pci_config.func.name();
|
|
||||||
name.push_str("_ac97");
|
|
||||||
|
|
||||||
let bar0 = pci_config.func.bars[0].expect_port();
|
|
||||||
let bar1 = pci_config.func.bars[1].expect_port();
|
|
||||||
|
|
||||||
let irq = pci_config
|
|
||||||
.func
|
|
||||||
.legacy_interrupt_line
|
|
||||||
.expect("ac97d: no legacy interrupts supported");
|
|
||||||
|
|
||||||
println!(" + ac97 {}", pci_config.func.display());
|
|
||||||
|
|
||||||
common::setup_logging(
|
|
||||||
"audio",
|
|
||||||
"pci",
|
|
||||||
&name,
|
|
||||||
common::output_level(),
|
|
||||||
common::file_level(),
|
|
||||||
);
|
|
||||||
|
|
||||||
common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3");
|
|
||||||
|
|
||||||
let mut irq_file = irq.irq_handle("ac97d");
|
|
||||||
|
|
||||||
let socket = Socket::nonblock().expect("ac97d: failed to create socket");
|
|
||||||
let mut device =
|
|
||||||
unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") };
|
|
||||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
|
||||||
|
|
||||||
user_data! {
|
|
||||||
enum Source {
|
|
||||||
Irq,
|
|
||||||
Scheme,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_queue = EventQueue::<Source>::new().expect("ac97d: Could not create event queue.");
|
|
||||||
event_queue
|
|
||||||
.subscribe(
|
|
||||||
irq_file.as_raw_fd() as usize,
|
|
||||||
Source::Irq,
|
|
||||||
event::EventFlags::READ,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
event_queue
|
|
||||||
.subscribe(
|
|
||||||
socket.inner().raw(),
|
|
||||||
Source::Scheme,
|
|
||||||
event::EventFlags::READ,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
register_sync_scheme(&socket, "audiohw", &mut device)
|
|
||||||
.expect("ac97d: failed to register audiohw scheme to namespace");
|
|
||||||
daemon.ready();
|
|
||||||
|
|
||||||
libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
|
|
||||||
|
|
||||||
let all = [Source::Irq, Source::Scheme];
|
|
||||||
for event in all
|
|
||||||
.into_iter()
|
|
||||||
.chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
|
|
||||||
{
|
|
||||||
match event {
|
|
||||||
Source::Irq => {
|
|
||||||
let mut irq = [0; 8];
|
|
||||||
irq_file.read(&mut irq).unwrap();
|
|
||||||
|
|
||||||
if !device.irq() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
irq_file.write(&mut irq).unwrap();
|
|
||||||
|
|
||||||
readiness_based
|
|
||||||
.poll_all_requests(&mut device)
|
|
||||||
.expect("ac97d: failed to poll requests");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("ac97d: failed to write to socket");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let next_read = device_irq.next_read();
|
|
||||||
if next_read > 0 {
|
|
||||||
return Ok(Some(next_read));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
Source::Scheme => {
|
|
||||||
readiness_based
|
|
||||||
.read_and_process_requests(&mut device)
|
|
||||||
.expect("ac97d: failed to read from socket");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("ac97d: failed to write to socket");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let next_read = device.borrow().next_read();
|
|
||||||
if next_read > 0 {
|
|
||||||
return Ok(Some(next_read));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
|
||||||
fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "ihdad"
|
|
||||||
description = "Intel HD Audio chipset driver"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags.workspace = true
|
|
||||||
libredox.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
redox_event.workspace = true
|
|
||||||
redox_syscall.workspace = true
|
|
||||||
spin.workspace = true
|
|
||||||
|
|
||||||
common = { path = "../../common" }
|
|
||||||
daemon = { path = "../../../daemon" }
|
|
||||||
pcid = { path = "../../pcid" }
|
|
||||||
redox-scheme.workspace = true
|
|
||||||
scheme-utils = { path = "../../../scheme-utils" }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[[drivers]]
|
|
||||||
name = "Intel HD Audio"
|
|
||||||
class = 0x04
|
|
||||||
subclass = 0x03
|
|
||||||
command = ["ihdad"]
|
|
||||||
@@ -1,501 +0,0 @@
|
|||||||
use common::dma::Dma;
|
|
||||||
use common::io::{Io, Mmio};
|
|
||||||
use common::timeout::Timeout;
|
|
||||||
use syscall::error::{Error, Result, EIO};
|
|
||||||
|
|
||||||
use super::common::*;
|
|
||||||
|
|
||||||
// CORBCTL
|
|
||||||
const CMEIE: u8 = 1 << 0; // 1 bit
|
|
||||||
const CORBRUN: u8 = 1 << 1; // 1 bit
|
|
||||||
|
|
||||||
// CORBSIZE
|
|
||||||
const CORBSZCAP: (u8, u8) = (4, 4);
|
|
||||||
const CORBSIZE: (u8, u8) = (0, 2);
|
|
||||||
|
|
||||||
// CORBRP
|
|
||||||
const CORBRPRST: u16 = 1 << 15;
|
|
||||||
|
|
||||||
// RIRBWP
|
|
||||||
const RIRBWPRST: u16 = 1 << 15;
|
|
||||||
|
|
||||||
// RIRBCTL
|
|
||||||
const RINTCTL: u8 = 1 << 0; // 1 bit
|
|
||||||
const RIRBDMAEN: u8 = 1 << 1; // 1 bit
|
|
||||||
|
|
||||||
const CORB_OFFSET: usize = 0x00;
|
|
||||||
const RIRB_OFFSET: usize = 0x10;
|
|
||||||
const ICMD_OFFSET: usize = 0x20;
|
|
||||||
|
|
||||||
// ICS
|
|
||||||
const ICB: u16 = 1 << 0;
|
|
||||||
const IRV: u16 = 1 << 1;
|
|
||||||
|
|
||||||
// CORB and RIRB offset
|
|
||||||
|
|
||||||
const COMMAND_BUFFER_OFFSET: usize = 0x40;
|
|
||||||
const CORB_BUFF_MAX_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
struct CommandBufferRegs {
|
|
||||||
corblbase: Mmio<u32>,
|
|
||||||
corbubase: Mmio<u32>,
|
|
||||||
corbwp: Mmio<u16>,
|
|
||||||
corbrp: Mmio<u16>,
|
|
||||||
corbctl: Mmio<u8>,
|
|
||||||
corbsts: Mmio<u8>,
|
|
||||||
corbsize: Mmio<u8>,
|
|
||||||
rsvd5: Mmio<u8>,
|
|
||||||
|
|
||||||
rirblbase: Mmio<u32>,
|
|
||||||
rirbubase: Mmio<u32>,
|
|
||||||
rirbwp: Mmio<u16>,
|
|
||||||
rintcnt: Mmio<u16>,
|
|
||||||
rirbctl: Mmio<u8>,
|
|
||||||
rirbsts: Mmio<u8>,
|
|
||||||
rirbsize: Mmio<u8>,
|
|
||||||
rsvd6: Mmio<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CorbRegs {
|
|
||||||
corblbase: Mmio<u32>,
|
|
||||||
corbubase: Mmio<u32>,
|
|
||||||
corbwp: Mmio<u16>,
|
|
||||||
corbrp: Mmio<u16>,
|
|
||||||
corbctl: Mmio<u8>,
|
|
||||||
corbsts: Mmio<u8>,
|
|
||||||
corbsize: Mmio<u8>,
|
|
||||||
rsvd5: Mmio<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Corb {
|
|
||||||
regs: &'static mut CorbRegs,
|
|
||||||
corb_base: *mut u32,
|
|
||||||
corb_base_phys: usize,
|
|
||||||
corb_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Corb {
|
|
||||||
pub fn new(regs_addr: usize, corb_buff_phys: usize, corb_buff_virt: *mut u32) -> Corb {
|
|
||||||
unsafe {
|
|
||||||
Corb {
|
|
||||||
regs: &mut *(regs_addr as *mut CorbRegs),
|
|
||||||
corb_base: corb_buff_virt,
|
|
||||||
corb_base_phys: corb_buff_phys,
|
|
||||||
corb_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Intel 4.4.1.3
|
|
||||||
pub fn init(&mut self) -> Result<()> {
|
|
||||||
self.stop()?;
|
|
||||||
//Determine CORB and RIRB size and allocate buffer
|
|
||||||
|
|
||||||
//3.3.24
|
|
||||||
let corbsize_reg = self.regs.corbsize.read();
|
|
||||||
let corbszcap = (corbsize_reg >> 4) & 0xF;
|
|
||||||
|
|
||||||
let mut corbsize_bytes: usize = 0;
|
|
||||||
let mut corbsize: u8 = 0;
|
|
||||||
|
|
||||||
if (corbszcap & 4) == 4 {
|
|
||||||
corbsize = 2;
|
|
||||||
corbsize_bytes = 1024;
|
|
||||||
|
|
||||||
self.corb_count = 256;
|
|
||||||
} else if (corbszcap & 2) == 2 {
|
|
||||||
corbsize = 1;
|
|
||||||
corbsize_bytes = 64;
|
|
||||||
|
|
||||||
self.corb_count = 16;
|
|
||||||
} else if (corbszcap & 1) == 1 {
|
|
||||||
corbsize = 0;
|
|
||||||
corbsize_bytes = 8;
|
|
||||||
|
|
||||||
self.corb_count = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(self.corb_count != 0);
|
|
||||||
let addr = self.corb_base_phys;
|
|
||||||
self.set_address(addr);
|
|
||||||
self.regs.corbsize.write((corbsize_reg & 0xFC) | corbsize);
|
|
||||||
|
|
||||||
self.reset_read_pointer()?;
|
|
||||||
let old_wp = self.regs.corbwp.read();
|
|
||||||
self.regs.corbwp.write(old_wp & 0xFF00);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
|
||||||
self.regs.corbctl.writef(CORBRUN, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(never)]
|
|
||||||
pub fn stop(&mut self) -> Result<()> {
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while self.regs.corbctl.readf(CORBRUN) {
|
|
||||||
self.regs.corbctl.writef(CORBRUN, false);
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on clearing CORBRUN");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_address(&mut self, addr: usize) {
|
|
||||||
self.regs.corblbase.write((addr & 0xFFFFFFFF) as u32);
|
|
||||||
self.regs.corbubase.write(((addr as u64) >> 32) as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_read_pointer(&mut self) -> Result<()> {
|
|
||||||
// 3.3.21
|
|
||||||
|
|
||||||
self.stop()?;
|
|
||||||
|
|
||||||
// Set CORBRPRST to 1
|
|
||||||
log::trace!("CORBRP {:X}", self.regs.corbrp.read());
|
|
||||||
self.regs.corbrp.writef(CORBRPRST, true);
|
|
||||||
log::trace!("CORBRP {:X}", self.regs.corbrp.read());
|
|
||||||
|
|
||||||
{
|
|
||||||
// Wait for it to become 1
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while !self.regs.corbrp.readf(CORBRPRST) {
|
|
||||||
self.regs.corbrp.writef(CORBRPRST, true);
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on setting CORBRPRST");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the bit again
|
|
||||||
self.regs.corbrp.writef(CORBRPRST, false);
|
|
||||||
|
|
||||||
{
|
|
||||||
// Read back the bit until zero to verify that it is cleared.
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
loop {
|
|
||||||
if !self.regs.corbrp.readf(CORBRPRST) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
self.regs.corbrp.writef(CORBRPRST, false);
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on clearing CORBRPRST");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_command(&mut self, cmd: u32) -> Result<()> {
|
|
||||||
{
|
|
||||||
// wait for the commands to finish
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while (self.regs.corbwp.read() & 0xff) != (self.regs.corbrp.read() & 0xff) {
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on CORB command");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let write_pos: usize = ((self.regs.corbwp.read() as usize & 0xFF) + 1) % self.corb_count;
|
|
||||||
unsafe {
|
|
||||||
*self.corb_base.offset(write_pos as isize) = cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.regs.corbwp.write(write_pos as u16);
|
|
||||||
|
|
||||||
log::trace!("Corb: {:08X}", cmd);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RirbRegs {
|
|
||||||
rirblbase: Mmio<u32>,
|
|
||||||
rirbubase: Mmio<u32>,
|
|
||||||
rirbwp: Mmio<u16>,
|
|
||||||
rintcnt: Mmio<u16>,
|
|
||||||
rirbctl: Mmio<u8>,
|
|
||||||
rirbsts: Mmio<u8>,
|
|
||||||
rirbsize: Mmio<u8>,
|
|
||||||
rsvd6: Mmio<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Rirb {
|
|
||||||
regs: &'static mut RirbRegs,
|
|
||||||
rirb_base: *mut u64,
|
|
||||||
rirb_base_phys: usize,
|
|
||||||
rirb_rp: u16,
|
|
||||||
rirb_count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Rirb {
|
|
||||||
pub fn new(regs_addr: usize, rirb_buff_phys: usize, rirb_buff_virt: *mut u64) -> Rirb {
|
|
||||||
unsafe {
|
|
||||||
Rirb {
|
|
||||||
regs: &mut *(regs_addr as *mut RirbRegs),
|
|
||||||
rirb_base: rirb_buff_virt,
|
|
||||||
rirb_rp: 0,
|
|
||||||
rirb_base_phys: rirb_buff_phys,
|
|
||||||
rirb_count: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Intel 4.4.1.3
|
|
||||||
pub fn init(&mut self) -> Result<()> {
|
|
||||||
self.stop()?;
|
|
||||||
|
|
||||||
let rirbsize_reg = self.regs.rirbsize.read();
|
|
||||||
let rirbszcap = (rirbsize_reg >> 4) & 0xF;
|
|
||||||
|
|
||||||
let mut rirbsize_bytes: usize = 0;
|
|
||||||
let mut rirbsize: u8 = 0;
|
|
||||||
|
|
||||||
if (rirbszcap & 4) == 4 {
|
|
||||||
rirbsize = 2;
|
|
||||||
rirbsize_bytes = 2048;
|
|
||||||
|
|
||||||
self.rirb_count = 256;
|
|
||||||
} else if (rirbszcap & 2) == 2 {
|
|
||||||
rirbsize = 1;
|
|
||||||
rirbsize_bytes = 128;
|
|
||||||
|
|
||||||
self.rirb_count = 8;
|
|
||||||
} else if (rirbszcap & 1) == 1 {
|
|
||||||
rirbsize = 0;
|
|
||||||
rirbsize_bytes = 16;
|
|
||||||
|
|
||||||
self.rirb_count = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(self.rirb_count != 0);
|
|
||||||
|
|
||||||
let addr = self.rirb_base_phys;
|
|
||||||
self.set_address(addr);
|
|
||||||
|
|
||||||
self.reset_write_pointer();
|
|
||||||
self.rirb_rp = 0;
|
|
||||||
|
|
||||||
self.regs.rintcnt.write(1);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
|
||||||
self.regs.rirbctl.writef(RIRBDMAEN | RINTCTL, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&mut self) -> Result<()> {
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while self.regs.rirbctl.readf(RIRBDMAEN) {
|
|
||||||
self.regs.rirbctl.writef(RIRBDMAEN, false);
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on clearing RIRBDMAEN");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_address(&mut self, addr: usize) {
|
|
||||||
self.regs.rirblbase.write((addr & 0xFFFFFFFF) as u32);
|
|
||||||
self.regs.rirbubase.write(((addr as u64) >> 32) as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_write_pointer(&mut self) {
|
|
||||||
self.regs.rirbwp.writef(RIRBWPRST, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_response(&mut self) -> Result<u64> {
|
|
||||||
{
|
|
||||||
// wait for response
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while (self.regs.rirbwp.read() & 0xff) == (self.rirb_rp & 0xff) {
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on RIRB response");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let read_pos: u16 = (self.rirb_rp + 1) % self.rirb_count as u16;
|
|
||||||
|
|
||||||
let res: u64;
|
|
||||||
unsafe {
|
|
||||||
res = *self.rirb_base.offset(read_pos as isize);
|
|
||||||
}
|
|
||||||
self.rirb_rp = read_pos;
|
|
||||||
log::trace!("Rirb: {:08X}", res);
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImmediateCommandRegs {
|
|
||||||
icoi: Mmio<u32>,
|
|
||||||
irii: Mmio<u32>,
|
|
||||||
ics: Mmio<u16>,
|
|
||||||
rsvd7: [Mmio<u8>; 6],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ImmediateCommand {
|
|
||||||
regs: &'static mut ImmediateCommandRegs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImmediateCommand {
|
|
||||||
pub fn new(regs_addr: usize) -> ImmediateCommand {
|
|
||||||
unsafe {
|
|
||||||
ImmediateCommand {
|
|
||||||
regs: &mut *(regs_addr as *mut ImmediateCommandRegs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd(&mut self, cmd: u32) -> Result<u64> {
|
|
||||||
{
|
|
||||||
// wait for ready
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while self.regs.ics.readf(ICB) {
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on immediate command");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write command
|
|
||||||
self.regs.icoi.write(cmd);
|
|
||||||
|
|
||||||
// set ICB bit to send command
|
|
||||||
self.regs.ics.writef(ICB, true);
|
|
||||||
|
|
||||||
{
|
|
||||||
// wait for IRV bit to be set to indicate a response is latched
|
|
||||||
let timeout = Timeout::from_secs(1);
|
|
||||||
while !self.regs.ics.readf(IRV) {
|
|
||||||
timeout.run().map_err(|()| {
|
|
||||||
log::error!("timeout on immediate response");
|
|
||||||
Error::new(EIO)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// read the result register twice, total of 8 bytes
|
|
||||||
// highest 4 will most likely be zeros (so I've heard)
|
|
||||||
let mut res: u64 = self.regs.irii.read() as u64;
|
|
||||||
res |= (self.regs.irii.read() as u64) << 32;
|
|
||||||
|
|
||||||
// clear the bit so we know when the next response comes
|
|
||||||
self.regs.ics.writef(IRV, false);
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CommandBuffer {
|
|
||||||
// regs: &'static mut CommandBufferRegs,
|
|
||||||
corb: Corb,
|
|
||||||
rirb: Rirb,
|
|
||||||
icmd: ImmediateCommand,
|
|
||||||
|
|
||||||
use_immediate_cmd: bool,
|
|
||||||
mem: Dma<[u8; 0x1000]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandBuffer {
|
|
||||||
pub fn new(regs_addr: usize, mut cmd_buff: Dma<[u8; 0x1000]>) -> CommandBuffer {
|
|
||||||
let corb = Corb::new(
|
|
||||||
regs_addr + CORB_OFFSET,
|
|
||||||
cmd_buff.physical(),
|
|
||||||
cmd_buff.as_mut_ptr().cast(),
|
|
||||||
);
|
|
||||||
let rirb = Rirb::new(
|
|
||||||
regs_addr + RIRB_OFFSET,
|
|
||||||
cmd_buff.physical() + CORB_BUFF_MAX_SIZE,
|
|
||||||
cmd_buff
|
|
||||||
.as_mut_ptr()
|
|
||||||
.cast::<u8>()
|
|
||||||
.wrapping_add(CORB_BUFF_MAX_SIZE)
|
|
||||||
.cast(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let icmd = ImmediateCommand::new(regs_addr + ICMD_OFFSET);
|
|
||||||
|
|
||||||
let cmdbuff = CommandBuffer {
|
|
||||||
corb,
|
|
||||||
rirb,
|
|
||||||
icmd,
|
|
||||||
|
|
||||||
use_immediate_cmd: false,
|
|
||||||
|
|
||||||
mem: cmd_buff,
|
|
||||||
};
|
|
||||||
|
|
||||||
cmdbuff
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(&mut self, use_imm_cmds: bool) -> Result<()> {
|
|
||||||
self.corb.init()?;
|
|
||||||
self.rirb.init()?;
|
|
||||||
self.set_use_imm_cmds(use_imm_cmds)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&mut self) -> Result<()> {
|
|
||||||
self.corb.stop()?;
|
|
||||||
self.rirb.stop()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd12(&mut self, addr: WidgetAddr, command: u32, data: u8) -> Result<u64> {
|
|
||||||
let mut ncmd: u32 = 0;
|
|
||||||
|
|
||||||
ncmd |= (addr.0 as u32 & 0x00F) << 28;
|
|
||||||
ncmd |= (addr.1 as u32 & 0x0FF) << 20;
|
|
||||||
ncmd |= (command & 0xFFF) << 8;
|
|
||||||
ncmd |= (data as u32 & 0x0FF) << 0;
|
|
||||||
self.cmd(ncmd)
|
|
||||||
}
|
|
||||||
pub fn cmd4(&mut self, addr: WidgetAddr, command: u32, data: u16) -> Result<u64> {
|
|
||||||
let mut ncmd: u32 = 0;
|
|
||||||
|
|
||||||
ncmd |= (addr.0 as u32 & 0x000F) << 28;
|
|
||||||
ncmd |= (addr.1 as u32 & 0x00FF) << 20;
|
|
||||||
ncmd |= (command & 0x000F) << 16;
|
|
||||||
ncmd |= (data as u32 & 0xFFFF) << 0;
|
|
||||||
self.cmd(ncmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd(&mut self, cmd: u32) -> Result<u64> {
|
|
||||||
if self.use_immediate_cmd {
|
|
||||||
self.cmd_imm(cmd)
|
|
||||||
} else {
|
|
||||||
self.cmd_buff(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_imm(&mut self, cmd: u32) -> Result<u64> {
|
|
||||||
self.icmd.cmd(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cmd_buff(&mut self, cmd: u32) -> Result<u64> {
|
|
||||||
self.corb.send_command(cmd)?;
|
|
||||||
self.rirb.read_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_use_imm_cmds(&mut self, use_imm: bool) -> Result<()> {
|
|
||||||
self.use_immediate_cmd = use_imm;
|
|
||||||
|
|
||||||
if self.use_immediate_cmd {
|
|
||||||
self.corb.stop()?;
|
|
||||||
self.rirb.stop()?;
|
|
||||||
} else {
|
|
||||||
self.corb.start();
|
|
||||||
self.rirb.start();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::mem::transmute;
|
|
||||||
|
|
||||||
pub type HDANodeAddr = u16;
|
|
||||||
pub type HDACodecAddr = u8;
|
|
||||||
|
|
||||||
pub type NodeAddr = u16;
|
|
||||||
pub type CodecAddr = u8;
|
|
||||||
|
|
||||||
pub type WidgetAddr = (CodecAddr, NodeAddr);
|
|
||||||
/*
|
|
||||||
impl fmt::Display for WidgetAddr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{:01X}:{:02X}\n", self.0, self.1)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum HDAWidgetType {
|
|
||||||
AudioOutput = 0x0,
|
|
||||||
AudioInput = 0x1,
|
|
||||||
AudioMixer = 0x2,
|
|
||||||
AudioSelector = 0x3,
|
|
||||||
PinComplex = 0x4,
|
|
||||||
Power = 0x5,
|
|
||||||
VolumeKnob = 0x6,
|
|
||||||
BeepGenerator = 0x7,
|
|
||||||
|
|
||||||
VendorDefined = 0xf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for HDAWidgetType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum DefaultDevice {
|
|
||||||
LineOut = 0x0,
|
|
||||||
Speaker = 0x1,
|
|
||||||
HPOut = 0x2,
|
|
||||||
CD = 0x3,
|
|
||||||
SPDIF = 0x4,
|
|
||||||
DigitalOtherOut = 0x5,
|
|
||||||
ModemLineSide = 0x6,
|
|
||||||
ModemHandsetSide = 0x7,
|
|
||||||
LineIn = 0x8,
|
|
||||||
AUX = 0x9,
|
|
||||||
MicIn = 0xA,
|
|
||||||
Telephony = 0xB,
|
|
||||||
SPDIFIn = 0xC,
|
|
||||||
DigitalOtherIn = 0xD,
|
|
||||||
Reserved = 0xE,
|
|
||||||
Other = 0xF,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum PortConnectivity {
|
|
||||||
ConnectedToJack = 0x0,
|
|
||||||
NoPhysicalConnection = 0x1,
|
|
||||||
FixedFunction = 0x2,
|
|
||||||
JackAndInternal = 0x3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum GrossLocation {
|
|
||||||
ExternalOnPrimary = 0x0,
|
|
||||||
Internal = 0x1,
|
|
||||||
SeperateChasis = 0x2,
|
|
||||||
Other = 0x3,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum GeometricLocation {
|
|
||||||
NA = 0x0,
|
|
||||||
Rear = 0x1,
|
|
||||||
Front = 0x2,
|
|
||||||
Left = 0x3,
|
|
||||||
Right = 0x4,
|
|
||||||
Top = 0x5,
|
|
||||||
Bottom = 0x6,
|
|
||||||
Special1 = 0x7,
|
|
||||||
Special2 = 0x8,
|
|
||||||
Special3 = 0x9,
|
|
||||||
Resvd1 = 0xA,
|
|
||||||
Resvd2 = 0xB,
|
|
||||||
Resvd3 = 0xC,
|
|
||||||
Resvd4 = 0xD,
|
|
||||||
Resvd5 = 0xE,
|
|
||||||
Resvd6 = 0xF,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Color {
|
|
||||||
Unknown = 0x0,
|
|
||||||
Black = 0x1,
|
|
||||||
Grey = 0x2,
|
|
||||||
Blue = 0x3,
|
|
||||||
Green = 0x4,
|
|
||||||
Red = 0x5,
|
|
||||||
Orange = 0x6,
|
|
||||||
Yellow = 0x7,
|
|
||||||
Purple = 0x8,
|
|
||||||
Pink = 0x9,
|
|
||||||
Resvd1 = 0xA,
|
|
||||||
Resvd2 = 0xB,
|
|
||||||
Resvd3 = 0xC,
|
|
||||||
Resvd4 = 0xD,
|
|
||||||
White = 0xE,
|
|
||||||
Other = 0xF,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ConfigurationDefault {
|
|
||||||
value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigurationDefault {
|
|
||||||
pub fn from_u32(value: u32) -> ConfigurationDefault {
|
|
||||||
ConfigurationDefault { value: value }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color(&self) -> Color {
|
|
||||||
unsafe { transmute(((self.value >> 12) & 0xF) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_device(&self) -> DefaultDevice {
|
|
||||||
unsafe { transmute(((self.value >> 20) & 0xF) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn port_connectivity(&self) -> PortConnectivity {
|
|
||||||
unsafe { transmute(((self.value >> 30) & 0x3) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gross_location(&self) -> GrossLocation {
|
|
||||||
unsafe { transmute(((self.value >> 28) & 0x3) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn geometric_location(&self) -> GeometricLocation {
|
|
||||||
unsafe { transmute(((self.value >> 24) & 0x7) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_output(&self) -> bool {
|
|
||||||
match self.default_device() {
|
|
||||||
DefaultDevice::LineOut
|
|
||||||
| DefaultDevice::Speaker
|
|
||||||
| DefaultDevice::HPOut
|
|
||||||
| DefaultDevice::CD
|
|
||||||
| DefaultDevice::SPDIF
|
|
||||||
| DefaultDevice::DigitalOtherOut
|
|
||||||
| DefaultDevice::ModemLineSide => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_input(&self) -> bool {
|
|
||||||
match self.default_device() {
|
|
||||||
DefaultDevice::ModemHandsetSide
|
|
||||||
| DefaultDevice::LineIn
|
|
||||||
| DefaultDevice::AUX
|
|
||||||
| DefaultDevice::MicIn
|
|
||||||
| DefaultDevice::Telephony
|
|
||||||
| DefaultDevice::SPDIFIn
|
|
||||||
| DefaultDevice::DigitalOtherIn => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sequence(&self) -> u8 {
|
|
||||||
(self.value & 0xF) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn default_association(&self) -> u8 {
|
|
||||||
((self.value >> 4) & 0xF) as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ConfigurationDefault {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{:?} {:?} {:?} {:?}",
|
|
||||||
self.default_device(),
|
|
||||||
self.color(),
|
|
||||||
self.gross_location(),
|
|
||||||
self.geometric_location()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
pub mod cmdbuff;
|
|
||||||
pub mod common;
|
|
||||||
pub mod device;
|
|
||||||
pub mod node;
|
|
||||||
pub mod stream;
|
|
||||||
|
|
||||||
pub use self::node::*;
|
|
||||||
pub use self::stream::*;
|
|
||||||
|
|
||||||
pub use self::cmdbuff::*;
|
|
||||||
pub use self::device::IntelHDA;
|
|
||||||
pub use self::stream::BitsPerSample;
|
|
||||||
pub use self::stream::BufferDescriptorListEntry;
|
|
||||||
pub use self::stream::StreamBuffer;
|
|
||||||
pub use self::stream::StreamDescriptorRegs;
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
use super::common::*;
|
|
||||||
use std::{fmt, mem};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct HDANode {
|
|
||||||
pub addr: WidgetAddr,
|
|
||||||
|
|
||||||
// 0x4
|
|
||||||
pub subnode_count: u16,
|
|
||||||
pub subnode_start: u16,
|
|
||||||
|
|
||||||
// 0x5
|
|
||||||
pub function_group_type: u8,
|
|
||||||
|
|
||||||
// 0x9
|
|
||||||
pub capabilities: u32,
|
|
||||||
|
|
||||||
// 0xE
|
|
||||||
pub conn_list_len: u8,
|
|
||||||
|
|
||||||
pub connections: Vec<WidgetAddr>,
|
|
||||||
|
|
||||||
pub connection_default: u8,
|
|
||||||
|
|
||||||
pub is_widget: bool,
|
|
||||||
|
|
||||||
pub config_default: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HDANode {
|
|
||||||
pub fn new() -> HDANode {
|
|
||||||
HDANode {
|
|
||||||
addr: (0, 0),
|
|
||||||
subnode_count: 0,
|
|
||||||
subnode_start: 0,
|
|
||||||
function_group_type: 0,
|
|
||||||
capabilities: 0,
|
|
||||||
conn_list_len: 0,
|
|
||||||
|
|
||||||
config_default: 0,
|
|
||||||
is_widget: false,
|
|
||||||
connections: Vec::<WidgetAddr>::new(),
|
|
||||||
connection_default: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn widget_type(&self) -> HDAWidgetType {
|
|
||||||
unsafe { mem::transmute(((self.capabilities >> 20) & 0xF) as u8) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn device_default(&self) -> Option<DefaultDevice> {
|
|
||||||
if self.widget_type() != HDAWidgetType::PinComplex {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { mem::transmute(((self.config_default >> 20) & 0xF) as u8) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configuration_default(&self) -> ConfigurationDefault {
|
|
||||||
ConfigurationDefault::from_u32(self.config_default)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr(&self) -> WidgetAddr {
|
|
||||||
self.addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for HDANode {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
if self.addr == (0, 0) {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Addr: {:02X}:{:02X}, Root Node.",
|
|
||||||
self.addr.0, self.addr.1
|
|
||||||
)
|
|
||||||
} else if self.is_widget {
|
|
||||||
match self.widget_type() {
|
|
||||||
HDAWidgetType::PinComplex => write!(
|
|
||||||
f,
|
|
||||||
"Addr: {:02X}:{:02X}, Type: {:?}: {:?}, Inputs: {}/{}: {:X?}.",
|
|
||||||
self.addr.0,
|
|
||||||
self.addr.1,
|
|
||||||
self.widget_type(),
|
|
||||||
self.device_default().unwrap(),
|
|
||||||
self.connection_default,
|
|
||||||
self.conn_list_len,
|
|
||||||
self.connections
|
|
||||||
),
|
|
||||||
_ => write!(
|
|
||||||
f,
|
|
||||||
"Addr: {:02X}:{:02X}, Type: {:?}, Inputs: {}/{}: {:X?}.",
|
|
||||||
self.addr.0,
|
|
||||||
self.addr.1,
|
|
||||||
self.widget_type(),
|
|
||||||
self.connection_default,
|
|
||||||
self.conn_list_len,
|
|
||||||
self.connections
|
|
||||||
),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Addr: {:02X}:{:02X}, AFG: {}, Widget count {}.",
|
|
||||||
self.addr.0, self.addr.1, self.function_group_type, self.subnode_count
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
use common::dma::Dma;
|
|
||||||
use common::io::{Io, Mmio};
|
|
||||||
use std::cmp::min;
|
|
||||||
use std::ptr::copy_nonoverlapping;
|
|
||||||
use std::result;
|
|
||||||
use syscall::error::{Error, Result, EIO};
|
|
||||||
use syscall::PAGE_SIZE;
|
|
||||||
|
|
||||||
extern crate syscall;
|
|
||||||
|
|
||||||
pub enum BaseRate {
|
|
||||||
BR44_1,
|
|
||||||
BR48,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SampleRate {
|
|
||||||
base: BaseRate,
|
|
||||||
mult: u16,
|
|
||||||
div: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
use self::BaseRate::{BR44_1, BR48};
|
|
||||||
|
|
||||||
pub const SR_8: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 1,
|
|
||||||
div: 6,
|
|
||||||
};
|
|
||||||
pub const SR_11_025: SampleRate = SampleRate {
|
|
||||||
base: BR44_1,
|
|
||||||
mult: 1,
|
|
||||||
div: 4,
|
|
||||||
};
|
|
||||||
pub const SR_16: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 1,
|
|
||||||
div: 3,
|
|
||||||
};
|
|
||||||
pub const SR_22_05: SampleRate = SampleRate {
|
|
||||||
base: BR44_1,
|
|
||||||
mult: 1,
|
|
||||||
div: 2,
|
|
||||||
};
|
|
||||||
pub const SR_32: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 2,
|
|
||||||
div: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SR_44_1: SampleRate = SampleRate {
|
|
||||||
base: BR44_1,
|
|
||||||
mult: 1,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
pub const SR_48: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 1,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
pub const SR_88_1: SampleRate = SampleRate {
|
|
||||||
base: BR44_1,
|
|
||||||
mult: 2,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
pub const SR_96: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 2,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
pub const SR_176_4: SampleRate = SampleRate {
|
|
||||||
base: BR44_1,
|
|
||||||
mult: 4,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
pub const SR_192: SampleRate = SampleRate {
|
|
||||||
base: BR48,
|
|
||||||
mult: 4,
|
|
||||||
div: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum BitsPerSample {
|
|
||||||
Bits8 = 0,
|
|
||||||
Bits16 = 1,
|
|
||||||
Bits20 = 2,
|
|
||||||
Bits24 = 3,
|
|
||||||
Bits32 = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_to_u16(sr: &SampleRate, bps: BitsPerSample, channels: u8) -> u16 {
|
|
||||||
// 3.3.41
|
|
||||||
|
|
||||||
let base: u16 = match sr.base {
|
|
||||||
BaseRate::BR44_1 => 1 << 14,
|
|
||||||
BaseRate::BR48 => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mult = ((sr.mult - 1) & 0x7) << 11;
|
|
||||||
|
|
||||||
let div = ((sr.div - 1) & 0x7) << 8;
|
|
||||||
|
|
||||||
let bits = (bps as u16) << 4;
|
|
||||||
|
|
||||||
let chan = ((channels - 1) & 0xF) as u16;
|
|
||||||
|
|
||||||
let val: u16 = base | mult | div | bits | chan;
|
|
||||||
|
|
||||||
val
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct StreamDescriptorRegs {
|
|
||||||
ctrl_lo: Mmio<u16>,
|
|
||||||
ctrl_hi: Mmio<u8>,
|
|
||||||
status: Mmio<u8>,
|
|
||||||
link_pos: Mmio<u32>,
|
|
||||||
buff_length: Mmio<u32>,
|
|
||||||
last_valid_index: Mmio<u16>,
|
|
||||||
resv1: Mmio<u16>,
|
|
||||||
fifo_size_: Mmio<u16>,
|
|
||||||
format: Mmio<u16>,
|
|
||||||
resv2: Mmio<u32>,
|
|
||||||
buff_desc_list_lo: Mmio<u32>,
|
|
||||||
buff_desc_list_hi: Mmio<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamDescriptorRegs {
|
|
||||||
pub fn status(&self) -> u8 {
|
|
||||||
self.status.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_status(&mut self, status: u8) {
|
|
||||||
self.status.write(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn control(&self) -> u32 {
|
|
||||||
let mut ctrl = self.ctrl_lo.read() as u32;
|
|
||||||
ctrl |= (self.ctrl_hi.read() as u32) << 16;
|
|
||||||
ctrl
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_control(&mut self, control: u32) {
|
|
||||||
self.ctrl_lo.write((control & 0xFFFF) as u16);
|
|
||||||
self.ctrl_hi.write(((control >> 16) & 0xFF) as u8);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_pcm_format(&mut self, sr: &SampleRate, bps: BitsPerSample, channels: u8) {
|
|
||||||
// 3.3.41
|
|
||||||
|
|
||||||
let val = format_to_u16(sr, bps, channels);
|
|
||||||
self.format.write(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fifo_size(&self) -> u16 {
|
|
||||||
self.fifo_size_.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cyclic_buffer_length(&mut self, length: u32) {
|
|
||||||
self.buff_length.write(length);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cyclic_buffer_length(&self) -> u32 {
|
|
||||||
self.buff_length.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
|
||||||
let val = self.control() | (1 << 1);
|
|
||||||
self.set_control(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
|
||||||
let val = self.control() & !(1 << 1);
|
|
||||||
self.set_control(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream_number(&self) -> u8 {
|
|
||||||
((self.control() >> 20) & 0xF) as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_stream_number(&mut self, stream_number: u8) {
|
|
||||||
let val = (self.control() & 0x00FFFF) | (((stream_number & 0xF) as u32) << 20);
|
|
||||||
self.set_control(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_address(&mut self, addr: usize) {
|
|
||||||
self.buff_desc_list_lo.write((addr & 0xFFFFFFFF) as u32);
|
|
||||||
self.buff_desc_list_hi
|
|
||||||
.write((((addr as u64) >> 32) & 0xFFFFFFFF) as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_last_valid_index(&mut self, index: u16) {
|
|
||||||
self.last_valid_index.write(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_position(&self) -> u32 {
|
|
||||||
self.link_pos.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_interrupt_on_completion(&mut self, enable: bool) {
|
|
||||||
let mut ctrl = self.control();
|
|
||||||
if enable {
|
|
||||||
ctrl |= 1 << 2;
|
|
||||||
} else {
|
|
||||||
ctrl &= !(1 << 2);
|
|
||||||
}
|
|
||||||
self.set_control(ctrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer_complete(&self) -> bool {
|
|
||||||
self.status.readf(1 << 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_interrupts(&mut self) {
|
|
||||||
self.status.write(0x7 << 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get sample size in bytes
|
|
||||||
pub fn sample_size(&self) -> usize {
|
|
||||||
let format = self.format.read();
|
|
||||||
let chan = (format & 0xF) as usize;
|
|
||||||
let bits = ((format >> 4) & 0xF) as usize;
|
|
||||||
match bits {
|
|
||||||
0 => 1 * (chan + 1),
|
|
||||||
1 => 2 * (chan + 1),
|
|
||||||
_ => 4 * (chan + 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OutputStream {
|
|
||||||
buff: StreamBuffer,
|
|
||||||
|
|
||||||
desc_regs: &'static mut StreamDescriptorRegs,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputStream {
|
|
||||||
pub fn new(
|
|
||||||
block_count: usize,
|
|
||||||
block_length: usize,
|
|
||||||
regs: &'static mut StreamDescriptorRegs,
|
|
||||||
) -> OutputStream {
|
|
||||||
OutputStream {
|
|
||||||
buff: StreamBuffer::new(block_length, block_count).unwrap(),
|
|
||||||
|
|
||||||
desc_regs: regs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_block(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
self.buff.write_block(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_size(&self) -> usize {
|
|
||||||
self.buff.block_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_count(&self) -> usize {
|
|
||||||
self.buff.block_count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_block(&self) -> usize {
|
|
||||||
self.buff.current_block()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr(&self) -> usize {
|
|
||||||
self.buff.addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn phys(&self) -> usize {
|
|
||||||
self.buff.phys()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct BufferDescriptorListEntry {
|
|
||||||
addr_low: Mmio<u32>,
|
|
||||||
addr_high: Mmio<u32>,
|
|
||||||
len: Mmio<u32>,
|
|
||||||
ioc_resv: Mmio<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BufferDescriptorListEntry {
|
|
||||||
pub fn address(&self) -> u64 {
|
|
||||||
(self.addr_low.read() as u64) | ((self.addr_high.read() as u64) << 32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_address(&mut self, addr: u64) {
|
|
||||||
self.addr_low.write(addr as u32);
|
|
||||||
self.addr_high.write((addr >> 32) as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn length(&self) -> u32 {
|
|
||||||
self.len.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_length(&mut self, length: u32) {
|
|
||||||
self.len.write(length)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interrupt_on_completion(&self) -> bool {
|
|
||||||
(self.ioc_resv.read() & 0x1) == 0x1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_interrupt_on_complete(&mut self, ioc: bool) {
|
|
||||||
self.ioc_resv.writef(1, ioc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct StreamBuffer {
|
|
||||||
mem: Dma<[u8]>,
|
|
||||||
|
|
||||||
block_cnt: usize,
|
|
||||||
block_len: usize,
|
|
||||||
|
|
||||||
cur_pos: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamBuffer {
|
|
||||||
pub fn new(
|
|
||||||
block_length: usize,
|
|
||||||
block_count: usize,
|
|
||||||
) -> result::Result<StreamBuffer, &'static str> {
|
|
||||||
let page_aligned_size = (block_length * block_count).next_multiple_of(PAGE_SIZE);
|
|
||||||
let mem = unsafe {
|
|
||||||
Dma::zeroed_slice(page_aligned_size)
|
|
||||||
.map_err(|_| "Could not allocate physical memory for buffer.")?
|
|
||||||
.assume_init()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(StreamBuffer {
|
|
||||||
mem,
|
|
||||||
block_len: block_length,
|
|
||||||
block_cnt: block_count,
|
|
||||||
cur_pos: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn length(&self) -> usize {
|
|
||||||
self.block_len * self.block_cnt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr(&self) -> usize {
|
|
||||||
self.mem.as_ptr() as usize
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn phys(&self) -> usize {
|
|
||||||
self.mem.physical()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_size(&self) -> usize {
|
|
||||||
self.block_len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_count(&self) -> usize {
|
|
||||||
self.block_cnt
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_block(&self) -> usize {
|
|
||||||
self.cur_pos
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_block(&mut self, buf: &[u8]) -> Result<usize> {
|
|
||||||
if buf.len() != self.block_size() {
|
|
||||||
return Err(Error::new(EIO));
|
|
||||||
}
|
|
||||||
let len = min(self.block_size(), buf.len());
|
|
||||||
|
|
||||||
//log::trace!("Phys: {:X} Virt: {:X} Offset: {:X} Len: {:X}", self.phys(), self.addr(), self.current_block() * self.block_size(), len);
|
|
||||||
unsafe {
|
|
||||||
copy_nonoverlapping(
|
|
||||||
buf.as_ptr(),
|
|
||||||
(self.addr() + self.current_block() * self.block_size()) as *mut u8,
|
|
||||||
len,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cur_pos += 1;
|
|
||||||
self.cur_pos %= self.block_count();
|
|
||||||
|
|
||||||
Ok(len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for StreamBuffer {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
log::debug!("IHDA: Deallocating buffer.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
use redox_scheme::scheme::register_sync_scheme;
|
|
||||||
use redox_scheme::Socket;
|
|
||||||
use scheme_utils::ReadinessBased;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::os::unix::io::AsRawFd;
|
|
||||||
use std::usize;
|
|
||||||
|
|
||||||
use event::{user_data, EventQueue};
|
|
||||||
use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
|
||||||
use pcid_interface::PciFunctionHandle;
|
|
||||||
|
|
||||||
pub mod hda;
|
|
||||||
|
|
||||||
/*
|
|
||||||
VEND:PROD
|
|
||||||
Virtualbox 8086:2668
|
|
||||||
QEMU ICH9 8086:293E
|
|
||||||
82801H ICH8 8086:284B
|
|
||||||
*/
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
pcid_interface::pci_daemon(daemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
|
||||||
let pci_config = pcid_handle.config();
|
|
||||||
|
|
||||||
let mut name = pci_config.func.name();
|
|
||||||
name.push_str("_ihda");
|
|
||||||
|
|
||||||
common::setup_logging(
|
|
||||||
"audio",
|
|
||||||
"pci",
|
|
||||||
&name,
|
|
||||||
common::output_level(),
|
|
||||||
common::file_level(),
|
|
||||||
);
|
|
||||||
|
|
||||||
log::info!("IHDA {}", pci_config.func.display());
|
|
||||||
|
|
||||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
|
||||||
|
|
||||||
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
|
|
||||||
|
|
||||||
{
|
|
||||||
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
|
|
||||||
| (pci_config.func.full_device_id.device_id as u32);
|
|
||||||
|
|
||||||
user_data! {
|
|
||||||
enum Source {
|
|
||||||
Irq,
|
|
||||||
Scheme,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_queue =
|
|
||||||
EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
|
|
||||||
let socket = Socket::nonblock().expect("ihdad: failed to create socket");
|
|
||||||
let mut device = unsafe {
|
|
||||||
hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
|
|
||||||
};
|
|
||||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
|
||||||
|
|
||||||
register_sync_scheme(&socket, "audiohw", &mut device)
|
|
||||||
.expect("ihdad: failed to register audiohw scheme to namespace");
|
|
||||||
daemon.ready();
|
|
||||||
|
|
||||||
event_queue
|
|
||||||
.subscribe(
|
|
||||||
socket.inner().raw(),
|
|
||||||
Source::Scheme,
|
|
||||||
event::EventFlags::READ,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
event_queue
|
|
||||||
.subscribe(
|
|
||||||
irq_file.irq_handle().as_raw_fd() as usize,
|
|
||||||
Source::Irq,
|
|
||||||
event::EventFlags::READ,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
libredox::call::setrens(0, 0).expect("ihdad: failed to enter null namespace");
|
|
||||||
|
|
||||||
let all = [Source::Irq, Source::Scheme];
|
|
||||||
|
|
||||||
for event in all
|
|
||||||
.into_iter()
|
|
||||||
.chain(event_queue.map(|e| e.expect("failed to get next event").user_data))
|
|
||||||
{
|
|
||||||
match event {
|
|
||||||
Source::Irq => {
|
|
||||||
let mut irq = [0; 8];
|
|
||||||
irq_file.irq_handle().read(&mut irq).unwrap();
|
|
||||||
|
|
||||||
if !device.irq() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
irq_file.irq_handle().write(&mut irq).unwrap();
|
|
||||||
|
|
||||||
readiness_based
|
|
||||||
.poll_all_requests(&mut device)
|
|
||||||
.expect("ihdad: failed to poll requests");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("ihdad: failed to write to socket");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let next_read = device_irq.next_read();
|
|
||||||
if next_read > 0 {
|
|
||||||
return Ok(Some(next_read));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
Source::Scheme => {
|
|
||||||
readiness_based
|
|
||||||
.read_and_process_requests(&mut device)
|
|
||||||
.expect("ihdad: failed to read from socket");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("ihdad: failed to write to socket");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let next_read = device.borrow().next_read();
|
|
||||||
if next_read > 0 {
|
|
||||||
return Ok(Some(next_read));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "sb16d"
|
|
||||||
description = "Sound Blaster sound card driver"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bitflags.workspace = true
|
|
||||||
common = { path = "../../common" }
|
|
||||||
libredox.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
daemon = { path = "../../../daemon" }
|
|
||||||
redox_event.workspace = true
|
|
||||||
redox_syscall.workspace = true
|
|
||||||
spin.workspace = true
|
|
||||||
redox-scheme.workspace = true
|
|
||||||
scheme-utils = { path = "../../../scheme-utils" }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
use std::{thread, time};
|
|
||||||
|
|
||||||
use common::io::{Io, Pio, ReadOnly, WriteOnly};
|
|
||||||
|
|
||||||
use redox_scheme::scheme::SchemeSync;
|
|
||||||
use redox_scheme::CallerCtx;
|
|
||||||
use redox_scheme::OpenResult;
|
|
||||||
use scheme_utils::{FpathWriter, HandleMap};
|
|
||||||
use syscall::error::{Error, Result, EACCES, EBADF, ENODEV};
|
|
||||||
use syscall::schemev2::NewFdFlags;
|
|
||||||
|
|
||||||
use spin::Mutex;
|
|
||||||
|
|
||||||
const NUM_SUB_BUFFS: usize = 32;
|
|
||||||
const SUB_BUFF_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
enum Handle {
|
|
||||||
Todo,
|
|
||||||
SchemeRoot,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct Sb16 {
|
|
||||||
handles: Mutex<HandleMap<Handle>>,
|
|
||||||
pub(crate) irqs: Vec<u8>,
|
|
||||||
dmas: Vec<u8>,
|
|
||||||
// Regs
|
|
||||||
/* 0x04 */ mixer_addr: WriteOnly<Pio<u8>>,
|
|
||||||
/* 0x05 */ mixer_data: Pio<u8>,
|
|
||||||
/* 0x06 */ dsp_reset: WriteOnly<Pio<u8>>,
|
|
||||||
/* 0x0A */ dsp_read_data: ReadOnly<Pio<u8>>,
|
|
||||||
/* 0x0C */ dsp_write_data: WriteOnly<Pio<u8>>,
|
|
||||||
/* 0x0C */ dsp_write_status: ReadOnly<Pio<u8>>,
|
|
||||||
/* 0x0E */ dsp_read_status: ReadOnly<Pio<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sb16 {
|
|
||||||
pub unsafe fn new(addr: u16) -> Result<Self> {
|
|
||||||
let mut module = Sb16 {
|
|
||||||
handles: Mutex::new(HandleMap::new()),
|
|
||||||
irqs: Vec::new(),
|
|
||||||
dmas: Vec::new(),
|
|
||||||
// Regs
|
|
||||||
mixer_addr: WriteOnly::new(Pio::new(addr + 0x04)),
|
|
||||||
mixer_data: Pio::new(addr + 0x05),
|
|
||||||
dsp_reset: WriteOnly::new(Pio::new(addr + 0x06)),
|
|
||||||
dsp_read_data: ReadOnly::new(Pio::new(addr + 0x0A)),
|
|
||||||
dsp_write_data: WriteOnly::new(Pio::new(addr + 0x0C)),
|
|
||||||
dsp_write_status: ReadOnly::new(Pio::new(addr + 0x0C)),
|
|
||||||
dsp_read_status: ReadOnly::new(Pio::new(addr + 0x0E)),
|
|
||||||
};
|
|
||||||
|
|
||||||
module.init()?;
|
|
||||||
|
|
||||||
Ok(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mixer_read(&mut self, index: u8) -> u8 {
|
|
||||||
self.mixer_addr.write(index);
|
|
||||||
self.mixer_data.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mixer_write(&mut self, index: u8, value: u8) {
|
|
||||||
self.mixer_addr.write(index);
|
|
||||||
self.mixer_data.write(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dsp_read(&mut self) -> Result<u8> {
|
|
||||||
// Bit 7 must be 1 before data can be sent
|
|
||||||
while !self.dsp_read_status.readf(1 << 7) {
|
|
||||||
//TODO: timeout!
|
|
||||||
std::thread::yield_now();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.dsp_read_data.read())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dsp_write(&mut self, value: u8) -> Result<()> {
|
|
||||||
// Bit 7 must be 0 before data can be sent
|
|
||||||
while self.dsp_write_status.readf(1 << 7) {
|
|
||||||
//TODO: timeout!
|
|
||||||
std::thread::yield_now();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dsp_write_data.write(value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(&mut self) -> Result<()> {
|
|
||||||
// Perform DSP reset
|
|
||||||
{
|
|
||||||
// Write 1 to reset port
|
|
||||||
self.dsp_reset.write(1);
|
|
||||||
|
|
||||||
// Wait 3us
|
|
||||||
thread::sleep(time::Duration::from_micros(3));
|
|
||||||
|
|
||||||
// Write 0 to reset port
|
|
||||||
self.dsp_reset.write(0);
|
|
||||||
|
|
||||||
//TODO: Wait for ready byte (0xAA) using read status
|
|
||||||
thread::sleep(time::Duration::from_micros(100));
|
|
||||||
|
|
||||||
let ready = self.dsp_read()?;
|
|
||||||
if ready != 0xAA {
|
|
||||||
log::error!("ready byte was 0x{:02X} instead of 0xAA", ready);
|
|
||||||
return Err(Error::new(ENODEV));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read DSP version
|
|
||||||
{
|
|
||||||
self.dsp_write(0xE1)?;
|
|
||||||
|
|
||||||
let major = self.dsp_read()?;
|
|
||||||
let minor = self.dsp_read()?;
|
|
||||||
log::info!("DSP version {}.{:02}", major, minor);
|
|
||||||
|
|
||||||
if major != 4 {
|
|
||||||
log::error!("Unsupported DSP major version {}", major);
|
|
||||||
return Err(Error::new(ENODEV));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get available IRQs and DMAs
|
|
||||||
{
|
|
||||||
self.irqs.clear();
|
|
||||||
let irq_mask = self.mixer_read(0x80);
|
|
||||||
if (irq_mask & (1 << 0)) != 0 {
|
|
||||||
self.irqs.push(2);
|
|
||||||
}
|
|
||||||
if (irq_mask & (1 << 1)) != 0 {
|
|
||||||
self.irqs.push(5);
|
|
||||||
}
|
|
||||||
if (irq_mask & (1 << 2)) != 0 {
|
|
||||||
self.irqs.push(7);
|
|
||||||
}
|
|
||||||
if (irq_mask & (1 << 3)) != 0 {
|
|
||||||
self.irqs.push(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dmas.clear();
|
|
||||||
let dma_mask = self.mixer_read(0x81);
|
|
||||||
if (dma_mask & (1 << 0)) != 0 {
|
|
||||||
self.dmas.push(0);
|
|
||||||
}
|
|
||||||
if (dma_mask & (1 << 1)) != 0 {
|
|
||||||
self.dmas.push(1);
|
|
||||||
}
|
|
||||||
if (dma_mask & (1 << 3)) != 0 {
|
|
||||||
self.dmas.push(3);
|
|
||||||
}
|
|
||||||
if (dma_mask & (1 << 5)) != 0 {
|
|
||||||
self.dmas.push(5);
|
|
||||||
}
|
|
||||||
if (dma_mask & (1 << 6)) != 0 {
|
|
||||||
self.dmas.push(6);
|
|
||||||
}
|
|
||||||
if (dma_mask & (1 << 7)) != 0 {
|
|
||||||
self.dmas.push(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("IRQs {:02X?} DMAs {:02X?}", self.irqs, self.dmas);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set output sample rate to 44100 Hz (Redox OS standard)
|
|
||||||
{
|
|
||||||
let rate = 44100u16;
|
|
||||||
self.dsp_write(0x41)?;
|
|
||||||
self.dsp_write((rate >> 8) as u8)?;
|
|
||||||
self.dsp_write(rate as u8)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn irq(&mut self) -> bool {
|
|
||||||
//TODO
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SchemeSync for Sb16 {
|
|
||||||
fn scheme_root(&mut self) -> Result<usize> {
|
|
||||||
Ok(self.handles.lock().insert(Handle::SchemeRoot))
|
|
||||||
}
|
|
||||||
fn openat(
|
|
||||||
&mut self,
|
|
||||||
dirfd: usize,
|
|
||||||
_path: &str,
|
|
||||||
_flags: usize,
|
|
||||||
_fcntl_flags: u32,
|
|
||||||
ctx: &CallerCtx,
|
|
||||||
) -> Result<OpenResult> {
|
|
||||||
{
|
|
||||||
let handles = self.handles.lock();
|
|
||||||
let handle = handles.get(dirfd)?;
|
|
||||||
if !matches!(handle, Handle::SchemeRoot) {
|
|
||||||
return Err(Error::new(EACCES));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ctx.uid == 0 {
|
|
||||||
let id = self.handles.lock().insert(Handle::Todo);
|
|
||||||
Ok(OpenResult::ThisScheme {
|
|
||||||
number: id,
|
|
||||||
flags: NewFdFlags::empty(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(Error::new(EACCES))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(
|
|
||||||
&mut self,
|
|
||||||
_id: usize,
|
|
||||||
_buf: &[u8],
|
|
||||||
_offset: u64,
|
|
||||||
_flags: u32,
|
|
||||||
_ctx: &CallerCtx,
|
|
||||||
) -> Result<usize> {
|
|
||||||
//TODO
|
|
||||||
Err(Error::new(EBADF))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fpath(&mut self, _id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
|
||||||
FpathWriter::with(buf, "audiohw", |_| Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_close(&mut self, id: usize) {
|
|
||||||
self.handles.lock().remove(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
use libredox::{flag, Fd};
|
|
||||||
use redox_scheme::scheme::register_sync_scheme;
|
|
||||||
use redox_scheme::Socket;
|
|
||||||
use scheme_utils::ReadinessBased;
|
|
||||||
use std::{env, usize};
|
|
||||||
|
|
||||||
use event::{user_data, EventQueue};
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
pub mod device;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
daemon::Daemon::new(daemon);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
|
||||||
let mut args = env::args().skip(1);
|
|
||||||
|
|
||||||
let addr_str = args.next().unwrap_or("220".to_string());
|
|
||||||
let addr = u16::from_str_radix(&addr_str, 16).expect("sb16: failed to parse address");
|
|
||||||
|
|
||||||
println!(" + sb16 at 0x{:X}\n", addr);
|
|
||||||
|
|
||||||
common::setup_logging(
|
|
||||||
"audio",
|
|
||||||
"pci",
|
|
||||||
"sb16",
|
|
||||||
common::output_level(),
|
|
||||||
common::file_level(),
|
|
||||||
);
|
|
||||||
|
|
||||||
common::acquire_port_io_rights().expect("sb16d: failed to acquire port IO rights");
|
|
||||||
|
|
||||||
let socket = Socket::nonblock().expect("sb16d: failed to create socket");
|
|
||||||
let mut device = unsafe { device::Sb16::new(addr).expect("sb16d: failed to allocate device") };
|
|
||||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
|
||||||
|
|
||||||
//TODO: error on multiple IRQs?
|
|
||||||
let irq_file = match device.irqs.first() {
|
|
||||||
Some(irq) => Fd::open(&format!("/scheme/irq/{}", irq), flag::O_RDWR, 0)
|
|
||||||
.expect("sb16d: failed to open IRQ file"),
|
|
||||||
None => panic!("sb16d: no IRQs found"),
|
|
||||||
};
|
|
||||||
user_data! {
|
|
||||||
enum Source {
|
|
||||||
Irq,
|
|
||||||
Scheme,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let event_queue = EventQueue::<Source>::new().expect("sb16d: Could not create event queue.");
|
|
||||||
event_queue
|
|
||||||
.subscribe(irq_file.raw(), Source::Irq, event::EventFlags::READ)
|
|
||||||
.unwrap();
|
|
||||||
event_queue
|
|
||||||
.subscribe(
|
|
||||||
socket.inner().raw(),
|
|
||||||
Source::Scheme,
|
|
||||||
event::EventFlags::READ,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
register_sync_scheme(&socket, "sb16d", &mut device)
|
|
||||||
.expect("sb16d: failed to register audiohw scheme to namespace");
|
|
||||||
|
|
||||||
daemon.ready();
|
|
||||||
|
|
||||||
libredox::call::setrens(0, 0).expect("sb16d: failed to enter null namespace");
|
|
||||||
|
|
||||||
let all = [Source::Irq, Source::Scheme];
|
|
||||||
|
|
||||||
for event in all
|
|
||||||
.into_iter()
|
|
||||||
.chain(event_queue.map(|e| e.expect("sb16d: failed to get next event").user_data))
|
|
||||||
{
|
|
||||||
match event {
|
|
||||||
Source::Irq => {
|
|
||||||
let mut irq = [0; 8];
|
|
||||||
irq_file.read(&mut irq).unwrap();
|
|
||||||
|
|
||||||
if !device.irq() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
irq_file.write(&mut irq).unwrap();
|
|
||||||
|
|
||||||
readiness_based
|
|
||||||
.poll_all_requests(&mut device)
|
|
||||||
.expect("sb16d: failed to poll requests");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("sb16d: failed to write to socket");
|
|
||||||
|
|
||||||
/*
|
|
||||||
let next_read = device_irq.next_read();
|
|
||||||
if next_read > 0 {
|
|
||||||
return Ok(Some(next_read));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
Source::Scheme => {
|
|
||||||
readiness_based
|
|
||||||
.read_and_process_requests(&mut device)
|
|
||||||
.expect("sb16d: failed to read from socket");
|
|
||||||
readiness_based
|
|
||||||
.write_responses()
|
|
||||||
.expect("sb16d: failed to write to socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
|
||||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "common"
|
|
||||||
description = "Shared driver code library"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
authors = ["4lDO2 <4lDO2@protonmail.com>"]
|
|
||||||
license = "MIT"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
libredox.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
redox_syscall = { workspace = true, features = ["std"] }
|
|
||||||
redox-log.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
use std::mem::{self, size_of, MaybeUninit};
|
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::ptr;
|
|
||||||
use std::sync::LazyLock;
|
|
||||||
|
|
||||||
use libredox::call::MmapArgs;
|
|
||||||
use libredox::{error::Result, flag, Fd};
|
|
||||||
use syscall::PAGE_SIZE;
|
|
||||||
|
|
||||||
use crate::{memory_root_fd, MemoryType, VirtaddrTranslationHandle};
|
|
||||||
|
|
||||||
/// Defines the platform-specific memory type for DMA operations
|
|
||||||
///
|
|
||||||
/// - On x86 systems, DMA uses Write-back memory ([`MemoryType::Writeback`])
|
|
||||||
/// - On aarch64 systems, DMA uses uncacheable memory ([`MemoryType::Uncacheable`])
|
|
||||||
const DMA_MEMTY: MemoryType = {
|
|
||||||
if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
|
|
||||||
// x86 ensures cache coherence with DMA memory
|
|
||||||
MemoryType::Writeback
|
|
||||||
} else if cfg!(target_arch = "aarch64") {
|
|
||||||
// aarch64 currently must map DMA memory without caching to ensure coherence
|
|
||||||
MemoryType::Uncacheable
|
|
||||||
} else if cfg!(target_arch = "riscv64") {
|
|
||||||
// FIXME check this out more
|
|
||||||
MemoryType::Uncacheable
|
|
||||||
} else {
|
|
||||||
panic!("invalid arch")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Returns a file descriptor for zeroized physically-contiguous DMA memory.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// A [Result] containing:
|
|
||||||
/// - '[Ok]' - A [Fd] (file descriptor) to zeroized, physically continuous DMA usable memory
|
|
||||||
/// - '[Err]' - The error returned by the provider of the /scheme/memory/zeroed scheme.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function can return an error in the following case:
|
|
||||||
///
|
|
||||||
/// - The request for the physical memory fails.
|
|
||||||
pub(crate) fn phys_contiguous_fd() -> Result<Fd> {
|
|
||||||
memory_root_fd().openat(
|
|
||||||
&format!("zeroed@{DMA_MEMTY}?phys_contiguous"),
|
|
||||||
flag::O_CLOEXEC,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates a chunk of physical memory for DMA, and then maps it to virtual memory.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// 'length: [usize]' - The length of the memory region. Must be a multiple of [`PAGE_SIZE`]
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// This function returns a [Result] containing the following:
|
|
||||||
/// - A '[Ok]([usize], *[mut] ())' containing a Tuple of the physical address of the region, and a raw pointer to that region in virtual memory.
|
|
||||||
/// - An '[Err]' - containing the error for the operation.
|
|
||||||
///
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// This function asserts if:
|
|
||||||
/// - length is not a multiple of [`PAGE_SIZE`]
|
|
||||||
///
|
|
||||||
/// This function returns an error if:
|
|
||||||
/// - A file descriptor to physically contiguous memory of type [`DMA_MEMTY`] could not be acquired
|
|
||||||
/// - A virtual mapping for the physically contiguous memory could not be created
|
|
||||||
/// - The virtual address returned by the memory manager was invalid.
|
|
||||||
fn alloc_and_map(length: usize, handle: &VirtaddrTranslationHandle) -> Result<(usize, *mut ())> {
|
|
||||||
assert_eq!(length % PAGE_SIZE, 0);
|
|
||||||
unsafe {
|
|
||||||
let fd = phys_contiguous_fd()?;
|
|
||||||
let virt = libredox::call::mmap(MmapArgs {
|
|
||||||
fd: fd.raw(),
|
|
||||||
offset: 0, // ignored
|
|
||||||
addr: core::ptr::null_mut(), // ignored
|
|
||||||
length,
|
|
||||||
flags: flag::MAP_PRIVATE,
|
|
||||||
prot: flag::PROT_READ | flag::PROT_WRITE,
|
|
||||||
})?;
|
|
||||||
let phys = handle.translate(virt as usize)?;
|
|
||||||
for i in 1..length.div_ceil(PAGE_SIZE) {
|
|
||||||
debug_assert_eq!(
|
|
||||||
handle.translate(virt as usize + i * PAGE_SIZE),
|
|
||||||
Ok(phys + i * PAGE_SIZE),
|
|
||||||
"NOT CONTIGUOUS"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok((phys, virt as *mut ()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A safe accessor for DMA memory.
|
|
||||||
pub struct Dma<T: ?Sized> {
|
|
||||||
/// The physical address of the memory
|
|
||||||
phys: usize,
|
|
||||||
/// The page-aligned length of the memory. Will be a multiple of [`PAGE_SIZE`]
|
|
||||||
aligned_len: usize,
|
|
||||||
/// The pointer to the Dma memory in the virtual address space.
|
|
||||||
virt: *mut T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Dma<T> {
|
|
||||||
/// [Dma] constructor that allocates and initializes a region of DMA memory with the page-aligned
|
|
||||||
/// size and initial value of some T
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// 'value: T' - The initial value to write to the allocated region
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// This function returns a [Result] containing the following:
|
|
||||||
///
|
|
||||||
/// - A '[Ok] (`[Dma]<T>`)' containing the initialized region
|
|
||||||
/// - An '[Err]' containing an error.
|
|
||||||
pub fn new(value: T) -> Result<Self> {
|
|
||||||
unsafe {
|
|
||||||
let mut zeroed = Self::zeroed()?;
|
|
||||||
zeroed.as_mut_ptr().write(value);
|
|
||||||
Ok(zeroed.assume_init())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [Dma] constructor that allocates and zeroizes a memory region of the page-aligned size of T
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// This function returns a [Result] containing the following:
|
|
||||||
///
|
|
||||||
/// - A '[Ok] (`[Dma]<[MaybeUninit]<T>>`)' containing the allocated and zeroized memory
|
|
||||||
/// - An '[Err]' containing an error.
|
|
||||||
pub fn zeroed() -> Result<Dma<MaybeUninit<T>>> {
|
|
||||||
let aligned_len = size_of::<T>().next_multiple_of(PAGE_SIZE);
|
|
||||||
let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?;
|
|
||||||
Ok(Dma {
|
|
||||||
phys,
|
|
||||||
virt: virt.cast(),
|
|
||||||
aligned_len,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Dma<MaybeUninit<T>> {
|
|
||||||
/// Assumes that possibly uninitialized DMA memory has been initialized, and returns a new
|
|
||||||
/// instance of an object of type `[Dma]<T>`.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// - `[Dma]<T>` - The original structure without the [`MaybeUninit`] wrapper around its contents.
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
/// - This is unsafe because it assumes that the memory stored within the `[Dma]<T>` is a valid
|
|
||||||
/// instance of T. If it isn't (for example -- if it was initialized with [`Dma::zeroed`]),
|
|
||||||
/// then the underlying memory may not contain the expected T structure.
|
|
||||||
pub unsafe fn assume_init(self) -> Dma<T> {
|
|
||||||
let Dma {
|
|
||||||
phys,
|
|
||||||
aligned_len,
|
|
||||||
virt,
|
|
||||||
} = self;
|
|
||||||
mem::forget(self);
|
|
||||||
|
|
||||||
Dma {
|
|
||||||
phys,
|
|
||||||
aligned_len,
|
|
||||||
virt: virt.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: ?Sized> Dma<T> {
|
|
||||||
/// Returns the physical address of the physical memory that this [Dma] structure references.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// [usize] - The physical address of the memory.
|
|
||||||
pub fn physical(&self) -> usize {
|
|
||||||
self.phys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: there should exist a "context" struct that drivers create at start, which would be passed
|
|
||||||
// to the respective functions
|
|
||||||
static VIRTTOPHYS_HANDLE: LazyLock<VirtaddrTranslationHandle> = LazyLock::new(|| {
|
|
||||||
VirtaddrTranslationHandle::new().expect("failed to acquire virttophys translation handle")
|
|
||||||
});
|
|
||||||
|
|
||||||
impl<T> Dma<[T]> {
|
|
||||||
/// Returns a [Dma] object containing a zeroized slice of T with a given count.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// - 'count: [usize]' - The number of elements of type T in the allocated slice.
|
|
||||||
pub fn zeroed_slice(count: usize) -> Result<Dma<[MaybeUninit<T>]>> {
|
|
||||||
let aligned_len = count
|
|
||||||
.checked_mul(size_of::<T>())
|
|
||||||
.unwrap()
|
|
||||||
.next_multiple_of(PAGE_SIZE);
|
|
||||||
let (phys, virt) = alloc_and_map(aligned_len, &*VIRTTOPHYS_HANDLE)?;
|
|
||||||
|
|
||||||
Ok(Dma {
|
|
||||||
phys,
|
|
||||||
aligned_len,
|
|
||||||
virt: ptr::slice_from_raw_parts_mut(virt.cast(), count),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Casts the slice from type T to type U.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// '`[DMA]<U>`' - A cast handle to the Dma memory.
|
|
||||||
pub unsafe fn cast_slice<U>(self) -> Dma<[U]> {
|
|
||||||
let Dma {
|
|
||||||
phys,
|
|
||||||
virt,
|
|
||||||
aligned_len,
|
|
||||||
} = self;
|
|
||||||
core::mem::forget(self);
|
|
||||||
|
|
||||||
Dma {
|
|
||||||
phys,
|
|
||||||
virt: virt as *mut [U],
|
|
||||||
aligned_len,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T> Dma<[MaybeUninit<T>]> {
|
|
||||||
/// See [`Dma<MaybeUninit<T>>::assume_init`]
|
|
||||||
pub unsafe fn assume_init(self) -> Dma<[T]> {
|
|
||||||
let &Dma {
|
|
||||||
phys,
|
|
||||||
aligned_len,
|
|
||||||
virt,
|
|
||||||
} = &self;
|
|
||||||
mem::forget(self);
|
|
||||||
|
|
||||||
Dma {
|
|
||||||
phys,
|
|
||||||
aligned_len,
|
|
||||||
virt: virt as *mut [T],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> Deref for Dma<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
unsafe { &*self.virt }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> DerefMut for Dma<T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut T {
|
|
||||||
unsafe { &mut *self.virt }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized> Drop for Dma<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ptr::drop_in_place(self.virt);
|
|
||||||
let _ = libredox::call::munmap(self.virt as *mut (), self.aligned_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
use core::{
|
|
||||||
cmp::PartialEq,
|
|
||||||
ops::{BitAnd, BitOr, Not},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod mmio;
|
|
||||||
mod mmio_ptr;
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
mod pio;
|
|
||||||
|
|
||||||
pub use mmio::*;
|
|
||||||
pub use mmio_ptr::*;
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
pub use pio::*;
|
|
||||||
|
|
||||||
/// IO abstraction
|
|
||||||
pub trait Io {
|
|
||||||
/// Value type for IO, usually some unsigned number
|
|
||||||
type Value: Copy
|
|
||||||
+ PartialEq
|
|
||||||
+ BitAnd<Output = Self::Value>
|
|
||||||
+ BitOr<Output = Self::Value>
|
|
||||||
+ Not<Output = Self::Value>;
|
|
||||||
|
|
||||||
/// Read the underlying valu2e
|
|
||||||
fn read(&self) -> Self::Value;
|
|
||||||
/// Write the underlying value
|
|
||||||
fn write(&mut self, value: Self::Value);
|
|
||||||
|
|
||||||
/// Check whether the underlying value contains bit flags
|
|
||||||
#[inline(always)]
|
|
||||||
fn readf(&self, flags: Self::Value) -> bool {
|
|
||||||
(self.read() & flags) as Self::Value == flags
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable or disable specific bit flags
|
|
||||||
#[inline(always)]
|
|
||||||
fn writef(&mut self, flags: Self::Value, value: bool) {
|
|
||||||
let tmp: Self::Value = match value {
|
|
||||||
true => self.read() | flags,
|
|
||||||
false => self.read() & !flags,
|
|
||||||
};
|
|
||||||
self.write(tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read-only IO
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct ReadOnly<I> {
|
|
||||||
inner: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> ReadOnly<I> {
|
|
||||||
/// Wraps IO
|
|
||||||
pub const fn new(inner: I) -> ReadOnly<I> {
|
|
||||||
ReadOnly { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> ReadOnly<I> {
|
|
||||||
/// Calls [`Io::read`]
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn read(&self) -> I::Value {
|
|
||||||
self.inner.read()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [`Io::readf`]
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn readf(&self, flags: I::Value) -> bool {
|
|
||||||
self.inner.readf(flags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
/// Write-only IO
|
|
||||||
pub struct WriteOnly<I> {
|
|
||||||
inner: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> WriteOnly<I> {
|
|
||||||
/// Wraps IO
|
|
||||||
pub const fn new(inner: I) -> WriteOnly<I> {
|
|
||||||
WriteOnly { inner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I: Io> WriteOnly<I> {
|
|
||||||
/// Calls [`Io::write`]
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn write(&mut self, value: I::Value) {
|
|
||||||
self.inner.write(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// writef requires read which is not valid when write-only
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
use core::{mem::MaybeUninit, ptr};
|
|
||||||
|
|
||||||
use super::Io;
|
|
||||||
|
|
||||||
/// MMIO abstraction
|
|
||||||
#[repr(C, packed)]
|
|
||||||
pub struct Mmio<T> {
|
|
||||||
value: MaybeUninit<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Mmio<T> {
|
|
||||||
/// Creates a zeroed instance
|
|
||||||
pub unsafe fn zeroed() -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::zeroed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an unitialized instance
|
|
||||||
pub unsafe fn uninit() -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::uninit(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new instance
|
|
||||||
pub const fn new(value: T) -> Self {
|
|
||||||
Self {
|
|
||||||
value: MaybeUninit::new(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic implementation (WARNING: requires aligned pointers!)
|
|
||||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
|
||||||
impl<T> Io for Mmio<T>
|
|
||||||
where
|
|
||||||
T: Copy
|
|
||||||
+ PartialEq
|
|
||||||
+ core::ops::BitAnd<Output = T>
|
|
||||||
+ core::ops::BitOr<Output = T>
|
|
||||||
+ core::ops::Not<Output = T>,
|
|
||||||
{
|
|
||||||
type Value = T;
|
|
||||||
|
|
||||||
fn read(&self) -> T {
|
|
||||||
unsafe { ptr::read_volatile(ptr::addr_of!(self.value).cast::<T>()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: T) {
|
|
||||||
unsafe { ptr::write_volatile(ptr::addr_of_mut!(self.value).cast::<T>(), value) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u8 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u8> {
|
|
||||||
type Value = u8;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {}, [{}]",
|
|
||||||
out(reg_byte) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg_byte) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u16 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u16> {
|
|
||||||
type Value = u16;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:x}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:x}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u32 implementation
|
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
|
||||||
impl Io for Mmio<u32> {
|
|
||||||
type Value = u32;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:e}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:e}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// x86 u64 implementation (x86_64 only)
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
|
||||||
impl Io for Mmio<u64> {
|
|
||||||
type Value = u64;
|
|
||||||
|
|
||||||
fn read(&self) -> Self::Value {
|
|
||||||
unsafe {
|
|
||||||
let value: Self::Value;
|
|
||||||
let ptr: *const Self::Value = ptr::addr_of!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov {:r}, [{}]",
|
|
||||||
out(reg) value,
|
|
||||||
in(reg) ptr
|
|
||||||
);
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&mut self, value: Self::Value) {
|
|
||||||
unsafe {
|
|
||||||
let ptr: *mut Self::Value = ptr::addr_of_mut!(self.value).cast::<Self::Value>();
|
|
||||||
core::arch::asm!(
|
|
||||||
"mov [{}], {:r}",
|
|
||||||
in(reg) ptr,
|
|
||||||
in(reg) value,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user