base: apply Red Bear patches on latest upstream/main

251 files: init, acpid, ipcd, netcfg, ihdgd, virtio-gpud, scheme-utils,
inputd, block driver, ptyd, ramfs, randd, initfs bootstrap, path deps,
version +rb0.3.1, author attribution
This commit is contained in:
Red Bear OS
2026-07-11 11:39:24 +03:00
parent 1b17b3fc24
commit bd595851e2
251 changed files with 24641 additions and 5993 deletions
Generated
+467 -467
View File
File diff suppressed because it is too large Load Diff
+45 -10
View File
@@ -5,6 +5,8 @@ members = [
"config",
"daemon",
"dhcpd",
"dhcpv6d",
"netdiag",
"init",
"initfs",
"initfs/tools",
@@ -13,6 +15,7 @@ members = [
"netstack",
"ptyd",
"ramfs",
"redbear-ufw",
"randd",
"scheme-utils",
"zerod",
@@ -66,6 +69,22 @@ members = [
"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",
]
# Bootstrap needs it's own profile configuration
@@ -74,29 +93,28 @@ 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" }
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-fourcc = "2.2.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.18"
orbclient = "0.4.1"
psf-rs = "2.2.0"
parking_lot = "0.12"
libredox = { path = "../libredox", default-features = true }
orbclient = "0.3.51"
parking_lot = { git = "https://github.com/Amanieu/parking_lot.git", rev = "0.12.3", default-features = false }
pico-args = "0.5"
plain = "0.2.3"
ransid = "0.4"
redox_event = "0.4.8"
redox-ioctl = { git = "https://gitlab.redox-os.org/redox-os/relibc.git" }
redox-ioctl = { path = "../relibc/redox-ioctl" }
redox-log = { git = "https://gitlab.redox-os.org/redox-os/redox-log.git" }
redox-scheme = { version = "0.11.2", default-features = false }
redox_syscall = { version = "0.9.0", features = ["std"] }
redox-rt = { path = "../relibc/redox-rt", default-features = false }
redox-scheme = { path = "../redox-scheme" }
redox_syscall = { path = "../syscall", features = ["std"] }
redox_termios = "0.1.3"
ron = "0.8.1"
serde = { version = "1", features = ["derive"] }
@@ -114,7 +132,24 @@ missing_docs = "allow" #TODO: set to deny when all public functions are document
[workspace.lints.clippy]
missing_safety_doc = "warn" #TODO: set to deny when all safety documentation is completed
precedence = "deny"
unused_io_amount = "deny"
[patch.crates-io]
# Red Bear OS Phase I: s2idle / Modern Standby support.
# The [patch.crates-io] replaces the upstream gitlab.redox-os.org
# redox_syscall (which lacks the new AcpiVerb::EnterS2Idle /
# ExitS2Idle variants) with the local fork at
# local/sources/syscall/ (a sibling directory of base/, both
# under local/sources/). The local fork is the upstream
# gitlab.redox-os.org/redox-os/syscall @ 79cb6d9 with our
# Red Bear OS P1 commit (cfa7f0c) on top. The version field
# stays at upstream 0.8.1 — periodic rebase via
# 'git fetch upstream && git rebase upstream/master' is the
# workflow when upstream changes. Hardware-agnostic — works
# for any platform with Modern Standby firmware (Dell, HP,
# Lenovo, LG Gram, etc.).
redox_syscall = { path = "../syscall" }
libredox = { path = "../libredox" }
redox-scheme = { path = "../redox-scheme" }
[patch."https://gitlab.redox-os.org/redox-os/relibc.git"]
#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
+9 -18
View File
@@ -1,5 +1,6 @@
TARGET ?= x86_64-unknown-redox
LINKER ?= $(shell redoxer env which $(shell redoxer env printenv LD))
BOARD ?=
BUILD_TYPE ?= release
BUILD_FLAGS ?= --release
CARGO ?= redoxer
@@ -13,10 +14,10 @@ TARGET_DIR = $(BUILD_DIR)/$(TARGET)/$(BUILD_TYPE)
BUILD_FLAGS += --target-dir $(BUILD_DIR)
INITFS_BINS = init logd ramfs randd zerod \
acpid fbbootlogd hwd inputd lived \
acpid fbbootlogd fbcond hwd inputd lived \
pcid pcid-spawner rtcd vesad
INITFS_DRIVERS_BINS = nvmed virtio-blkd virtio-gpud
BASE_BINS = fbcond inputd pcid pcid-spawner redoxerd audiod dhcpd ipcd ptyd netstack
BASE_BINS = inputd pcid pcid-spawner redoxerd audiod dhcpd ipcd ptyd netstack
DRIVERS_BINS = e1000d ihdad ihdgd ixgbed rtl8139d rtl8168d \
usbctl usbhidd usbhubd usbscsid virtio-netd xhcid
@@ -27,7 +28,9 @@ ifneq (,$(filter i586-unknown-redox i686-unknown-redox x86_64-unknown-redox,$(TA
endif
ifeq ($(TARGET),aarch64-unknown-redox)
INITFS_BINS += bcm2835-sdhcid
ifeq ($(BOARD),raspi3b)
INITFS_BINS += bcm2835-sdhcid
endif
endif
INITFS_CARGO_ARGS = $(foreach bin,$(INITFS_BINS),-p $(bin))
@@ -46,12 +49,12 @@ clean:
# test if booting
test: all
$(MAKE) install
REDOXER_SYSROOT=$(DESTDIR) redoxer exec true
redoxer exec --folder ./sysroot/:/ true
# test with interactive gui
test-gui: all
$(MAKE) install
REDOXER_SYSROOT=$(DESTDIR) redoxer exec --gui ion
redoxer exec --gui --folder ./sysroot/:/ ion
# -----------------------------------------------------------------------------
# base
@@ -79,6 +82,7 @@ base:
install-base: base $(SYSROOT)/bin/redoxfs
@mkdir -pv "$(DESTDIR)/usr/bin" "$(DESTDIR)/usr/lib/drivers"
@mkdir -pv "$(DESTDIR)/usr/lib/init.d/" "$(DESTDIR)/usr/lib/pcid.d"
# Distribute binaries
@for bin in $(BASE_BINS); do \
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/bin"; \
@@ -87,13 +91,11 @@ install-base: base $(SYSROOT)/bin/redoxfs
cp -v "$(TARGET_DIR)/$$bin" "$(DESTDIR)/usr/lib/drivers"; \
done
# Copy configurations
@mkdir -pv "$(DESTDIR)/usr/lib/init.d/" "$(DESTDIR)/usr/lib/pcid.d/" "$(DESTDIR)/usr/lib/xhcid.d/"
@cp -v "$(SRC_DIR)/init.d"/* "$(DESTDIR)/usr/lib/init.d/"
@find "$(SRC_DIR)/drivers" -maxdepth 3 -type f -name 'config.toml' | while read -r conf; do \
driver=$$(basename "$$(dirname "$$conf")"); \
cp -v "$$conf" "$(DESTDIR)/usr/lib/pcid.d/$$driver.toml"; \
done
@cp -v "$(SRC_DIR)/drivers/usb/xhcid/drivers.toml" "$(DESTDIR)/usr/lib/xhcid.d/"
rm -rf "$(BUILD_DIR)/initfs"
# Distribute initfs binaries
@@ -115,14 +117,3 @@ install-base: base $(SYSROOT)/bin/redoxfs
# Distribute initfs
@mkdir -pv "$(DESTDIR)/usr/lib/boot"
cp -v "$(BUILD_DIR)/initfs.img" "$(DESTDIR)/usr/lib/boot/initfs"
# Device file symlinks
@mkdir -pv "$(DESTDIR)/dev"
ln -sf /scheme/null $(DESTDIR)/dev/null
ln -sf /scheme/rand $(DESTDIR)/dev/random
ln -sf /scheme/rand $(DESTDIR)/dev/urandom
ln -sf /scheme/zero $(DESTDIR)/dev/zero
ln -sf libc:tty $(DESTDIR)/dev/tty
ln -sf libc:stdin $(DESTDIR)/dev/stdin
ln -sf libc:stdout $(DESTDIR)/dev/stdout
ln -sf libc:stderr $(DESTDIR)/dev/stderr
+10 -14
View File
@@ -14,18 +14,15 @@ use scheme_utils::ReadinessBased;
use daemon::SchemeDaemon;
use self::scheme::{AudioScheme, AudioSchemeInner};
use self::scheme::AudioScheme;
mod scheme;
extern "C" fn sigusr_handler(_sig: usize) {}
fn thread(inner_mutex: Arc<Mutex<AudioSchemeInner>>, pid: usize, hw_file: Fd) -> Result<()> {
fn thread(scheme: Arc<Mutex<AudioScheme>>, pid: usize, hw_file: Fd) -> Result<()> {
loop {
let buffer = {
let mut inner = inner_mutex.lock().unwrap();
inner.buffer()
};
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))
};
@@ -44,8 +41,7 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
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 *const () as usize);
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)?;
@@ -56,9 +52,9 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
let socket = Socket::create().context("failed to create scheme")?;
let mut scheme = AudioScheme::new();
let scheme = Arc::new(Mutex::new(AudioScheme::new()));
let _ = daemon.ready_sync_scheme(&socket, &mut scheme).unwrap();
let _ = daemon.ready_sync_scheme(&socket, &mut *scheme.lock().unwrap());
// Enter a constrained namespace
let ns = libredox::call::mkns(&[
@@ -69,14 +65,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
libredox::call::setns(ns).context("failed to set namespace")?;
// Spawn a thread to mix and send audio data
let inner_thread = scheme.inner.clone();
let _thread = thread::spawn(move || thread(inner_thread, pid, hw_file));
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)?;
readiness.poll_all_requests(&mut scheme)?;
readiness.read_and_process_requests(&mut *scheme.lock().unwrap())?;
readiness.poll_all_requests(&mut *scheme.lock().unwrap())?;
readiness.write_responses()?;
}
}
+12 -34
View File
@@ -1,10 +1,7 @@
use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap;
use std::{
collections::VecDeque,
str,
sync::{Arc, Mutex},
};
use std::collections::VecDeque;
use std::str;
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EWOULDBLOCK};
use redox_scheme::scheme::SchemeSync;
@@ -23,14 +20,14 @@ enum Handle {
SchemeRoot,
}
pub struct AudioSchemeInner {
pub struct AudioScheme {
handles: HandleMap<Handle>,
volume: i32,
}
impl AudioSchemeInner {
impl AudioScheme {
pub fn new() -> Self {
Self {
AudioScheme {
handles: HandleMap::new(),
volume: 50,
}
@@ -66,22 +63,9 @@ impl AudioSchemeInner {
}
}
pub struct AudioScheme {
pub inner: Arc<Mutex<AudioSchemeInner>>,
}
impl AudioScheme {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(AudioSchemeInner::new())),
}
}
}
impl SchemeSync for AudioScheme {
fn scheme_root(&mut self) -> Result<usize> {
let mut inner = self.inner.lock().unwrap();
Ok(inner.handles.insert(Handle::SchemeRoot))
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
@@ -91,9 +75,7 @@ impl SchemeSync for AudioScheme {
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
let mut inner = self.inner.lock().unwrap();
if !matches!(inner.handles.get(dirfd)?, Handle::SchemeRoot) {
if !matches!(self.handles.get(dirfd)?, Handle::SchemeRoot) {
return Err(Error::new(EACCES));
}
@@ -108,7 +90,7 @@ impl SchemeSync for AudioScheme {
_ => return Err(Error::new(ENOENT)),
};
let id = inner.handles.insert(handle);
let id = self.handles.insert(handle);
Ok(OpenResult::ThisScheme { number: id, flags })
}
@@ -121,10 +103,8 @@ impl SchemeSync for AudioScheme {
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let mut inner = self.inner.lock().unwrap();
//TODO: check flags for readable
match inner.handles.get_mut(id)? {
match self.handles.get_mut(id)? {
Handle::Audio { buffer: _ } => {
//TODO: audio input?
Err(Error::new(EBADF))
@@ -134,7 +114,7 @@ impl SchemeSync for AudioScheme {
return Ok(0);
};
//TODO: should we allocate every time?
let bytes = format!("{}", inner.volume).into_bytes();
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]);
@@ -153,10 +133,8 @@ impl SchemeSync for AudioScheme {
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let mut inner = self.inner.lock().unwrap();
//TODO: check flags for writable
match inner.handles.get_mut(id)? {
match self.handles.get_mut(id)? {
Handle::Audio { ref mut buffer } => {
if buffer.len() >= HANDLE_BUFFER_SIZE {
Err(Error::new(EWOULDBLOCK))
@@ -183,7 +161,7 @@ impl SchemeSync for AudioScheme {
.parse::<i32>()
.map_err(|_| Error::new(EINVAL))?;
if value >= 0 && value <= 100 {
inner.volume = value;
self.volume = value;
Ok(buf.len())
} else {
Err(Error::new(EINVAL))
+23 -37
View File
@@ -4,15 +4,15 @@ version = 4
[[package]]
name = "arrayvec"
version = "0.7.6"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "bitflags"
version = "2.10.0"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "bootstrap"
@@ -25,7 +25,7 @@ dependencies = [
"log",
"plain",
"redox-initfs",
"redox-path 0.4.0",
"redox-path",
"redox-rt",
"redox-scheme",
"redox_syscall",
@@ -41,13 +41,12 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "generic-rt"
version = "0.1.0"
source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#e8d4ce4822d721d4dafaafa8c04dcbfe626622e7"
[[package]]
name = "goblin"
version = "0.10.5"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9"
checksum = "17582616a7718cca54cec18e534a76c7c4aec11a8b9a85695712f262fd15a4c8"
dependencies = [
"log",
"plain",
@@ -71,15 +70,13 @@ checksum = "5e571352c8a3b89074d12e3ee5173ffe162159105352aaaf1fc5764da747e31b"
[[package]]
name = "libc"
version = "0.2.181"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "libredox"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
version = "0.1.18+rb0.3.0"
dependencies = [
"bitflags",
"libc",
@@ -89,9 +86,9 @@ dependencies = [
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897"
dependencies = [
"spinning_top",
]
@@ -107,9 +104,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.29"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "plain"
@@ -128,9 +125,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.44"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
@@ -148,16 +145,9 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717"
[[package]]
name = "redox-path"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54ec1b67c01c63545205ea6d218b336751399f0d8974c1a635c28604bedb81bc"
[[package]]
name = "redox-rt"
version = "0.1.0"
source = "git+https://gitlab.redox-os.org/redox-os/relibc.git#e8d4ce4822d721d4dafaafa8c04dcbfe626622e7"
dependencies = [
"bitflags",
"generic-rt",
@@ -165,15 +155,13 @@ dependencies = [
"ioslice",
"libredox",
"plain",
"redox-path 0.3.1",
"redox-path",
"redox_syscall",
]
[[package]]
name = "redox-scheme"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5928b2f572e06a74e9c210fcacf15b70b8ffa6fdb3d8650f4601b83c12fe194b"
version = "0.11.2+rb0.3.0"
dependencies = [
"libredox",
"redox_syscall",
@@ -181,9 +169,7 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759"
version = "0.9.0+rb0.3.0"
dependencies = [
"bitflags",
]
@@ -231,9 +217,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.114"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
@@ -242,6 +228,6 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+16 -5
View File
@@ -6,24 +6,31 @@ authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2024"
license = "MIT"
[workspace.dependencies]
libredox = { path = "../../libredox", default-features = false, features = ["base", "protocol", "redox_syscall"] }
redox_syscall = { path = "../../syscall" }
redox-scheme = { path = "../../redox-scheme", default-features = false }
[workspace]
[dependencies]
hashbrown = { version = "0.15", default-features = false, features = [
"inline-more",
"default-hasher",
] }
linked_list_allocator = "0.10"
libredox = { version = "0.1.18", default-features = false, features = ["protocol"] }
libredox = { workspace = true }
log = { version = "0.4", default-features = false }
plain = "0.2"
redox-initfs = { path = "../initfs", default-features = false }
redox_syscall = "0.9.0"
redox-scheme = { version = "0.11.2", default-features = false }
redox-path = "0.4.0"
redox_syscall = { workspace = true }
redox-scheme = { workspace = true }
redox-path = "0.3.1"
slab = { version = "0.4.9", default-features = false }
arrayvec = { version = "0.7.6", default-features = false }
[target.'cfg(target_os = "redox")'.dependencies]
redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc.git", default-features = false }
redox-rt = { path = "../../relibc/redox-rt", default-features = false }
[profile.release]
panic = "abort"
@@ -33,3 +40,7 @@ opt-level = "s"
[profile.dev]
panic = "abort"
opt-level = "s"
[patch.crates-io]
redox_syscall = { path = "../../syscall" }
libredox = { path = "../../libredox" }
+39 -49
View File
@@ -6,11 +6,10 @@ use core::str::FromStr;
use hashbrown::HashMap;
use redox_scheme::Socket;
use libredox::protocol::O_CLOEXEC;
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::{O_DIRECTORY, O_RDONLY, O_STAT};
use syscall::CallFlags;
use syscall::{Error, EINTR};
use syscall::data::{GlobalSchemes, KernelSchemeInfo};
use syscall::flag::{O_CLOEXEC, O_RDONLY, O_STAT};
use syscall::{EINTR, Error};
use redox_rt::proc::*;
@@ -27,7 +26,7 @@ impl log::Log for Logger {
let line = record.line().unwrap_or(0);
let level = record.level();
let msg = record.args();
let _ = syscall::write(
let _ = libredox::call::write(
1,
alloc::format!("[{file}:{line} {level}] {msg}\n").as_bytes(),
);
@@ -54,8 +53,6 @@ pub fn main() -> ! {
FdGuard::new(*(base_ptr as *const usize))
};
let cur_context_idx = scheme_creation_cap.as_raw_fd() + 1;
let mut kernel_schemes = KernelSchemeMap::new(kernel_scheme_infos);
let auth = kernel_schemes
@@ -63,8 +60,8 @@ pub fn main() -> ! {
.remove(&GlobalSchemes::Proc)
.expect("failed to get proc fd");
let this_thr_fd = syscall::dup_into(auth.as_raw_fd(), cur_context_idx, b"cur-context")
.map(FdGuard::new)
let this_thr_fd = auth
.dup(b"cur-context")
.expect("failed to open open_via_dup")
.to_upper()
.unwrap();
@@ -73,13 +70,13 @@ pub fn main() -> ! {
let mut env_bytes = [0_u8; 4096];
let mut envs = {
let fd = FdGuard::new(
redox_rt::sys::openat(
libredox::call::openat(
kernel_schemes
.get(GlobalSchemes::Sys)
.expect("failed to get sys fd")
.as_raw_fd(),
"env",
O_RDONLY | O_CLOEXEC,
(O_RDONLY | O_CLOEXEC) as i32,
0,
)
.expect("bootstrap: failed to open env"),
@@ -208,16 +205,12 @@ pub fn main() -> ! {
// from this point, this_thr_fd is no longer valid
const CWD: &[u8] = b"/scheme/initfs";
let initfs_root_fd = initns_fd
.openat_into_upper("/scheme/initfs", O_DIRECTORY, 0)
.expect("failed to open initfs root fd");
let cwd_fd = initfs_root_fd
.openat_into_upper("", O_STAT, 0)
.expect("failed to open cwd fd");
let filetable_binary_fd = init_thr_fd
.dup_into_upper(b"filetable-binary")
.expect("faild to create filetable-binary fd");
let cwd_fd = FdGuard::new(
libredox::call::openat(initns_fd.as_raw_fd(), "/scheme/initfs", O_STAT as i32, 0)
.expect("failed to open cwd fd"),
)
.to_upper()
.unwrap();
let extrainfo = ExtraInfo {
cwd: Some(CWD),
sigprocmask: 0,
@@ -227,18 +220,18 @@ pub fn main() -> ! {
proc_fd: init_proc_fd.as_raw_fd(),
ns_fd: Some(initns_fd.take()),
cwd_fd: Some(cwd_fd.as_raw_fd()),
filetable_fd: Some(filetable_binary_fd.as_raw_fd()),
same_process: true,
};
let exe_path = "/scheme/initfs/bin/init";
let exe_reference = "bin/init";
let path = "/scheme/initfs/bin/init";
let image_file = initfs_root_fd
.openat_into_upper(exe_reference, O_RDONLY | O_CLOEXEC, 0)
.expect("failed to open init");
let image_file = FdGuard::new(
libredox::call::openat(extrainfo.ns_fd.unwrap(), path, (O_RDONLY | O_CLOEXEC) as i32, 0)
.expect("failed to open init"),
)
.to_upper()
.unwrap();
drop(initfs_root_fd);
let exe_path = alloc::format!("/scheme/initfs{}", path);
let FexecResult::Interp {
path: interp_path,
@@ -253,33 +246,26 @@ pub fn main() -> ! {
&extrainfo,
None,
)
.ok()
.flatten()
.map_err(|e| {
let _ = libredox::call::write(1, alloc::format!("fexec_impl failed: {}\n", e).as_bytes());
e
})
.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_path = interp_cstr.to_str().expect("interpreter not UTF-8");
let root_fd = FdGuard::new(
redox_rt::sys::openat_into_upper(
let interp_file = FdGuard::new(
libredox::call::openat(
extrainfo.ns_fd.unwrap(), // initns, not initfs!
interp_path,
O_RDONLY | O_CLOEXEC,
interp_cstr.to_str().expect("interpreter not UTF-8"),
(O_RDONLY | O_CLOEXEC) as i32,
0,
)
.expect("failed to open root fd"),
.expect("failed to open dynamic linker"),
)
.to_upper()
.unwrap();
let redox_path = redox_path::RedoxPath::from_absolute(interp_path)
.expect("interpreter path is not a Scheme-rooted path");
let (_, reference) = redox_path
.as_parts()
.expect("redox_path is not scheme root path");
let interp_file = root_fd
.openat_into_upper(reference.as_ref(), O_RDONLY | O_CLOEXEC, 0)
.expect("failed to open dynamic linker");
fexec_impl(
interp_file,
@@ -306,13 +292,13 @@ pub(crate) fn spawn(
inner: impl FnOnce(FdGuard, Socket, FdGuard, KernelSchemeMap) -> !,
) -> (FdGuard, FdGuard, KernelSchemeMap, FdGuard) {
let read = FdGuard::new(
redox_rt::sys::openat(
libredox::call::openat(
kernel_schemes
.get(GlobalSchemes::Pipe)
.expect("failed to get pipe fd")
.as_raw_fd(),
"",
O_CLOEXEC,
O_CLOEXEC as i32,
0,
)
.expect("failed to open sync read pipe"),
@@ -320,7 +306,7 @@ pub(crate) fn spawn(
// The write pipe will not inherit O_CLOEXEC, but is closed by the daemon later.
let write = FdGuard::new(
redox_rt::sys::dup(read.as_raw_fd(), b"write").expect("failed to open sync write pipe"),
libredox::call::dup(read.as_raw_fd(), b"write").expect("failed to open sync write pipe"),
);
match fork_impl(&ForkArgs::Init {
@@ -332,11 +318,15 @@ pub(crate) fn spawn(
}
// Continue serving the scheme as the child.
Ok(0) => {
let _ = libredox::call::write(1, b"SP:0\n");
drop(read);
let _ = libredox::call::write(1, b"SP:1\n");
let socket = Socket::create_inner(scheme_creation_cap.as_raw_fd(), nonblock)
.expect("failed to open proc scheme socket");
let _ = libredox::call::write(1, b"SP:2\n");
drop(scheme_creation_cap);
let _ = libredox::call::write(1, b"SP:3\n");
inner(write, socket, auth, kernel_schemes)
}
@@ -352,7 +342,7 @@ pub(crate) fn spawn(
)
};
loop {
match redox_rt::sys::sys_call_ro(
match syscall::call_ro(
read.as_raw_fd(),
fd_bytes,
CallFlags::FD | CallFlags::FD_UPPER,
+82 -24
View File
@@ -6,15 +6,16 @@ use core::str;
use alloc::string::String;
use hashbrown::HashMap;
use redox_initfs::{InitFs, Inode, InodeDir, InodeKind, InodeStruct};
use redox_initfs::{InitFs, Inode, InodeDir, InodeKind, InodeStruct, types::Timespec};
use redox_rt::proc::FdGuard;
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
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;
@@ -22,7 +23,6 @@ use syscall::dirent::DirentKind;
use syscall::error::*;
use syscall::flag::*;
use syscall::schemev2::NewFdFlags;
use syscall::PAGE_SIZE;
enum Handle {
Node(Node),
@@ -135,7 +135,7 @@ impl SchemeSync for InitFsScheme {
// filter out double slashes (e.g. /usr//bin/...)
.filter(|c| !c.is_empty());
let mut current_inode = self.fs.root_inode();
let mut current_inode = InitFs::ROOT_INODE;
while let Some(component) = components.next() {
match component {
@@ -315,6 +315,8 @@ impl SchemeSync for InitFsScheme {
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();
@@ -329,10 +331,10 @@ impl SchemeSync for InitFsScheme {
stat.st_gid = 0;
stat.st_size = u64::try_from(inode_len(inode)?).unwrap_or(u64::MAX);
stat.st_ctime = 0;
stat.st_ctime_nsec = 0;
stat.st_mtime = 0;
stat.st_mtime_nsec = 0;
stat.st_ctime = sec.get();
stat.st_ctime_nsec = nsec.get();
stat.st_mtime = sec.get();
stat.st_mtime_nsec = nsec.get();
Ok(())
}
@@ -391,9 +393,9 @@ pub fn run(bytes: &'static [u8], sync_pipe: FdGuard, socket: Socket) -> ! {
let cap_fd = socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("failed to issue initfs root fd");
let _ = redox_rt::sys::sys_call_wo(
let _ = syscall::call_rw(
sync_pipe.as_raw_fd(),
&cap_fd.to_ne_bytes(),
&mut cap_fd.to_ne_bytes(),
CallFlags::FD,
&[],
);
@@ -444,21 +446,72 @@ pub unsafe extern "C" fn redox_write_v1(fd: usize, ptr: *const u8, len: usize) -
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_openat_v1(
fd: usize,
buf: *const u8,
path_len: usize,
flags: u32,
fcntl_flags: u32,
) -> isize {
let path = unsafe { core::slice::from_raw_parts(buf, path_len) };
let path_str = match core::str::from_utf8(path) {
Ok(s) => s,
Err(_) => return -(syscall::EINVAL as isize),
};
Error::mux(syscall::openat(fd, path_str, flags as usize, fcntl_flags as usize)) as isize
}
#[unsafe(no_mangle)]
pub unsafe fn redox_dup_v1(fd: usize, buf: *const u8, len: usize) -> isize {
Error::mux(redox_rt::sys::dup(fd, unsafe {
Error::mux(syscall::dup(fd, unsafe {
core::slice::from_raw_parts(buf, len)
})) as isize
}
#[unsafe(no_mangle)]
pub unsafe fn redox_fcntl_v0(fd: usize, cmd: usize, arg: usize) -> isize {
Error::mux(redox_rt::sys::fcntl(fd, cmd, arg)) as isize
pub extern "C" fn redox_close_v1(fd: usize) -> isize {
Error::mux(syscall::close(fd)) as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn redox_close_v1(fd: usize) -> isize {
Error::mux(redox_rt::sys::close(fd)) as isize
pub extern "C" fn redox_fcntl_v0(fd: usize, cmd: usize, arg: usize) -> isize {
Error::mux(syscall::fcntl(fd, cmd, arg)) as isize
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn redox_strerror_v1(
dst: *mut u8,
dst_len: *mut usize,
error: u32,
) -> isize {
let msg = match error {
x if x == syscall::EPERM as u32 => "Operation not permitted",
x if x == syscall::ENOENT as u32 => "No such file or directory",
x if x == syscall::EINTR as u32 => "Interrupted system call",
x if x == syscall::EIO as u32 => "I/O error",
x if x == syscall::EBADF as u32 => "Bad file descriptor",
x if x == syscall::EAGAIN as u32 => "Resource temporarily unavailable",
x if x == syscall::ENOMEM as u32 => "Cannot allocate memory",
x if x == syscall::EACCES as u32 => "Permission denied",
x if x == syscall::EFAULT as u32 => "Bad address",
x if x == syscall::EBUSY as u32 => "Device or resource busy",
x if x == syscall::EEXIST as u32 => "File exists",
x if x == syscall::ENOTDIR as u32 => "Not a directory",
x if x == syscall::EISDIR as u32 => "Is a directory",
x if x == syscall::EINVAL as u32 => "Invalid argument",
x if x == syscall::ENOSYS as u32 => "Function not implemented",
x if x == syscall::ENOTEMPTY as u32 => "Directory not empty",
_ => "Unknown error",
};
let msg_bytes = msg.as_bytes();
unsafe {
let avail = *dst_len;
let copy_len = avail.min(msg_bytes.len());
core::ptr::copy_nonoverlapping(msg_bytes.as_ptr(), dst, copy_len);
*dst_len = copy_len;
copy_len as isize
}
}
#[unsafe(no_mangle)]
@@ -471,15 +524,20 @@ pub unsafe extern "C" fn redox_sys_call_v0(
metadata_len: usize,
) -> isize {
let flags = CallFlags::from_bits_retain(flags);
let read = flags.contains(syscall::CallFlags::READ);
let write = flags.contains(syscall::CallFlags::WRITE);
let payload = unsafe { core::slice::from_raw_parts_mut(payload, payload_len) };
let metadata = unsafe { core::slice::from_raw_parts(metadata, metadata_len) };
Error::mux(match (read, write) {
(true, true) => redox_rt::sys::sys_call_rw(fd, payload, flags, metadata),
(true, false) => redox_rt::sys::sys_call_ro(fd, payload, flags, metadata),
(false, true) => redox_rt::sys::sys_call_wo(fd, payload, flags, metadata),
(false, false) => redox_rt::sys::sys_call(fd, payload, flags, metadata),
}) as isize
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
}
+101 -150
View File
@@ -10,14 +10,14 @@ use libredox::protocol::{NsDup, NsPermissions};
use log::{error, warn};
use redox_path::RedoxPath;
use redox_path::RedoxScheme;
use redox_rt::proc::{FdGuard, FdGuardUpper};
use redox_rt::proc::FdGuard;
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
CallerCtx, OpenResult, RequestKind, Response, SendFdRequest, SignalBehavior, Socket,
scheme::{SchemeState, SchemeSync},
};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::Stat;
use syscall::{error::*, schemev2::NewFdFlags, CallFlags, FobtainFdFlags};
use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::{CallFlags, FobtainFdFlags, error::*, schemev2::NewFdFlags};
#[derive(Debug, Clone)]
struct Namespace {
@@ -92,7 +92,6 @@ impl SchemeRegister {
enum Handle {
Access(NamespaceAccess),
Register(SchemeRegister),
OtherScheme { name: String, fd: Rc<FdGuard> },
List(NamespaceAccess),
}
@@ -101,7 +100,7 @@ pub struct NamespaceScheme<'sock> {
handles: HashMap<usize, Handle>,
root_namespace: Namespace,
next_id: usize,
scheme_creation_cap: Rc<FdGuardUpper>,
scheme_creation_cap: FdGuard,
}
const HIGH_PERMISSIONS: NsPermissions = NsPermissions::SCHEME_CREATE;
@@ -110,14 +109,14 @@ impl<'sock> NamespaceScheme<'sock> {
pub fn new(
socket: &'sock Socket,
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuardUpper,
scheme_creation_cap: FdGuard,
) -> Self {
Self {
socket,
handles: HashMap::new(),
root_namespace: Namespace { schemes },
next_id: 0,
scheme_creation_cap: Rc::new(scheme_creation_cap),
scheme_creation_cap,
}
}
@@ -138,55 +137,23 @@ impl<'sock> NamespaceScheme<'sock> {
}
fn open_namespace_resource(
&mut self,
&self,
ns_access: &NamespaceAccess,
redox_path: RedoxPath<'_>,
reference: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let (_scheme, reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
match reference.as_ref() {
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));
}
let new_id = self.next_id;
self.next_id += 1;
self.handles.insert(new_id, Handle::List(ns_access.clone()));
let resource_id = self.next_id;
self.next_id += 1;
let dup_fd = self.scheme_creation_cap.dup(&[])?;
self.handles.insert(
resource_id,
Handle::OtherScheme {
name: redox_path.to_string(),
fd: Rc::new(dup_fd),
},
);
Ok(resource_id)
}
"" => {
if !ns_access.has_permission(NsPermissions::LIST) {
error!("Permission denied to list schemes in namespace root");
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()));
Ok(new_id)
Ok(libredox::call::dup(self.scheme_creation_cap.as_raw_fd(), &[])?)
}
_ => {
error!("Unknown special reference: {}", reference.as_ref());
error!("Unknown special reference: {}", reference);
return Err(Error::new(EINVAL));
}
}
@@ -196,19 +163,24 @@ impl<'sock> NamespaceScheme<'sock> {
&self,
ns: &Namespace,
scheme: &str,
reference: &str,
flags: usize,
_ctx: &CallerCtx,
) -> Result<FdGuard> {
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));
};
if flags & syscall::O_DIRECTORY == 0 {
return Err(Error::new(EISDIR));
}
let scheme_fd = syscall::openat(
cap_fd.as_raw_fd(),
reference,
flags,
fcntl_flags as usize,
)?;
cap_fd.dup(&[])
Ok(scheme_fd)
}
fn fork_namespace(&mut self, namespace: Rc<RefCell<Namespace>>, names: &[u8]) -> Result<usize> {
@@ -237,10 +209,6 @@ impl<'sock> NamespaceScheme<'sock> {
self.next_id += 1;
Ok(next_id)
}
fn on_close(&mut self, id: usize) {
let _ = self.handles.remove(&id);
}
}
impl<'sock> SchemeSync for NamespaceScheme<'sock> {
@@ -252,83 +220,55 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
fcntl_flags: u32,
ctx: &CallerCtx,
) -> Result<OpenResult> {
let handle = self.handles.get(&fd).cloned().ok_or_else(|| {
error!("Handle with ID {} not found", fd);
Error::new(ENOENT)
})?;
match handle {
Handle::Access(ns_access) => {
let redox_path = RedoxPath::from_absolute(path).ok_or(Error::new(EINVAL))?;
let (scheme, _reference) = redox_path.as_parts().ok_or(Error::new(EINVAL))?;
match scheme.as_ref() {
"namespace" | "" => {
let new_id = self.open_namespace_resource(
&ns_access,
redox_path,
flags,
fcntl_flags,
ctx,
)?;
Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
})
}
_ => {
let res_fd = self.open_scheme_resource(
&ns_access.namespace.borrow(),
scheme.as_ref(),
flags,
ctx,
)?;
Ok(OpenResult::OtherScheme { fd: res_fd.take() })
}
}
}
Handle::List(ns_access) => {
if path.is_empty() || path == "." {
let new_id = self.next_id;
self.next_id += 1;
self.handles.insert(new_id, Handle::List(ns_access.clone()));
Ok(OpenResult::ThisScheme {
number: new_id,
flags: NewFdFlags::empty(),
})
} else {
error!("Cannot open a path under List handle: {}", path);
Err(Error::new(EINVAL))
}
}
Handle::OtherScheme {
name: saved_path_str,
fd: target_fd,
} => {
let saved_path =
RedoxPath::from_absolute(&saved_path_str).ok_or(Error::new(syscall::EPROTO))?;
let (_scheme, reference) =
saved_path.as_parts().ok_or(Error::new(syscall::EPROTO))?;
if reference.as_ref() == path {
Ok(OpenResult::OtherScheme {
fd: target_fd.dup(&[])?.take(),
})
} else {
error!(
"Resource path mismatch: expected {}, got {}",
reference.as_ref(),
path,
);
Err(Error::new(EINVAL))
}
}
_ => {
error!("Handle {} is not accessible for openat", fd);
Err(Error::new(EBADF))
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> {
@@ -346,6 +286,7 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
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(
@@ -383,7 +324,7 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
})
}
fn unlinkat(&mut self, fd: usize, path: &str, _flags: usize, _ctx: &CallerCtx) -> Result<()> {
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)
@@ -392,20 +333,31 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
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() {
return Err(Error::new(EXDEV));
}
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(_) => Ok(()),
None => {
error!("Scheme {} not found in namespace", scheme);
Err(Error::new(ENODEV))
if reference.as_ref().is_empty() {
if !ns_access.has_permission(NsPermissions::DELETE) {
error!("Permission denied to remove scheme for namespace {}", fd);
return Err(Error::new(EACCES));
}
match ns.remove_scheme(scheme.as_ref()) {
Some(_) => return Ok(()),
None => {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
}
}
}
let Some(cap_fd) = ns.get_scheme_fd(scheme.as_ref()) else {
error!("Scheme {} not found in namespace", scheme);
return Err(Error::new(ENODEV));
};
syscall::unlinkat(cap_fd.as_raw_fd(), reference, flags)?;
Ok(())
}
fn on_close(&mut self, id: usize) {
self.handles.remove(&id);
}
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
@@ -433,7 +385,7 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
}
let mut new_fd = usize::MAX;
if let Err(e) = sendfd_request.obtain_fd(
self.socket,
&self.socket,
FobtainFdFlags::UPPER_TBL,
core::slice::from_mut(&mut new_fd),
) {
@@ -468,11 +420,12 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
}
if let Err(err) = buf.entry(DirEntry {
kind: DirentKind::Unspecified,
name: name,
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);
@@ -485,7 +438,7 @@ impl<'sock> SchemeSync for NamespaceScheme<'sock> {
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(_) | Handle::OtherScheme { .. } => Stat {
Handle::List(_) => Stat {
st_mode: 0o444 | syscall::MODE_DIR,
st_uid: 0,
st_gid: 0,
@@ -547,9 +500,6 @@ pub fn run(
schemes: HashMap<String, Arc<FdGuard>>,
scheme_creation_cap: FdGuard,
) -> ! {
let scheme_creation_cap = scheme_creation_cap
.to_upper()
.expect("Failed to move scheme creation cap into upper");
let mut state = SchemeState::new();
let mut scheme = NamespaceScheme::new(&socket, schemes, scheme_creation_cap);
@@ -561,7 +511,7 @@ pub fn run(
.socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("nsmgr: failed to create namespace fd");
let _ = redox_rt::sys::sys_call_wo(
let _ = syscall::call_wo(
sync_pipe.as_raw_fd(),
&cap_fd.to_ne_bytes(),
CallFlags::FD,
@@ -599,6 +549,7 @@ pub fn run(
break;
}
}
_ => (),
}
}
+24 -7
View File
@@ -1,6 +1,7 @@
#![no_std]
#![no_main]
#![feature(abort_immediate, never_type)]
#![allow(internal_features)]
#![feature(core_intrinsics, str_from_raw_parts, never_type)]
#[cfg(target_arch = "aarch64")]
#[path = "aarch64.rs"]
@@ -38,18 +39,34 @@ use syscall::flag::MapFlags;
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
use core::fmt::Write;
struct Writer;
// Try fd 1 first (opened in start()). If that fails, open a fresh debug handle.
struct Writer {
fd: usize,
}
impl Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
syscall::write(1, s.as_bytes())
.map_err(|_| core::fmt::Error)
.map(|_| ())
// Try writing to our fd. If it fails and we're on fd 1,
// attempt to open a fresh debug handle.
if libredox::call::write(self.fd, s.as_bytes()).is_ok() {
return Ok(());
}
if self.fd == 1 {
let debug_root =
syscall::UPPER_FDTBL_TAG + syscall::data::GlobalSchemes::Debug as usize;
if let Ok(new_fd) =
libredox::call::openat(debug_root, "", syscall::O_WRONLY as i32, 0)
{
self.fd = new_fd;
let _ = libredox::call::write(self.fd, s.as_bytes());
}
}
Ok(()) // Always succeed so writeln! continues formatting
}
}
let _ = writeln!(&mut Writer, "{}", info);
core::process::abort_immediate();
let _ = writeln!(Writer { fd: 1 }, "{}", info);
core::intrinsics::abort();
}
const HEAP_OFF: usize = arch::USERMODE_END / 2;
+28 -21
View File
@@ -46,15 +46,20 @@ enum VirtualId {
}
pub fn run(write_fd: FdGuard, socket: Socket, auth: FdGuard, event: FdGuard) -> ! {
let _ = libredox::call::write(1, b"PM:0\n");
// TODO?
let socket_ident = socket.inner().raw();
let _ = libredox::call::write(1, b"PM:1\n");
let queue = RawEventQueue::new(event.as_raw_fd()).expect("failed to create event queue");
let _ = libredox::call::write(1, b"PM:2\n");
drop(event);
let _ = libredox::call::write(1, b"PM:3\n");
queue
.subscribe(socket.inner().raw(), socket_ident, EventFlags::EVENT_READ)
.expect("failed to listen to scheme socket events");
let _ = libredox::call::write(1, b"PM:4\n");
let mut scheme = ProcScheme::new(auth, &queue);
@@ -63,9 +68,10 @@ pub fn run(write_fd: FdGuard, socket: Socket, auth: FdGuard, event: FdGuard) ->
let cap_fd = socket
.create_this_scheme_fd(0, new_id, 0, 0)
.expect("failed to issue procmgr root fd");
let _ = libredox::call::write(1, b"PM:5\n");
log::debug!("process manager started");
let _ = redox_rt::sys::sys_call_wo(
let _ = syscall::call_wo(
write_fd.as_raw_fd(),
&cap_fd.to_ne_bytes(),
CallFlags::FD,
@@ -549,7 +555,8 @@ enum WaitpidTarget {
struct RawEventQueue(FdGuard);
impl RawEventQueue {
pub fn new(cap_fd: usize) -> Result<Self> {
redox_rt::sys::openat(cap_fd, "", O_CREAT, 0)
libredox::call::openat(cap_fd, "", O_CREAT as i32, 0)
.map_err(Into::into)
.map(FdGuard::new)
.map(Self)
}
@@ -725,7 +732,10 @@ impl<'a> ProcScheme<'a> {
sig_pctl: None, // TODO
rtqs: Vec::new(),
}));
if let Err(err) = new_process.borrow_mut().sync_kernel_attrs(&self.auth) {
if let Err(err) = new_process
.borrow_mut()
.sync_kernel_attrs(&self.auth)
{
log::warn!("Failed to set kernel attrs when forking: {err}");
}
@@ -1065,27 +1075,25 @@ impl<'a> ProcScheme<'a> {
// FIXME remove this ProcCall variant
ProcCall::Setrens => Response::ready_err(EINVAL, op),
ProcCall::SetProcPriority => {
let target_pid = NonZeroUsize::new(metadata[1] as usize)
.map_or(fd_pid, |n| ProcessId(n.get()));
let target_pid = NonZeroUsize::new(metadata[1] as usize).map_or(fd_pid, |n| ProcessId(n.get()));
let new_prio = metadata[2] as u32;
Ready(Response::new(
self.on_setprocprio(fd_pid, target_pid, new_prio)
.map(|()| 0),
op,
self.on_setprocprio(fd_pid, target_pid, new_prio).map(|()| 0),
op
))
}
},
ProcCall::GetProcPriority => {
let target_pid = NonZeroUsize::new(metadata[1] as usize)
.map_or(fd_pid, |n| ProcessId(n.get()));
Ready(Response::new(
self.on_getprocprio(fd_pid, target_pid)
.map(|prio| prio as usize),
self.on_getprocprio(fd_pid, target_pid).map(|prio| prio as usize),
op,
))
}
},
}
}
Handle::Ps(_) => Response::ready_err(EOPNOTSUPP, op),
@@ -2541,8 +2549,8 @@ impl<'a> ProcScheme<'a> {
// Useful for debugging memory leaks.
log::trace!("NEXT FD: {}", {
let nextfd = redox_rt::sys::dup(0, &[]).unwrap();
let _ = redox_rt::sys::close(nextfd);
let nextfd = libredox::call::dup(0, &[]).unwrap();
let _ = libredox::call::close(nextfd);
nextfd
});
log::trace!("{} processes", self.processes.len());
@@ -2565,12 +2573,7 @@ impl<'a> ProcScheme<'a> {
return Err(Error::new(EINVAL));
}
let caller_euid = self
.processes
.get(&caller_pid)
.ok_or(Error::new(ESRCH))?
.borrow()
.euid;
let caller_euid = self.processes.get(&caller_pid).ok_or(Error::new(ESRCH))?.borrow().euid;
let target_rc = self.processes.get(&target_pid).ok_or(Error::new(ESRCH))?;
let mut target = target_rc.borrow_mut();
@@ -2587,7 +2590,11 @@ impl<'a> ProcScheme<'a> {
Ok(())
}
fn on_getprocprio(&self, caller_pid: ProcessId, target_pid: ProcessId) -> Result<u32> {
fn on_getprocprio(
&self,
caller_pid: ProcessId,
target_pid: ProcessId,
) -> Result<u32> {
let target_rc = self.processes.get(&target_pid).ok_or(Error::new(ESRCH))?;
Ok(target_rc.borrow().prio)
}
+21 -9
View File
@@ -2,13 +2,10 @@ 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;
}
@@ -38,42 +35,56 @@ mod offsets {
}
}
fn dbg(msg: &[u8]) {
let _ = libredox::call::write(1, msg);
}
#[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_into(debug_fd, 0, "", syscall::O_RDONLY, 0); // stdin
let _ = syscall::openat_into(debug_fd, 1, "", syscall::O_WRONLY, 0); // stdout
let _ = syscall::openat_into(debug_fd, 2, "", syscall::O_WRONLY, 0); // stderr
let _ = libredox::call::openat(debug_fd, "", syscall::O_RDONLY as i32, 0);
let _ = libredox::call::openat(debug_fd, "", syscall::O_WRONLY as i32, 0);
let _ = libredox::call::openat(debug_fd, "", syscall::O_WRONLY as i32, 0);
dbg(b"BS:0\n");
unsafe {
let _ = syscall::mprotect(4096, 4096, MapFlags::PROT_READ | MapFlags::MAP_PRIVATE)
.expect("mprotect failed for initfs header page");
dbg(b"BS:1\n");
let _ = syscall::mprotect(
text_start,
text_end - text_start,
MapFlags::PROT_READ | MapFlags::PROT_EXEC | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .text");
dbg(b"BS:2\n");
let _ = syscall::mprotect(
rodata_start,
rodata_end - rodata_start,
MapFlags::PROT_READ | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .rodata");
dbg(b"BS:3\n");
let _ = syscall::mprotect(
data_start,
data_end - data_start,
MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE,
)
.expect("mprotect failed for .data/.bss");
dbg(b"BS:4\n");
let _ = syscall::mprotect(
data_end,
crate::arch::STACK_START - data_end,
@@ -82,5 +93,6 @@ pub unsafe extern "C" fn start() -> ! {
.expect("mprotect failed for rest of memory");
}
dbg(b"BS:5\n");
crate::exec::main();
}
+19 -19
View File
@@ -2,7 +2,7 @@
#![feature(never_type)]
use std::io::{self, PipeWriter, Read, Write};
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::Command;
@@ -11,15 +11,7 @@ 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_or_else(|_| {
panic!(
"Daemons can't be started manually. \
Add a service config to make init start this daemon instead."
)
})
.parse()
.unwrap();
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: {}",
@@ -29,12 +21,12 @@ unsafe fn get_fd(var: &str) -> RawFd {
fd
}
unsafe fn pass_fd(cmd: &mut Command, env: &str, fd: RawFd) {
cmd.env(env, format!("{}", 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, libc::F_SETFD, 0) == -1 {
if libc::fcntl(fd.as_raw_fd(), libc::F_SETFD, 0) == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
@@ -51,15 +43,25 @@ pub struct Daemon {
impl Daemon {
/// Create a new daemon.
pub fn new(f: impl FnOnce(Self) -> !) -> ! {
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) {
self.write_pipe.write_all(&[0]).unwrap();
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.
@@ -68,14 +70,13 @@ impl Daemon {
pub fn spawn(mut cmd: Command) {
let (mut read_pipe, write_pipe) = io::pipe().unwrap();
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.as_raw_fd()) };
unsafe { pass_fd(&mut cmd, "INIT_NOTIFY", write_pipe.into()) };
if let Err(err) = cmd.spawn() {
eprintln!("daemon: failed to execute {cmd:?}: {err}");
return;
}
drop(write_pipe);
let mut data = [0];
match read_pipe.read_exact(&mut data) {
Ok(()) => {}
@@ -97,7 +98,6 @@ pub struct SchemeDaemon {
impl SchemeDaemon {
/// Create a new daemon for use with schemes.
#[expect(clippy::new_ret_no_self)]
pub fn new(f: impl FnOnce(SchemeDaemon) -> !) -> ! {
let write_pipe = unsafe { io::PipeWriter::from_raw_fd(get_fd("INIT_NOTIFY")) };
@@ -106,7 +106,7 @@ impl SchemeDaemon {
/// Notify the process that the scheme daemon is ready to accept requests.
pub fn ready_with_fd(self, cap_fd: Fd) -> syscall::Result<()> {
libredox::call::call_wo(
syscall::call_wo(
self.write_pipe.as_raw_fd() as usize,
&cap_fd.into_raw().to_ne_bytes(),
syscall::CallFlags::FD,
+18
View File
@@ -17,3 +17,21 @@ pub struct Dhcp {
pub magic: u32,
pub options: [u8; 308],
}
pub const DHCPDISCOVER: u8 = 1;
pub const DHCPOFFER: u8 = 2;
pub const DHCPREQUEST: u8 = 3;
pub const DHCPDECLINE: u8 = 4;
pub const DHCPACK: u8 = 5;
pub const DHCPNAK: u8 = 6;
pub const DHCPRELEASE: u8 = 7;
pub const OPT_SUBNET_MASK: u8 = 1;
pub const OPT_ROUTER: u8 = 3;
pub const OPT_DNS: u8 = 6;
pub const OPT_REQUESTED_IP: u8 = 50;
pub const OPT_LEASE_TIME: u8 = 51;
pub const OPT_MESSAGE_TYPE: u8 = 53;
pub const OPT_SERVER_ID: u8 = 54;
pub const OPT_PARAM_REQUEST: u8 = 55;
pub const OPT_END: u8 = 255;
+208 -329
View File
@@ -4,7 +4,11 @@ use std::net::{SocketAddr, UdpSocket};
use std::time::Duration;
use std::{env, process, time};
use dhcp::Dhcp;
use dhcp::{
Dhcp, DHCPACK, DHCPDISCOVER, DHCPNAK, DHCPOFFER, DHCPREQUEST, DHCPRELEASE,
OPT_DNS, OPT_LEASE_TIME, OPT_MESSAGE_TYPE, OPT_REQUESTED_IP, OPT_ROUTER,
OPT_SERVER_ID, OPT_SUBNET_MASK, OPT_END,
};
mod dhcp;
@@ -101,356 +105,243 @@ impl MacAddr {
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")?
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()
);
println!("DHCP: MAC: {} Starting", current_mac.to_string());
}
let tid = try_fmt!(
time::SystemTime::now().duration_since(time::UNIX_EPOCH),
"failed to get time"
)
.subsec_nanos();
).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"
);
try_fmt!(socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "failed to connect");
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 subnet_option: Option<[u8; 4]> = None;
let mut router_option: Option<[u8; 4]> = None;
let mut dns_option: Option<[u8; 4]> = None;
let mut server_id_option: Option<[u8; 4]> = None;
let mut lease_time_secs: u32 = 86400;
// DHCPDISCOVER
{
let mut discover = Dhcp {
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 discover = Dhcp::default();
init_dhcp_header(&mut discover, current_mac, tid);
let disc_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPDISCOVER, OPT_END];
discover.options[..disc_opts.len()].copy_from_slice(disc_opts);
try_fmt!(send_dhcp(&discover, &socket), "failed to send discover");
if verbose { println!("DHCP: Sent Discover"); }
}
// Recv DHCPOFFER
let mut offer_data = [0; 65536];
try_fmt!(socket.recv(&mut offer_data), "failed to receive offer");
let offer = unsafe { &*(offer_data.as_ptr() as *const Dhcp) };
if verbose {
println!(
"DHCP: Offer IP: {:?}, Server IP: {:?}",
offer.yiaddr, offer.siaddr
);
if verbose { println!("DHCP: Offer IP: {:?}", offer.yiaddr); }
parse_options(&offer.options, &mut |code, data| {
match code {
OPT_SUBNET_MASK if data.len() == 4 && subnet_option.is_none() => {
subnet_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_ROUTER if data.len() == 4 && router_option.is_none() => {
router_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_DNS if data.len() == 4 && dns_option.is_none() => {
dns_option = Some([data[0], data[1], data[2], data[3]]);
}
OPT_LEASE_TIME if data.len() == 4 => {
lease_time_secs = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
}
OPT_SERVER_ID if data.len() == 4 && server_id_option.is_none() => {
server_id_option = Some([data[0], data[1], data[2], data[3]]);
}
_ => {}
}
});
let mask_len = compute_prefix_len(subnet_option);
let new_ips = format!(
"{}.{}.{}.{}/{}\n",
offer.yiaddr[0], offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3], mask_len
);
try_fmt!(set_iface_cfg_value(iface, "addr/set", &new_ips), "failed to set ip");
apply_dhcp_config(iface, router_option, dns_option, verbose)?;
let server_id = server_id_option.unwrap_or([0, 0, 0, 0]);
// DHCPREQUEST
{
let mut request = Dhcp::default();
init_dhcp_header(&mut request, current_mac, tid);
let req_opts: &[u8] = &[
OPT_MESSAGE_TYPE, 1, DHCPREQUEST,
OPT_REQUESTED_IP, 4,
offer.yiaddr[0], offer.yiaddr[1], offer.yiaddr[2], offer.yiaddr[3],
OPT_SERVER_ID, 4, server_id[0], server_id[1], server_id[2], server_id[3],
OPT_END,
];
request.options[..req_opts.len()].copy_from_slice(req_opts);
try_fmt!(send_dhcp(&request, &socket), "failed to send request");
if verbose { println!("DHCP: Sent Request"); }
}
let mut subnet_option = None;
let mut router_option = None;
let mut dns_option = None;
let mut server_id_option = None;
// Recv DHCPACK
let mut ack_data = [0; 65536];
try_fmt!(socket.recv(&mut ack_data), "failed to receive ack");
if verbose { println!("DHCP: lease acquired, {}s lease time", lease_time_secs); }
// RFC 2131 lease lifecycle: RENEW at T1, REBIND at T2
let t1 = Duration::from_secs(lease_time_secs as u64 / 2);
let t2 = Duration::from_secs((lease_time_secs as u64 * 7) / 8);
let now = time::Instant::now();
let t1_deadline = now + t1;
let mut remaining = t1_deadline.saturating_duration_since(time::Instant::now());
while remaining > Duration::ZERO {
std::thread::sleep(std::cmp::min(remaining, Duration::from_secs(60)));
remaining = t1_deadline.saturating_duration_since(time::Instant::now());
}
if verbose { println!("DHCP: entering RENEW state"); }
{
let mut 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 mut renew = Dhcp::default();
init_dhcp_header(&mut renew, current_mac, tid.wrapping_add(1));
renew.ciaddr = offer.yiaddr;
let rn_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
renew.options[..rn_opts.len()].copy_from_slice(rn_opts);
try_fmt!(send_dhcp(&renew, &socket), "failed to send renew");
}
socket.set_read_timeout(Some(t2.saturating_sub(t1))).ok();
match socket.recv(&mut ack_data) {
Ok(_) => {
let response = unsafe { &*(ack_data.as_ptr() as *const Dhcp) };
match get_message_type(&response.options) {
Some(DHCPACK) => { if verbose { println!("DHCP: renewed"); } }
Some(DHCPNAK) => {
if verbose { println!("DHCP: NAK, restarting"); }
return dhcp(iface, verbose);
}
_ => {}
}
}
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());
Err(_) => {
if verbose { println!("DHCP: entering REBIND state"); }
let bind_socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind rebind");
try_fmt!(bind_socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "rebind connect");
let mut rebind = Dhcp::default();
init_dhcp_header(&mut rebind, current_mac, tid.wrapping_add(2));
rebind.ciaddr = offer.yiaddr;
let rb_opts: &[u8] = &[OPT_MESSAGE_TYPE, 1, DHCPREQUEST, OPT_END];
rebind.options[..rb_opts.len()].copy_from_slice(rb_opts);
let _ = send_dhcp(&rebind, &bind_socket);
bind_socket.set_read_timeout(Some(Duration::from_secs(10))).ok();
if let Ok(_) = bind_socket.recv(&mut ack_data) {
if verbose { println!("DHCP: rebound"); }
} else {
if verbose { println!("DHCP: lease expired, restarting"); }
return dhcp(iface, verbose);
}
}
}
{
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 apply_dhcp_config(
iface: &str,
router: Option<[u8; 4]>,
dns: Option<[u8; 4]>,
verbose: bool,
) -> Result<(), String> {
if let Some(router) = router {
let route = format!("default via {}.{}.{}.{}", router[0], router[1], router[2], router[3]);
try_fmt!(set_cfg_value("route/add", &route), "failed to set route");
}
if let Some(mut dns) = dns {
if dns[0] == 127 {
dns = [9, 9, 9, 9];
if verbose { println!("DHCP: replaced loopback DNS with Quad9"); }
}
let ns = format!("{}.{}.{}.{}", dns[0], dns[1], dns[2], dns[3]);
try_fmt!(set_cfg_value("resolv/nameserver", &ns), "failed to set DNS");
}
Ok(())
}
fn compute_prefix_len(subnet: Option<[u8; 4]>) -> u32 {
let Some(subnet) = subnet else { return 24 };
let inverted: u32 = !u32::from_be_bytes(subnet);
inverted.leading_zeros()
}
fn parse_options(options: &[u8], cb: &mut dyn FnMut(u8, &[u8])) {
let mut i = 0;
while i < options.len() {
let code = options[i];
if code == 0 { i += 1; continue; }
if code == OPT_END { break; }
i += 1;
if i >= options.len() { break; }
let len = options[i] as usize;
i += 1;
if i + len > options.len() { break; }
cb(code, &options[i..i + len]);
i += len;
}
}
fn get_message_type(options: &[u8]) -> Option<u8> {
let mut msg_type = None;
parse_options(options, &mut |code, data| {
if code == OPT_MESSAGE_TYPE && data.len() == 1 {
msg_type = Some(data[0]);
}
});
msg_type
}
fn init_dhcp_header(pkt: &mut Dhcp, mac: MacAddr, tid: u32) {
*pkt = Dhcp::default();
pkt.op = 1;
pkt.htype = 1;
pkt.hlen = 6;
pkt.tid = tid;
pkt.flags = 0x8000u16.to_be();
pkt.chaddr[..6].copy_from_slice(&mac.bytes);
pkt.magic = 0x63825363u32.to_be();
}
fn send_dhcp(pkt: &Dhcp, socket: &UdpSocket) -> Result<(), String> {
let data = unsafe {
std::slice::from_raw_parts(pkt as *const Dhcp as *const u8, std::mem::size_of::<Dhcp>())
};
socket.send(data).map(|_| ()).map_err(|e| format!("send: {}", e))
}
impl Default for Dhcp {
fn default() -> Self {
Dhcp {
op: 0, htype: 0, hlen: 0, hops: 0, tid: 0, secs: 0, flags: 0,
ciaddr: [0; 4], yiaddr: [0; 4], siaddr: [0; 4], giaddr: [0; 4],
chaddr: [0; 16], sname: [0; 64], file: [0; 128], magic: 0,
options: [0; 308],
}
}
}
fn main() {
let mut verbose = false;
let iface = "eth0";
//TODO: parse iface from the args
for arg in env::args().skip(1) {
match arg.as_ref() {
"-v" => verbose = true,
@@ -470,11 +361,8 @@ mod test {
#[test]
fn from_str_test() {
let mac = MacAddr {
bytes: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
};
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"));
@@ -483,15 +371,6 @@ mod test {
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()));
}
}
}
+4
View File
@@ -0,0 +1,4 @@
[package]
name = "dhcpv6d"
version = "0.1.0"
edition = "2021"
+286
View File
@@ -0,0 +1,286 @@
//! dhcpv6d — DHCPv6 client daemon for Red Bear OS.
//!
//! Mirrors Linux 7.1's systemd-networkd `sd-dhcp6-client.c`.
//! Implements RFC 8415 state machine:
//! SOLICIT → ADVERTISE → REQUEST → REPLY
//!
//! Reference:
//! - `sd-dhcp6-client.c` — DHCPv6 client state machine
//! - RFC 8415 §17 — client behavior
//! - RFC 3315 — original DHCPv6 specification
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::net::{SocketAddr, UdpSocket};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::{env, process, time};
const DHCPV6_CLIENT_PORT: u16 = 546;
const DHCPV6_SERVER_PORT: u16 = 547;
const MULTICAST_ADDR: &str = "ff02::1:2";
const SOLICIT: u8 = 1;
const ADVERTISE: u8 = 2;
const REQUEST: u8 = 3;
const REPLY: u8 = 7;
const RELEASE: u8 = 8;
const INFO_REQUEST: u8 = 11;
const OPT_CLIENTID: u16 = 1;
const OPT_SERVERID: u16 = 2;
const OPT_IA_NA: u16 = 3;
const OPT_IAADDR: u16 = 5;
const OPT_ORO: u16 = 6;
const OPT_DNS: u16 = 23;
const OPT_DOMAIN: u16 = 24;
macro_rules! try_fmt {
($e:expr, $m:expr) => {
match $e {
Ok(ok) => ok,
Err(err) => return Err(format!("{}: {}", $m, err)),
}
};
}
fn set_cfg(path: &str, value: &str) -> Result<(), String> {
let full = format!("/scheme/netcfg/{path}");
let mut f = OpenOptions::new().write(true).create(false).open(&full)
.map_err(|_| format!("open {}", full))?;
f.write_all(value.as_bytes()).map_err(|_| format!("write {}", full))?;
f.sync_data().map_err(|_| "sync failed".into())
}
fn get_mac() -> Result<[u8; 6], String> {
let s = fs::read_to_string("/scheme/netcfg/ifaces/eth0/mac")
.map_err(|e| format!("read mac: {}", e))?;
let mut bytes = [0u8; 6];
for (i, part) in s.trim().split(&[':', '-'][..]).take(6).enumerate() {
bytes[i] = u8::from_str_radix(part, 16).map_err(|_| "bad mac".to_string())?;
}
Ok(bytes)
}
fn build_duid(mac: [u8; 6]) -> Vec<u8> {
let time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let mut duid = vec![0u8; 14];
duid[0..2].copy_from_slice(&[0x00, 0x01]);
duid[2..4].copy_from_slice(&[0x00, 0x01]);
duid[4..8].copy_from_slice(&time.to_be_bytes());
duid[8..14].copy_from_slice(&mac);
duid
}
fn put_u16(buf: &mut Vec<u8>, val: u16) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn put_u32(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn put_option(buf: &mut Vec<u8>, code: u16, data: &[u8]) {
put_u16(buf, code);
put_u16(buf, data.len() as u16);
buf.extend_from_slice(data);
}
struct Dhcp6Packet {
data: Vec<u8>,
}
impl Dhcp6Packet {
fn new(msg_type: u8, tid: [u8; 3], duid: &[u8]) -> Self {
let mut data = vec![msg_type];
data.extend_from_slice(&tid);
put_option(&mut data, OPT_CLIENTID, duid);
put_option(&mut data, OPT_ORO, &[0, OPT_DNS as u8, 0, OPT_DOMAIN as u8]);
Dhcp6Packet { data }
}
fn add_ia_na(&mut self, iaid: u32, t1: u32, t2: u32) {
let mut ia = Vec::new();
put_u32(&mut ia, iaid);
put_u32(&mut ia, t1);
put_u32(&mut ia, t2);
put_option(&mut self.data, OPT_IA_NA, &ia);
}
}
struct Dhcp6Reply {
msg_type: u8,
server_id: Option<Vec<u8>>,
addresses: Vec<([u8; 16], u32, u32)>,
dns_servers: Vec<[u8; 16]>,
domains: Vec<String>,
}
fn parse_reply(data: &[u8]) -> Result<Dhcp6Reply, String> {
if data.len() < 4 {
return Err("packet too short".into());
}
let msg_type = data[0];
let mut reply = Dhcp6Reply {
msg_type,
server_id: None,
addresses: Vec::new(),
dns_servers: Vec::new(),
domains: Vec::new(),
};
let mut pos = 4;
while pos + 4 <= data.len() {
let code = u16::from_be_bytes([data[pos], data[pos + 1]]);
let len = u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize;
pos += 4;
if pos + len > data.len() {
break;
}
let opt_data = &data[pos..pos + len];
match code {
OPT_SERVERID => reply.server_id = Some(opt_data.to_vec()),
OPT_DNS => {
for chunk in opt_data.chunks(16) {
if chunk.len() == 16 {
let mut addr = [0u8; 16];
addr.copy_from_slice(chunk);
reply.dns_servers.push(addr);
}
}
}
OPT_IA_NA => {
if opt_data.len() >= 12 {
let t1 = u32::from_be_bytes([opt_data[4], opt_data[5], opt_data[6], opt_data[7]]);
let t2 = u32::from_be_bytes([opt_data[8], opt_data[9], opt_data[10], opt_data[11]]);
let mut inner = &opt_data[12..];
while inner.len() >= 4 {
let inner_code = u16::from_be_bytes([inner[0], inner[1]]);
let inner_len = u16::from_be_bytes([inner[2], inner[3]]) as usize;
inner = &inner[4..];
if inner.len() < inner_len { break; }
if inner_code == OPT_IAADDR && inner_len >= 24 {
let mut addr = [0u8; 16];
addr.copy_from_slice(&inner[..16]);
let pref = u32::from_be_bytes([inner[16], inner[17], inner[18], inner[19]]);
let valid = u32::from_be_bytes([inner[20], inner[21], inner[22], inner[23]]);
reply.addresses.push((addr, pref, valid));
}
inner = &inner[inner_len..];
}
}
}
_ => {}
}
pos += len;
}
Ok(reply)
}
fn main() {
let mut verbose = false;
for arg in env::args().skip(1) {
if arg == "-v" || arg == "--verbose" { verbose = true; }
}
if let Err(e) = run(verbose) {
eprintln!("dhcpv6d: {}", e);
process::exit(1);
}
}
fn run(verbose: bool) -> Result<(), String> {
let mac = get_mac()?;
let duid = build_duid(mac);
if verbose {
println!("dhcpv6d: DUID {:02x?}", duid);
}
let socket = try_fmt!(
UdpSocket::bind((MULTICAST_ADDR, DHCPV6_CLIENT_PORT)),
"bind"
);
try_fmt!(
socket.connect(SocketAddr::new(
MULTICAST_ADDR.parse().map_err(|_| "bad addr")?,
DHCPV6_SERVER_PORT,
)),
"connect"
);
try_fmt!(socket.set_read_timeout(Some(Duration::from_secs(5))), "timeout");
let tid = [
(mac[0] ^ mac[1]) as u8,
(mac[2] ^ mac[3]) as u8,
(mac[4] ^ mac[5]) as u8,
];
// SOLICIT
let mut solicit = Dhcp6Packet::new(SOLICIT, tid, &duid);
solicit.add_ia_na(1, 0, 0);
try_fmt!(socket.send(&solicit.data), "send solicit");
if verbose { println!("dhcpv6d: sent SOLICIT"); }
// Recv ADVERTISE
let mut buf = [0u8; 65536];
let n = try_fmt!(socket.recv(&mut buf), "recv advertise");
let adv = parse_reply(&buf[..n])?;
if verbose {
println!("dhcpv6d: received ADVERTISE, {} addresses", adv.addresses.len());
}
// REQUEST
let mut request = Dhcp6Packet::new(REQUEST, tid, &duid);
if let Some(ref sid) = adv.server_id {
put_option(&mut request.data, OPT_SERVERID, sid);
}
request.add_ia_na(1, 0, 0);
try_fmt!(socket.send(&request.data), "send request");
if verbose { println!("dhcpv6d: sent REQUEST"); }
// Recv REPLY
let n = try_fmt!(socket.recv(&mut buf), "recv reply");
let reply = parse_reply(&buf[..n])?;
if verbose {
println!("dhcpv6d: received REPLY, {} addresses", reply.addresses.len());
}
for (addr, pref, valid) in &reply.addresses {
let addr_str = format!(
"{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}",
u16::from_be_bytes([addr[0], addr[1]]),
u16::from_be_bytes([addr[2], addr[3]]),
u16::from_be_bytes([addr[4], addr[5]]),
u16::from_be_bytes([addr[6], addr[7]]),
u16::from_be_bytes([addr[8], addr[9]]),
u16::from_be_bytes([addr[10], addr[11]]),
u16::from_be_bytes([addr[12], addr[13]]),
u16::from_be_bytes([addr[14], addr[15]]),
);
let cidr = format!("{}/128\n", addr_str);
try_fmt!(set_cfg("ifaces/eth0/addr/set", &cidr), "set addr");
if verbose {
println!("dhcpv6d: configured {} (pref={}s valid={}s)", addr_str, pref, valid);
}
}
for dns in &reply.dns_servers {
let dns_str = format!(
"{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}:{:04x}",
u16::from_be_bytes([dns[0], dns[1]]),
u16::from_be_bytes([dns[2], dns[3]]),
u16::from_be_bytes([dns[4], dns[5]]),
u16::from_be_bytes([dns[6], dns[7]]),
u16::from_be_bytes([dns[8], dns[9]]),
u16::from_be_bytes([dns[10], dns[11]]),
u16::from_be_bytes([dns[12], dns[13]]),
u16::from_be_bytes([dns[14], dns[15]]),
);
try_fmt!(set_cfg("resolv/nameserver6", &dns_str), "set DNS");
if verbose { println!("dhcpv6d: DNS6 {}", dns_str); }
}
Ok(())
}
+63
View File
@@ -0,0 +1,63 @@
# 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 |
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "acpi-resource"
description = "Shared ACPI resource template decoder"
version = "0.0.1"
authors = ["Red Bear OS"]
repository = "https://gitlab.redox-os.org/redox-os/drivers"
categories = ["hardware-support"]
license = "MIT/Apache-2.0"
edition = "2021"
[dependencies]
serde.workspace = true
thiserror.workspace = true
+688
View File
@@ -0,0 +1,688 @@
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(_)));
}
}
+1002 -40
View File
File diff suppressed because it is too large Load Diff
+30 -1
View File
@@ -52,7 +52,23 @@ impl Deref for Dmar {
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 => {
@@ -71,7 +87,14 @@ impl Dmar {
log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags);
log::debug!("DMAR: {:?}", dmar);
for dmar_entry in dmar.iter() {
// 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) => {
@@ -87,6 +110,12 @@ impl Dmar {
_ => (),
}
}
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> {
+31 -6
View File
@@ -2,7 +2,7 @@ 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;
use rustc_hash::{FxHashMap, FxHashSet};
use std::fmt::LowerHex;
use std::mem::size_of;
use std::ptr::NonNull;
@@ -141,6 +141,12 @@ impl AmlPageCache {
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.
@@ -156,6 +162,10 @@ impl AmlPhysMemHandler {
Self {
page_cache,
pci_fd: Arc::new(pci_fd),
mutex_state: Arc::new(Mutex::new(AmlMutexState {
next_id: 1,
held: FxHashSet::default(),
})),
}
}
@@ -415,16 +425,31 @@ impl acpi::Handler for AmlPhysMemHandler {
}
fn create_mutex(&self) -> Handle {
log::debug!("TODO: Handler::create_mutex");
Handle(0)
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> {
log::debug!("TODO: Handler::acquire");
Ok(())
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) {
log::debug!("TODO: Handler::release");
self.mutex_state.lock().unwrap().held.remove(&mutex.0);
}
}
+959
View File
@@ -0,0 +1,959 @@
//! 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"));
}
}
+50 -36
View File
@@ -202,55 +202,69 @@ impl RegionHandler for Ec {
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_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
// EC is 8-bit; compose 16-bit AML reads as little-endian 8-bit EC reads.
// Cross-referenced with Linux drivers/acpi/ec.c: acpi_ec_read() and
// AML acpi_extract_value() which handles the same byte-decomposition.
let lo = self.read_u8(region, offset)? as u16;
let hi = self.read_u8(region, offset + 1)? as u16;
Ok(lo | (hi << 8))
}
fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
warn!("Got u32 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> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let part = self.read_u16(region, offset)? as u32;
let part2 = self.read_u16(region, offset + 2)? as u32;
Ok(part | (part2 << 16))
}
fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
warn!("Got u64 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> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let part = self.read_u32(region, offset)? as u64;
let part2 = self.read_u32(region, offset + 4)? as u64;
Ok(part | (part2 << 32))
}
fn write_u16(
&self,
_region: &OpRegion,
_offset: usize,
_value: u16,
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
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
Ok(())
}
fn write_u32(
&self,
_region: &OpRegion,
_offset: usize,
_value: u32,
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
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
self.write_u8(region, offset + 2, bytes[2])?;
self.write_u8(region, offset + 3, bytes[3])?;
Ok(())
}
fn write_u64(
&self,
_region: &OpRegion,
_offset: usize,
_value: u64,
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
assert_eq!(region.space, RegionSpace::EmbeddedControl);
let bytes = value.to_le_bytes();
self.write_u8(region, offset, bytes[0])?;
self.write_u8(region, offset + 1, bytes[1])?;
self.write_u8(region, offset + 2, bytes[2])?;
self.write_u8(region, offset + 3, bytes[3])?;
self.write_u8(region, offset + 4, bytes[4])?;
self.write_u8(region, offset + 5, bytes[5])?;
self.write_u8(region, offset + 6, bytes[6])?;
self.write_u8(region, offset + 7, bytes[7])?;
Ok(())
}
}
+51 -20
View File
@@ -1,25 +1,23 @@
use std::convert::TryFrom;
use std::fs::File;
use std::mem;
use std::ops::ControlFlow;
use std::os::unix::io::AsRawFd;
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;
use libredox::Fd;
use syscall::flag::{AcpiVerb, CallFlags};
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"misc",
@@ -98,6 +96,12 @@ fn daemon(daemon: daemon::Daemon) -> ! {
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
@@ -113,7 +117,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
libredox::call::setrens(0, 0).expect("acpid: failed to enter null namespace");
daemon.ready();
log::info!("acpid ready");
let mut mounted = true;
while mounted {
@@ -135,20 +138,48 @@ fn daemon(daemon: daemon::Daemon) -> ! {
ControlFlow::Break(()) => break,
}
}
} else if event.fd == shutdown_pipe.raw() {
if shutdown_pipe
.call_ro(
&mut [],
CallFlags::empty(),
&[AcpiVerb::CheckShutdown as u64],
)
.expect("acpid: failed to get shutdown status")
== 0
{
continue;
} 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;
}
}
log::info!("Received shutdown request from kernel.");
mounted = false;
} else {
log::debug!("Received request to unknown fd: {}", event.fd);
continue;
@@ -166,4 +197,4 @@ fn daemon(daemon: daemon::Daemon) -> ! {
fn main() {
common::init();
daemon::Daemon::new(daemon);
}
}
+378 -6
View File
@@ -6,6 +6,8 @@ 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};
@@ -21,13 +23,19 @@ 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::acpi::{AcpiContext, AmlSymbols, PowerCache, 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>,
power_cache: PowerCache,
}
struct Handle<'a> {
@@ -43,6 +51,53 @@ enum HandleKind<'a> {
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,
PowerBatteries,
PowerBattery { name: String, file: PowerFileKind },
PowerAdapter { name: String, file: PowerFileKind },
/// `/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)]
enum PowerFileKind {
State,
Percentage,
Online,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ProcFileKind {
Pss,
Psd,
Cst,
Cpc,
}
impl HandleKind<'_> {
@@ -55,6 +110,11 @@ impl HandleKind<'_> {
Self::Symbol { .. } => false,
Self::SchemeRoot => false,
Self::RegisterPci => false,
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir | Self::PowerBatteries => true,
Self::PowerBattery { .. } | Self::PowerAdapter { .. } => false,
Self::Dmi => true,
Self::DmiField(_) => false,
Self::ProcFile { .. } => false,
}
}
fn len(&self, acpi_ctx: &AcpiContext) -> Result<usize> {
@@ -65,8 +125,25 @@ impl HandleKind<'_> {
.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),
Self::PowerBatteries | Self::PowerBattery { .. } | Self::PowerAdapter { .. } => 2,
// 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)),
})
}
@@ -79,8 +156,91 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
handles: HandleMap::new(),
pci_fd: None,
socket,
kstop_fd: None,
power_cache: PowerCache::default(),
}
}
fn power_cache(&mut self) -> &PowerCache {
if self.power_cache.batteries.is_empty() && self.power_cache.adapter.is_none() {
self.power_cache = self.ctx.power_devices();
}
&self.power_cache
}
/// 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> {
@@ -123,6 +283,18 @@ fn parse_oem_table_id(hex: [u8; 16]) -> Option<[u8; 8]> {
])
}
/// 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)?;
@@ -184,9 +356,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
HandleKind::SchemeRoot => {
// TODO: arrayvec
let components = {
let mut v = arrayvec::ArrayVec::<&str, 3>::new();
let mut v = arrayvec::ArrayVec::<&str, 4>::new();
let it = path.split('/');
for component in it.take(3) {
for component in it.take(4) {
v.push(component);
}
@@ -197,6 +369,26 @@ impl SchemeSync for AcpiScheme<'_, '_> {
[""] => HandleKind::TopLevel,
["register_pci"] => HandleKind::RegisterPci,
["tables"] => HandleKind::Tables,
["thermal"] => HandleKind::Thermal,
["power"] => HandleKind::Power,
["dmi"] => HandleKind::Dmi,
["processor"] => HandleKind::Processor,
["power", "batteries"] => HandleKind::PowerBatteries,
["power", "batteries", name, file] => {
let file = match *file {
"state" => PowerFileKind::State,
"percentage" => PowerFileKind::Percentage,
_ => return Err(Error::new(ENOENT)),
};
HandleKind::PowerBattery { name: (*name).to_owned(), file }
}
["power", "adapters", name, file] => {
let file = match *file {
"online" => PowerFileKind::Online,
_ => return Err(Error::new(ENOENT)),
};
HandleKind::PowerAdapter { name: (*name).to_owned(), file }
}
["tables", table] => {
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
@@ -222,6 +414,37 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
}
["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)),
}
}
@@ -302,14 +525,63 @@ impl SchemeSync for AcpiScheme<'_, '_> {
return Err(Error::new(EBADF));
}
let src_buf = match &handle.kind {
// 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(),
_ => return Err(Error::new(EINVAL)),
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::PowerBattery { name, file } => {
dmi_buf = match file {
PowerFileKind::State => self.ctx.battery_state_text(name),
PowerFileKind::Percentage => self.ctx.battery_percentage_text(name),
PowerFileKind::Online => String::new(),
};
dmi_buf.as_bytes()
}
HandleKind::PowerAdapter { name, file } => {
dmi_buf = match file {
PowerFileKind::Online => self.ctx.adapter_online_text(name),
PowerFileKind::State | PowerFileKind::Percentage => String::new(),
};
dmi_buf.as_bytes()
}
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::PowerBatteries | 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);
@@ -332,7 +604,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
match &handle.kind {
HandleKind::TopLevel => {
const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
const TOPLEVEL_ENTRIES: &[&str] = &[
"tables", "symbols", "thermal", "power", "dmi", "processor",
];
for (idx, name) in TOPLEVEL_ENTRIES
.iter()
@@ -391,6 +665,104 @@ impl SchemeSync for AcpiScheme<'_, '_> {
})?;
}
}
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 cache = self.power_cache().clone();
if !cache.batteries.is_empty() {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: 1,
name: "batteries",
kind: DirentKind::Directory,
})?;
}
if cache.adapter.is_some() {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: 2,
name: "adapters",
kind: DirentKind::Directory,
})?;
}
}
HandleKind::PowerBatteries => {
let batteries = &self.power_cache().batteries;
for (idx, battery) in batteries.iter().enumerate().skip(opaque_offset as usize) {
buf.entry(DirEntry {
inode: 0,
next_opaque_id: idx as u64 + 1,
name: battery.as_str(),
kind: DirentKind::Directory,
})?;
}
}
HandleKind::PowerBattery { .. } => {
for (idx, file) in ["state", "percentage"].iter().enumerate().skip(opaque_offset as usize) {
buf.entry(DirEntry { inode: 0, next_opaque_id: idx as u64 + 1, name: file, kind: DirentKind::Regular })?;
}
}
HandleKind::PowerAdapter { .. } => {
buf.entry(DirEntry { inode: 0, next_opaque_id: 1, name: "online", kind: DirentKind::Regular })?;
}
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)),
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "amlserde"
description = "AML symbol serialization/deserialization library"
description = "Library for serializing AML symbols"
version = "0.0.1"
authors = ["Ron Williams"]
repository = "https://gitlab.redox-os.org/redox-os/drivers"
+3 -2
View File
@@ -129,6 +129,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
unimplemented!()
fn daemon(_daemon: daemon::Daemon, _pcid_handle: PciFunctionHandle) -> ! {
// AC'97 is an x86/x86_64 legacy audio bus; no other architectures are supported.
panic!("ac97d: only supported on x86 and x86_64");
}
+16 -14
View File
@@ -13,6 +13,12 @@ use common::timeout::Timeout;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::CallerCtx;
use redox_scheme::OpenResult;
use crate::hda::verbs::{
AC_VERB_PARAMETERS, AC_VERB_GET_CONNECT_SEL, AC_VERB_GET_CONNECT_LIST,
AC_VERB_GET_CONFIG_DEFAULT,
AC_PAR_NODE_COUNT, AC_PAR_AUDIO_WIDGET_CAP, AC_PAR_CONNLIST_LEN,
AC_PAR_FUNCTION_TYPE, AC_CLIST_LONG, AC_CLIST_LENGTH,
};
use scheme_utils::{FpathWriter, HandleMap};
use syscall::error::{Error, Result, EACCES, EBADF, EIO, ENODEV, EWOULDBLOCK};
@@ -254,7 +260,7 @@ impl IntelHDA {
node.addr = addr;
temp = self.cmd.cmd12(addr, 0xF00, 0x04)?;
temp = self.cmd.cmd12(addr, AC_VERB_PARAMETERS, AC_PAR_NODE_COUNT)?;
node.subnode_count = (temp & 0xff) as u16;
node.subnode_start = ((temp >> 16) & 0xff) as u16;
@@ -262,41 +268,37 @@ impl IntelHDA {
if addr == (0, 0) {
return Ok(node);
}
temp = self.cmd.cmd12(addr, 0xF00, 0x04)?;
temp = self.cmd.cmd12(addr, AC_VERB_PARAMETERS, AC_PAR_FUNCTION_TYPE)?;
node.function_group_type = (temp & 0xff) as u8;
temp = self.cmd.cmd12(addr, 0xF00, 0x09)?;
temp = self.cmd.cmd12(addr, AC_VERB_PARAMETERS, AC_PAR_AUDIO_WIDGET_CAP)?;
node.capabilities = temp as u32;
temp = self.cmd.cmd12(addr, 0xF00, 0x0E)?;
temp = self.cmd.cmd12(addr, AC_VERB_PARAMETERS, AC_PAR_CONNLIST_LEN)?;
node.conn_list_len = (temp & 0xFF) as u8;
node.connections = self.node_get_connection_list(&node)?;
node.connection_default = self.cmd.cmd12(addr, 0xF01, 0x00)? as u8;
node.connection_default = self.cmd.cmd12(addr, AC_VERB_GET_CONNECT_SEL, 0x00)? as u8;
node.config_default = self.cmd.cmd12(addr, 0xF1C, 0x00)? as u32;
node.config_default = self.cmd.cmd12(addr, AC_VERB_GET_CONFIG_DEFAULT, 0x00)? as u32;
Ok(node)
}
pub fn node_get_connection_list(&mut self, node: &HDANode) -> Result<Vec<WidgetAddr>> {
let len_field: u8 = (self.cmd.cmd12(node.addr, 0xF00, 0x0E)? & 0xFF) as u8;
// Highest bit is if addresses are represented in longer notation
// lower 7 is actual count
let count: u8 = len_field & 0x7F;
let use_long_addr: bool = (len_field >> 7) & 0x1 == 1;
let len_field: u8 = (self.cmd.cmd12(node.addr, AC_VERB_PARAMETERS, AC_PAR_CONNLIST_LEN)? & 0xFF) as u8;
let count: u8 = len_field & (AC_CLIST_LENGTH as u8);
let use_long_addr: bool = (len_field & (AC_CLIST_LONG as u8)) != 0;
let mut current: u8 = 0;
let mut list = Vec::<WidgetAddr>::new();
while current < count {
let response: u32 = (self.cmd.cmd12(node.addr, 0xF02, current)? & 0xFFFFFFFF) as u32;
let response: u32 = (self.cmd.cmd12(node.addr, AC_VERB_GET_CONNECT_LIST, current)? & 0xFFFFFFFF) as u32;
if use_long_addr {
for i in 0..2 {
+1
View File
@@ -4,6 +4,7 @@ pub mod common;
pub mod device;
pub mod node;
pub mod stream;
pub mod verbs;
pub use self::node::*;
pub use self::stream::*;
+206
View File
@@ -0,0 +1,206 @@
// HDA verb and parameter constants — ported from Linux 7.1 include/sound/hda_verbs.h.
// The hda_verbs.h header defines the HDA specification's verb IDs, parameter IDs,
// and capability bitfields for codec communication via CORB/RIRB.
// ---- Widget types (hda_verbs.h:25) ----
pub const AC_WID_AUD_OUT: u8 = 0x00;
pub const AC_WID_AUD_IN: u8 = 0x01;
pub const AC_WID_AUD_MIX: u8 = 0x02;
pub const AC_WID_AUD_SEL: u8 = 0x03;
pub const AC_WID_PIN: u8 = 0x04;
pub const AC_WID_POWER: u8 = 0x05;
pub const AC_WID_VOL_KNB: u8 = 0x06;
pub const AC_WID_BEEP: u8 = 0x07;
pub const AC_WID_VENDOR: u8 = 0x0f;
// ---- GET verbs (hda_verbs.h:40-84) ----
pub const AC_VERB_GET_STREAM_FORMAT: u32 = 0x0a00;
pub const AC_VERB_GET_AMP_GAIN_MUTE: u32 = 0x0b00;
pub const AC_VERB_GET_PROC_COEF: u32 = 0x0c00;
pub const AC_VERB_GET_COEF_INDEX: u32 = 0x0d00;
pub const AC_VERB_PARAMETERS: u32 = 0x0f00;
pub const AC_VERB_GET_CONNECT_SEL: u32 = 0x0f01;
pub const AC_VERB_GET_CONNECT_LIST: u32 = 0x0f02;
pub const AC_VERB_GET_PROC_STATE: u32 = 0x0f03;
pub const AC_VERB_GET_SDI_SELECT: u32 = 0x0f04;
pub const AC_VERB_GET_POWER_STATE: u32 = 0x0f05;
pub const AC_VERB_GET_CONV: u32 = 0x0f06;
pub const AC_VERB_GET_PIN_WIDGET_CONTROL: u32 = 0x0f07;
pub const AC_VERB_GET_UNSOLICITED_RESPONSE: u32 = 0x0f08;
pub const AC_VERB_GET_PIN_SENSE: u32 = 0x0f09;
pub const AC_VERB_GET_BEEP_CONTROL: u32 = 0x0f0a;
pub const AC_VERB_GET_EAPD_BTLENABLE: u32 = 0x0f0c;
pub const AC_VERB_GET_DIGI_CONVERT_1: u32 = 0x0f0d;
pub const AC_VERB_GET_VOLUME_KNOB_CONTROL: u32 = 0x0f0f;
pub const AC_VERB_GET_CONFIG_DEFAULT: u32 = 0x0f1c;
pub const AC_VERB_GET_SUBSYSTEM_ID: u32 = 0x0f20;
pub const AC_VERB_GET_STRIPE_CONTROL: u32 = 0x0f24;
pub const AC_VERB_GET_CVT_CHAN_COUNT: u32 = 0x0f2d;
pub const AC_VERB_GET_HDMI_DIP_SIZE: u32 = 0x0f2e;
pub const AC_VERB_GET_HDMI_ELDD: u32 = 0x0f2f;
pub const AC_VERB_GET_DEVICE_SEL: u32 = 0x0f35;
pub const AC_VERB_GET_DEVICE_LIST: u32 = 0x0f36;
// ---- SET verbs (hda_verbs.h:89-131) ----
pub const AC_VERB_SET_STREAM_FORMAT: u32 = 0x200;
pub const AC_VERB_SET_AMP_GAIN_MUTE: u32 = 0x300;
pub const AC_VERB_SET_PROC_COEF: u32 = 0x400;
pub const AC_VERB_SET_COEF_INDEX: u32 = 0x500;
pub const AC_VERB_SET_CONNECT_SEL: u32 = 0x701;
pub const AC_VERB_SET_PROC_STATE: u32 = 0x703;
pub const AC_VERB_SET_SDI_SELECT: u32 = 0x704;
pub const AC_VERB_SET_POWER_STATE: u32 = 0x705;
pub const AC_VERB_SET_CHANNEL_STREAMID: u32 = 0x706;
pub const AC_VERB_SET_PIN_WIDGET_CONTROL: u32 = 0x707;
pub const AC_VERB_SET_UNSOLICITED_ENABLE: u32 = 0x708;
pub const AC_VERB_SET_PIN_SENSE: u32 = 0x709;
pub const AC_VERB_SET_BEEP_CONTROL: u32 = 0x70a;
pub const AC_VERB_SET_EAPD_BTLENABLE: u32 = 0x70c;
pub const AC_VERB_SET_DIGI_CONVERT_1: u32 = 0x70d;
pub const AC_VERB_SET_DIGI_CONVERT_2: u32 = 0x70e;
pub const AC_VERB_SET_VOLUME_KNOB_CONTROL: u32 = 0x70f;
pub const AC_VERB_SET_CONFIG_DEFAULT_BYTES_0: u32 = 0x71c;
pub const AC_VERB_SET_CONFIG_DEFAULT_BYTES_1: u32 = 0x71d;
pub const AC_VERB_SET_CONFIG_DEFAULT_BYTES_2: u32 = 0x71e;
pub const AC_VERB_SET_CONFIG_DEFAULT_BYTES_3: u32 = 0x71f;
pub const AC_VERB_SET_EAPD: u32 = 0x788;
pub const AC_VERB_SET_CODEC_RESET: u32 = 0x7ff;
pub const AC_VERB_SET_STRIPE_CONTROL: u32 = 0x724;
pub const AC_VERB_SET_CVT_CHAN_COUNT: u32 = 0x72d;
// ---- Parameter IDs for AC_VERB_PARAMETERS (hda_verbs.h:136-154) ----
pub const AC_PAR_VENDOR_ID: u8 = 0x00;
pub const AC_PAR_SUBSYSTEM_ID: u8 = 0x01;
pub const AC_PAR_REV_ID: u8 = 0x02;
pub const AC_PAR_NODE_COUNT: u8 = 0x04;
pub const AC_PAR_FUNCTION_TYPE: u8 = 0x05;
pub const AC_PAR_AUDIO_FG_CAP: u8 = 0x08;
pub const AC_PAR_AUDIO_WIDGET_CAP: u8 = 0x09;
pub const AC_PAR_PCM: u8 = 0x0a;
pub const AC_PAR_STREAM: u8 = 0x0b;
pub const AC_PAR_PIN_CAP: u8 = 0x0c;
pub const AC_PAR_AMP_IN_CAP: u8 = 0x0d;
pub const AC_PAR_CONNLIST_LEN: u8 = 0x0e;
pub const AC_PAR_POWER_STATE: u8 = 0x0f;
pub const AC_PAR_PROC_CAP: u8 = 0x10;
pub const AC_PAR_GPIO_CAP: u8 = 0x11;
pub const AC_PAR_AMP_OUT_CAP: u8 = 0x12;
pub const AC_PAR_VOL_KNB_CAP: u8 = 0x13;
pub const AC_PAR_DEVLIST_LEN: u8 = 0x15;
pub const AC_PAR_HDMI_LPCM_CAP: u8 = 0x20;
// ---- Audio Widget Capabilities (hda_verbs.h:171-188) ----
pub const AC_WCAP_STEREO: u32 = 1 << 0;
pub const AC_WCAP_IN_AMP: u32 = 1 << 1;
pub const AC_WCAP_OUT_AMP: u32 = 1 << 2;
pub const AC_WCAP_AMP_OVRD: u32 = 1 << 3;
pub const AC_WCAP_FORMAT_OVRD: u32 = 1 << 4;
pub const AC_WCAP_STRIPE: u32 = 1 << 5;
pub const AC_WCAP_PROC_WID: u32 = 1 << 6;
pub const AC_WCAP_UNSOL_CAP: u32 = 1 << 7;
pub const AC_WCAP_CONN_LIST: u32 = 1 << 8;
pub const AC_WCAP_DIGITAL: u32 = 1 << 9;
pub const AC_WCAP_POWER: u32 = 1 << 10;
pub const AC_WCAP_LR_SWAP: u32 = 1 << 11;
pub const AC_WCAP_CP_CAPS: u32 = 1 << 12;
pub const AC_WCAP_CHAN_CNT_EXT: u32 = 7 << 13;
pub const AC_WCAP_DELAY: u32 = 0xf << 16;
pub const AC_WCAP_DELAY_SHIFT: u8 = 16;
pub const AC_WCAP_TYPE: u32 = 0xf << 20;
pub const AC_WCAP_TYPE_SHIFT: u8 = 20;
// ---- Pin Capabilities (hda_verbs.h:262-289) ----
pub const AC_PINCAP_IMP_SENSE: u32 = 1 << 0;
pub const AC_PINCAP_TRIG_REQ: u32 = 1 << 1;
pub const AC_PINCAP_PRES_DETECT: u32 = 1 << 2;
pub const AC_PINCAP_HP_DRV: u32 = 1 << 3;
pub const AC_PINCAP_OUT: u32 = 1 << 4;
pub const AC_PINCAP_IN: u32 = 1 << 5;
pub const AC_PINCAP_BALANCE: u32 = 1 << 6;
pub const AC_PINCAP_HDMI: u32 = 1 << 7;
pub const AC_PINCAP_DP: u32 = 1 << 24;
pub const AC_PINCAP_VREF: u32 = 0x37 << 8;
pub const AC_PINCAP_VREF_SHIFT: u8 = 8;
pub const AC_PINCAP_EAPD: u32 = 1 << 16;
pub const AC_PINCAP_HBR: u32 = 1 << 27;
// ---- Pin Widget Control (hda_verbs.h:400-411) ----
pub const AC_PINCTL_EPT: u32 = 0x3;
pub const AC_PINCTL_EPT_NATIVE: u8 = 0;
pub const AC_PINCTL_EPT_HBR: u8 = 3;
pub const AC_PINCTL_IN_EN: u8 = 1 << 5;
pub const AC_PINCTL_OUT_EN: u8 = 1 << 6;
pub const AC_PINCTL_HP_EN: u8 = 1 << 7;
// ---- Pin Sense (hda_verbs.h:414-416) ----
pub const AC_PINSENSE_IMPEDANCE_MASK: u32 = 0x7fff_ffff;
pub const AC_PINSENSE_PRESENCE: u32 = 1 << 31;
pub const AC_PINSENSE_ELDV: u32 = 1 << 30;
// ---- Power State (hda_verbs.h:311-330) ----
pub const AC_PWRST_D0SUP: u32 = 1 << 0;
pub const AC_PWRST_D1SUP: u32 = 1 << 1;
pub const AC_PWRST_D2SUP: u32 = 1 << 2;
pub const AC_PWRST_D3SUP: u32 = 1 << 3;
pub const AC_PWRST_D3COLDSUP: u32 = 1 << 4;
pub const AC_PWRST_S3D3COLDSUP: u32 = 1 << 29;
pub const AC_PWRST_CLKSTOP: u32 = 1 << 30;
pub const AC_PWRST_EPSS: u32 = 1 << 31;
pub const AC_PWRST_SETTING: u32 = 0xf;
pub const AC_PWRST_ACTUAL: u32 = 0xf << 4;
pub const AC_PWRST_ACTUAL_SHIFT: u8 = 4;
pub const AC_PWRST_D0: u32 = 0x00;
pub const AC_PWRST_D1: u32 = 0x01;
pub const AC_PWRST_D2: u32 = 0x02;
pub const AC_PWRST_D3: u32 = 0x03;
pub const AC_PWRST_ERROR: u32 = 1 << 8;
pub const AC_PWRST_CLK_STOP_OK: u32 = 1 << 9;
pub const AC_PWRST_SETTING_RESET: u32 = 1 << 10;
// ---- Amplifier (hda_verbs.h:366-380) ----
pub const AC_AMP_MUTE: u8 = 1 << 7;
pub const AC_AMP_GAIN: u8 = 0x7f;
pub const AC_AMP_GET_LEFT: u32 = 1 << 13;
pub const AC_AMP_GET_OUTPUT: u32 = 1 << 15;
// ---- PCM/Stream format (hda_verbs.h:191-235) ----
pub const AC_SUPPCM_BITS_8: u32 = 1 << 16;
pub const AC_SUPPCM_BITS_16: u32 = 1 << 17;
pub const AC_SUPPCM_BITS_20: u32 = 1 << 18;
pub const AC_SUPPCM_BITS_24: u32 = 1 << 19;
pub const AC_SUPPCM_BITS_32: u32 = 1 << 20;
pub const AC_SUPFMT_PCM: u32 = 1;
// ---- DIGITAL1 (hda_verbs.h:383-391) ----
pub const AC_DIG1_ENABLE: u32 = 1;
pub const AC_DIG1_V: u32 = 1 << 1;
pub const AC_DIG1_EMPHASIS: u32 = 1 << 3;
pub const AC_DIG1_COPYRIGHT: u32 = 1 << 4;
pub const AC_DIG1_NONAUDIO: u32 = 1 << 5;
pub const AC_DIG1_PROFESSIONAL: u32 = 1 << 6;
pub const AC_DIG1_LEVEL: u32 = 1 << 7;
// ---- Connection List (hda_verbs.h:307-308) ----
pub const AC_CLIST_LENGTH: u32 = 0x7f;
pub const AC_CLIST_LONG: u32 = 1 << 7;
// ---- Function Group (hda_verbs.h:161-168) ----
pub const AC_GRP_AUDIO_FUNCTION: u8 = 0x01;
pub const AC_GRP_MODEM_FUNCTION: u8 = 0x02;
pub const AC_FGT_UNSOL_CAP: u32 = 1 << 8;
pub const AC_AFG_OUT_DELAY: u32 = 0xf;
pub const AC_AFG_IN_DELAY: u32 = 0xf << 8;
pub const AC_AFG_BEEP_GEN: u32 = 1 << 16;
// ---- Stream Format (hda_verbs.h:221-235) ----
pub const AC_FMT_CHAN_SHIFT: u8 = 0;
pub const AC_FMT_CHAN_MASK: u32 = 0x0f;
pub const AC_FMT_BITS_SHIFT: u8 = 4;
pub const AC_FMT_BITS_MASK: u32 = 7 << 4;
pub const AC_FMT_BITS_8: u32 = 0;
pub const AC_FMT_BITS_16: u32 = 1 << 4;
pub const AC_FMT_BITS_20: u32 = 2 << 4;
pub const AC_FMT_BITS_24: u32 = 3 << 4;
pub const AC_FMT_BITS_32: u32 = 4 << 4;
pub const AC_FMT_BASE_48K: u32 = 0;
pub const AC_FMT_BASE_44K: u32 = 1 << 14;
+1 -5
View File
@@ -1,4 +1,3 @@
use common::MemoryType;
use redox_scheme::scheme::register_sync_scheme;
use redox_scheme::Socket;
use scheme_utils::ReadinessBased;
@@ -39,10 +38,7 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
log::info!("IHDA {}", pci_config.func.display());
let address = unsafe { pcid_handle.map_bar(0, MemoryType::Uncacheable) }
.ptr
.as_ptr()
.expose_provenance();
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
+3 -2
View File
@@ -113,6 +113,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn daemon(daemon: daemon::Daemon) -> ! {
unimplemented!()
fn daemon(_daemon: daemon::Daemon) -> ! {
// Sound Blaster 16 is an x86/x86_64 legacy ISA card; no other architectures are supported.
panic!("sb16d: only supported on x86 and x86_64");
}
+45 -6
View File
@@ -67,17 +67,16 @@ pub fn memory_root_fd() -> &'static libredox::Fd {
/// aarch64 and x86 have very different cache-coherency rules, so this API as written is likely
/// not sufficient to describe the memory caching behavior in a cross-platform manner. As such,
/// consider this API unstable.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)] // Make sure the discriminants match mmap_prep in pcid/src/scheme.rs
#[derive(Clone, Copy, Debug)]
pub enum MemoryType {
/// A region of memory that implements Write-back caching.
///
/// In write-back caching, the processor will first store data in its local cache, and then
/// flush it to the actual storage location at regular intervals, or as applications access
/// the data.
Writeback = 0b00,
Writeback,
/// A region of memory that does not implement caching. Writes to these regions are immediate.
Uncacheable = 0b01,
Uncacheable,
/// A region of memory that implements write combining.
///
/// Write combining memory regions store all writes in a temporary buffer called a Write
@@ -85,10 +84,10 @@ pub enum MemoryType {
/// released to the memory location in an unspecified order. Write-Combine memory does not
/// guarantee that the order at which you write to it is the order at which those writes are
/// committed to memory.
WriteCombining = 0b10,
WriteCombining,
/// Memory stored in an intermediate Write Combine Buffer and released later
/// Memory-Mapped I/O. This is an aarch64-specific term.
DeviceMemory = 0b11,
DeviceMemory,
}
impl Default for MemoryType {
fn default() -> Self {
@@ -330,3 +329,43 @@ impl VirtaddrTranslationHandle {
Ok(usize::from_ne_bytes(buf))
}
}
#[cfg(test)]
mod physmap_type_assertions {
use super::*;
/// If `physmap` ever changes its error type, this test fails to
/// compile and surfaces the regression at `cargo check` time on the
/// host rather than during a full Redox cross-build.
///
/// The check uses `std::mem::size_of_val` on a constructed error
/// value. `libredox::error::Error` is `pub struct { errno: u16 }`,
/// so its `size_of_val` is exactly `size_of::<u16>() == 2`. A
/// future change to a different error type (e.g.
/// `syscall::error::Error { errno: i32 }` which would be 4 bytes)
/// would change this constant and force a maintainer to update
/// both the assertion and the downstream `map_err` adapters in
/// lockstep — making the type drift visible at review time rather
/// than at build time.
#[test]
fn physmap_returns_libredox_error_result() {
// Reference layout: `libredox::error::Error { errno: u16 }`.
// If this changes the size assertion below must be updated.
const EXPECTED_SIZE: usize = std::mem::size_of::<u16>();
// Reference error type used at the assertion site.
let reference: libredox::error::Error = libredox::error::Error::new(0);
assert_eq!(std::mem::size_of_val(&reference), EXPECTED_SIZE);
// Coercion check: the function pointer signature must be
// mappable to the expected return type. If physmap's error
// type drifts (e.g. from `libredox::error::Error` to
// `syscall::error::Error` or `std::io::Error`), this coercion
// fails to compile. We never call `f` — we only borrow its
// signature.
let _f: PhysmapSig = physmap;
}
/// Concrete signature of `physmap` for the assertion above.
type PhysmapSig = unsafe fn(usize, usize, Prot, MemoryType) -> libredox::error::Result<*mut ()>;
}
+1 -2
View File
@@ -187,8 +187,7 @@ impl<Hw: Hardware> LocalExecutor<Hw> {
if self.intx {
let mut buf = [0_u8; core::mem::size_of::<usize>()];
if (&self.irq_handle).read(&mut buf).unwrap() != 0 {
let amount = (&self.irq_handle).write(&buf).unwrap();
assert!(amount == core::mem::size_of::<usize>());
(&self.irq_handle).write(&buf).unwrap();
}
}
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "gpiod"
description = "GPIO controller registry daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
+496
View File
@@ -0,0 +1,496 @@
use std::collections::BTreeMap;
use std::process;
use anyhow::{Context, Result};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use serde::{Deserialize, Serialize};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GpioControllerInfo {
pub id: u32,
pub name: String,
pub pin_count: usize,
pub supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
ReadPin { controller_id: u32, pin: u32 },
WritePin { controller_id: u32, pin: u32, value: bool },
ConfigurePin { controller_id: u32, pin: u32, config: PinConfig },
ListControllers,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct PinConfig {
pub direction: PinDirection,
pub pull: PullMode,
pub interrupt_mode: Option<InterruptMode>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PinDirection {
Input,
Output,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum PullMode {
None,
Up,
Down,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum InterruptMode {
EdgeRising,
EdgeFalling,
EdgeBoth,
LevelHigh,
LevelLow,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Controllers(Vec<GpioControllerInfo>),
Controller(GpioControllerInfo),
PinValue(bool),
Ack,
Error(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum PinOpKind {
Read,
Write,
Configure,
}
enum Handle {
SchemeRoot,
Register { pending: Vec<u8> },
Provider { controller_id: u32, pending: Vec<u8> },
ControllersDir { pending: Vec<u8> },
ControllerDetail { id: u32, pending: Vec<u8> },
PinOp { kind: PinOpKind, pending: Vec<u8> },
}
struct ControllerEntry {
info: GpioControllerInfo,
provider_handle: usize,
}
struct GpioDaemon {
handles: HandleMap<Handle>,
controllers: BTreeMap<u32, ControllerEntry>,
next_id: u32,
}
impl GpioDaemon {
fn new() -> Self {
Self {
handles: HandleMap::new(),
controllers: BTreeMap::new(),
next_id: 0,
}
}
fn controller_list(&self) -> Vec<GpioControllerInfo> {
self.controllers
.values()
.map(|entry| entry.info.clone())
.collect()
}
fn serialize_response(response: &GpioControlResponse) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(response)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("gpiod: failed to serialize control response: {err}");
SysError::new(EINVAL)
})
}
fn deserialize_request(buf: &[u8]) -> syscall::Result<GpioControlRequest> {
let text = std::str::from_utf8(buf).map_err(|err| {
log::warn!("gpiod: invalid UTF-8 request payload: {err}");
SysError::new(EINVAL)
})?;
ron::from_str(text).map_err(|err| {
log::warn!("gpiod: failed to decode control request: {err}");
SysError::new(EINVAL)
})
}
fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> {
let pending = Self::serialize_response(&response)?;
Self::set_pending_bytes(handle, pending)
}
fn set_pending_bytes(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
match handle {
Handle::Register { pending: slot }
| Handle::Provider { pending: slot, .. }
| Handle::ControllersDir { pending: slot }
| Handle::ControllerDetail { pending: slot, .. }
| Handle::PinOp { pending: slot, .. } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Register { pending }
| Handle::Provider { pending, .. }
| Handle::ControllersDir { pending }
| Handle::ControllerDetail { pending, .. }
| Handle::PinOp { pending, .. } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
fn validate_pin_target(
&self,
controller_id: u32,
pin: u32,
) -> std::result::Result<GpioControllerInfo, String> {
let entry = self
.controllers
.get(&controller_id)
.ok_or_else(|| format!("unknown controller {controller_id}"))?;
if usize::try_from(pin)
.ok()
.filter(|pin| *pin < entry.info.pin_count)
.is_none()
{
return Err(format!(
"pin {pin} is out of range for controller {} (pin_count={})",
entry.info.name, entry.info.pin_count
));
}
Ok(entry.info.clone())
}
}
impl SchemeSync for GpioDaemon {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
let handle = self.handles.get(dirfd)?;
let segments = path.trim_matches('/');
let new_handle = match handle {
Handle::SchemeRoot => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let mut parts = segments.split('/');
match parts.next() {
Some("register") if parts.next().is_none() => Handle::Register {
pending: Vec::new(),
},
Some("controllers") => match parts.next() {
None => Handle::ControllersDir {
pending: Vec::new(),
},
Some(id) if parts.next().is_none() => Handle::ControllerDetail {
id: id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
pending: Vec::new(),
},
_ => return Err(SysError::new(EINVAL)),
},
Some("read_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Read,
pending: Vec::new(),
},
Some("write_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Write,
pending: Vec::new(),
},
Some("configure_pin") if parts.next().is_none() => Handle::PinOp {
kind: PinOpKind::Configure,
pending: Vec::new(),
},
_ => return Err(SysError::new(ENOENT)),
}
}
Handle::ControllersDir { .. } => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
Handle::ControllerDetail {
id: segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EACCES)),
};
let fd = self.handles.insert(new_handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let controllers = self.controller_list();
let detail = match self.handles.get(id)? {
Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()),
_ => None,
};
let handle = self.handles.get_mut(id)?;
match handle {
Handle::ControllersDir { pending } if pending.is_empty() => {
*pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?;
}
Handle::ControllerDetail { id, pending } if pending.is_empty() => {
let info = detail.ok_or(SysError::new(ENOENT))?;
*pending = Self::serialize_response(&GpioControlResponse::Controller(info))?;
log::debug!("gpiod: served controller detail for id={id}");
}
_ => {}
}
Self::copy_pending(handle, buf, offset)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let request = Self::deserialize_request(buf)?;
match request {
GpioControlRequest::RegisterController { mut info } => {
if !matches!(self.handles.get(id)?, Handle::Register { .. }) {
return Err(SysError::new(EINVAL));
}
let controller_id = self.next_id;
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
info.id = controller_id;
self.controllers.insert(
controller_id,
ControllerEntry {
info: info.clone(),
provider_handle: id,
},
);
let handle = self.handles.get_mut(id)?;
*handle = Handle::Provider {
controller_id,
pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered {
id: controller_id,
})?,
};
log::info!(
"RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}",
info.id,
info.name,
info.pin_count,
info.supports_interrupt,
);
Ok(buf.len())
}
GpioControlRequest::ListControllers => {
let controllers = self.controller_list();
let handle = self.handles.get_mut(id)?;
Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?;
Ok(buf.len())
}
GpioControlRequest::ReadPin { controller_id, pin } => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Read,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub",
controller_id,
info.name,
pin,
);
Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
GpioControlRequest::WritePin {
controller_id,
pin,
value,
} => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Write,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub",
controller_id,
info.name,
pin,
value,
);
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
GpioControlRequest::ConfigurePin {
controller_id,
pin,
config,
} => {
let validation = self.validate_pin_target(controller_id, pin);
let handle = self.handles.get_mut(id)?;
match handle {
Handle::PinOp {
kind: PinOpKind::Configure,
..
} => {
match validation {
Ok(info) => {
log::info!(
"RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub",
controller_id,
info.name,
pin,
config.direction,
config.pull,
config.interrupt_mode,
);
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
}
Err(message) => {
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
}
}
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
}
}
fn on_close(&mut self, id: usize) {
let Some(handle) = self.handles.remove(id) else {
return;
};
if let Handle::Provider { controller_id, .. } = handle {
if let Some(entry) = self.controllers.remove(&controller_id) {
log::info!(
"RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}",
controller_id,
entry.info.name,
entry.provider_handle,
);
}
}
}
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
let socket = Socket::create().context("failed to create gpio scheme socket")?;
let mut scheme = GpioDaemon::new();
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish gpio scheme root")?;
log::info!("RB_GPIOD_SCHEMA version=1");
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process gpiod requests")?;
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("gpiod: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn main() {
common::setup_logging(
"gpio",
"gpio",
"gpiod",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
@@ -0,0 +1,21 @@
[package]
name = "i2c-gpio-expanderd"
description = "I2C GPIO expander bridge daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
[lints]
workspace = true
+454
View File
@@ -0,0 +1,454 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{GpioDescriptor, I2cSerialBusDescriptor, ResourceDescriptor};
use anyhow::{Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferResponse, I2cTransferSegment,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ExpanderResources {
i2c: I2cSerialBusDescriptor,
pin_count: usize,
gpio_int_count: usize,
gpio_io_count: usize,
}
#[derive(Debug)]
struct ExpanderDescriptor {
device: String,
hid: String,
resources: ExpanderResources,
}
struct RegisteredExpander {
_registration: File,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct GpioControllerInfo {
id: u32,
name: String,
pin_count: usize,
supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Error(String),
}
fn main() {
common::setup_logging(
"gpio",
"i2c-gpio-expander",
"i2c-gpio-expanderd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("i2c-gpio-expanderd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
let expanders = discover_expanders().context("failed to discover ACPI I2C GPIO expanders")?;
if expanders.is_empty() {
log::info!("i2c-gpio-expanderd: no probable ACPI I2C GPIO expanders found");
}
let adapters = list_i2c_adapters().unwrap_or_else(|err| {
log::warn!("i2c-gpio-expanderd: unable to query i2cd adapters: {err:#}");
Vec::new()
});
let mut registered = Vec::new();
for expander in expanders {
match register_expander(expander, &adapters) {
Ok(expander) => registered.push(expander),
Err(err) => log::warn!("i2c-gpio-expanderd: expander registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("i2c-gpio-expanderd: registered {} expander(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_expanders() -> Result<Vec<ExpanderDescriptor>> {
let mut matched = BTreeMap::new();
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
log::debug!("i2c-gpio-expanderd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if is_excluded_device_id(&id) {
continue;
}
let Some(device) = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned)
else {
continue;
};
let resources = match read_expander_resources(&device) {
Ok(resources) => resources,
Err(err) => {
log::debug!("i2c-gpio-expanderd: skipping {device}: {err:#}");
continue;
}
};
if resources.gpio_int_count == 0 && resources.gpio_io_count == 0 {
continue;
}
matched.entry(device).or_insert((id, resources));
}
let mut expanders = Vec::new();
for (device, (hid, resources)) in matched {
expanders.push(ExpanderDescriptor {
device,
hid,
resources,
});
}
Ok(expanders)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"i2c-gpio-expanderd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("i2c-gpio-expanderd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_expander_resources(device: &str) -> Result<ExpanderResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut i2c = None;
let mut pin_count = 0usize;
let mut gpio_int_count = 0usize;
let mut gpio_io_count = 0usize;
for resource in resources {
match resource {
ResourceDescriptor::I2cSerialBus(bus) if i2c.is_none() => i2c = Some(bus),
ResourceDescriptor::GpioInt(descriptor) => {
gpio_int_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
}
ResourceDescriptor::GpioIo(descriptor) => {
gpio_io_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(&descriptor));
}
_ => {}
}
}
Ok(ExpanderResources {
i2c: i2c.context("no I2cSerialBus resource was found")?,
pin_count,
gpio_int_count,
gpio_io_count,
})
}
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
descriptor
.pins
.iter()
.copied()
.max()
.map(|pin| usize::from(pin).saturating_add(1))
.unwrap_or(0)
}
fn is_excluded_device_id(id: &str) -> bool {
matches!(
id,
"PNP0C50"
| "ACPI0C50"
| "INT34C5"
| "INTC1055"
| "INT33C2"
| "INT33C3"
| "INT3432"
| "INT3433"
| "INTC10EF"
| "AMDI0010"
| "AMDI0019"
| "AMDI0510"
| "PNP0CA0"
| "AMDI0042"
) || id.starts_with("ELAN")
|| id.starts_with("CYAP")
|| id.starts_with("SYNA")
}
fn register_expander(expander: ExpanderDescriptor, adapters: &[I2cAdapterInfo]) -> Result<RegisteredExpander> {
let ExpanderDescriptor {
device,
hid,
resources,
} = expander;
let adapter_name = resources
.i2c
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| String::from("ACPI-I2C"));
let adapter = match match_i2c_adapter(adapters, &adapter_name) {
Some(adapter) => Some(adapter.clone()),
None => {
log::warn!(
"i2c-gpio-expanderd: unable to resolve I2C adapter {} for {}",
adapter_name,
device,
);
None
}
};
if let Some(adapter) = adapter.as_ref() {
if let Err(err) = probe_expander(adapter, &adapter_name, resources.i2c.slave_address) {
log::warn!(
"i2c-gpio-expanderd: expander {} probe on {}@{:04x} failed: {err:#}",
device,
adapter_name,
resources.i2c.slave_address,
);
}
}
let info = GpioControllerInfo {
id: 0,
name: format!("i2c-gpio-expander:{device}"),
pin_count: resources.pin_count,
supports_interrupt: resources.gpio_int_count > 0,
};
let mut registration = register_with_gpiod(&info)
.with_context(|| format!("failed to register {device} with gpiod"))?;
let response = read_gpio_registration_response(&mut registration)
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
match response {
GpioControlResponse::ControllerRegistered { id } => {
log::info!(
"RB_I2C_GPIO_EXPANDERD_DEVICE device={} hid={} controller_id={} adapter={} addr={:04x} pin_count={} gpio_int={} gpio_io={}",
device,
hid,
id,
adapter_name,
resources.i2c.slave_address,
info.pin_count,
resources.gpio_int_count,
resources.gpio_io_count,
);
}
GpioControlResponse::Error(message) => {
anyhow::bail!("gpiod rejected expander {device}: {message}");
}
}
Ok(RegisteredExpander {
_registration: registration,
})
}
fn list_i2c_adapters() -> Result<Vec<I2cAdapterInfo>> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/adapters")
.context("failed to open /scheme/i2c/adapters")?;
let payload = ron::ser::to_string(&I2cControlRequest::ListAdapters)
.context("failed to encode I2C list-adapters request")?;
file.write_all(payload.as_bytes())
.context("failed to request I2C adapter list")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::AdapterList(adapters) => Ok(adapters),
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
other => anyhow::bail!("unexpected i2cd list-adapters response: {other:?}"),
}
}
fn match_i2c_adapter<'a>(adapters: &'a [I2cAdapterInfo], wanted: &str) -> Option<&'a I2cAdapterInfo> {
adapters
.iter()
.find(|adapter| adapter.name == wanted)
.or_else(|| adapters.iter().find(|adapter| adapter.name.ends_with(wanted)))
.or_else(|| adapters.iter().find(|adapter| wanted.ends_with(&adapter.name)))
}
fn probe_expander(adapter: &I2cAdapterInfo, adapter_name: &str, address: u16) -> Result<I2cTransferResponse> {
let request = I2cTransferRequest {
adapter: adapter_name.to_string(),
segments: vec![I2cTransferSegment::read(address, 1)],
stop: true,
};
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
let payload = ron::ser::to_string(&I2cControlRequest::Transfer {
adapter_id: adapter.id,
request,
})
.context("failed to encode I2C expander probe request")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C expander probe request")?;
let response = read_i2c_control_response(&mut file)?;
match response {
I2cControlResponse::TransferResult(result) => {
if !result.ok {
let detail = result
.error
.clone()
.unwrap_or_else(|| String::from("unknown I2C transfer failure"));
anyhow::bail!("I2C probe failed: {detail}");
}
Ok(result)
}
I2cControlResponse::Error(message) => anyhow::bail!("i2cd returned an error: {message}"),
other => anyhow::bail!("unexpected I2C transfer response: {other:?}"),
}
}
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/gpio/register")
.context("failed to open /scheme/gpio/register")?;
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
.context("failed to encode GPIO controller registration")?;
file.write_all(payload.as_bytes())
.context("failed to send GPIO controller registration")?;
Ok(file)
}
fn read_gpio_registration_response(file: &mut File) -> Result<GpioControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read GPIO registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode GPIO registration response")
}
fn read_i2c_control_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C control response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C control response was not UTF-8")?;
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(I2cControlResponse::AdapterList(Vec::new()));
}
ron::from_str(trimmed).context("failed to decode I2C control response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "intel-gpiod"
description = "Intel ACPI GPIO registrar daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
[lints]
workspace = true
+401
View File
@@ -0,0 +1,401 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, GpioDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use serde::{Deserialize, Serialize};
const SUPPORTED_IDS: &[&str] = &["INT34C5", "INTC1055"];
const PADNFGPIO_OWN_BASE: usize = 0x20;
const PADNFGPIO_PADCFG_BASE: usize = 0x700;
const GPI_INT_STATUS: usize = 0x100;
const GPI_INT_EN: usize = 0x120;
const INTEL_GPIO_MMIO_WINDOW: usize = PADNFGPIO_PADCFG_BASE + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
pin_count: usize,
supports_interrupt: bool,
gpio_int_count: usize,
gpio_io_count: usize,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
struct GpioControllerInfo {
id: u32,
name: String,
pin_count: usize,
supports_interrupt: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlRequest {
RegisterController { info: GpioControllerInfo },
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
enum GpioControlResponse {
ControllerRegistered { id: u32 },
Error(String),
}
fn main() {
common::setup_logging(
"gpio",
"intel-gpio",
"intel-gpiod",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("intel-gpiod: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers =
discover_controllers(SUPPORTED_IDS).context("failed to discover Intel GPIO controllers")?;
if controllers.is_empty() {
log::info!("intel-gpiod: no supported Intel GPIO ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller(controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("intel-gpiod: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("intel-gpiod: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
log::debug!("intel-gpiod: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"intel-gpiod: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("intel-gpiod: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut supports_interrupt = false;
let mut gpio_int_count = 0usize;
let mut gpio_io_count = 0usize;
let mut pin_count = 0usize;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((
*address as usize,
(*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW),
));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(INTEL_GPIO_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(INTEL_GPIO_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(INTEL_GPIO_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) => {
supports_interrupt |= !interrupts.is_empty();
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. }) => {
supports_interrupt |= !interrupts.is_empty();
}
ResourceDescriptor::GpioInt(descriptor) => {
gpio_int_count += 1;
supports_interrupt = true;
pin_count = pin_count.max(pin_count_from_descriptor(descriptor));
}
ResourceDescriptor::GpioIo(descriptor) => {
gpio_io_count += 1;
pin_count = pin_count.max(pin_count_from_descriptor(descriptor));
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
pin_count,
supports_interrupt,
gpio_int_count,
gpio_io_count,
})
}
fn pin_count_from_descriptor(descriptor: &GpioDescriptor) -> usize {
descriptor
.pins
.iter()
.copied()
.max()
.map(|pin| usize::from(pin).saturating_add(1))
.unwrap_or(0)
}
fn register_controller(controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"intel-gpiod: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"intel-gpiod: discovered {device} hid={hid} mmio={:#x}+{:#x} pin_count={} gpio_int={} gpio_io={} supports_interrupt={}",
resources.mmio_base,
resources.mmio_len,
resources.pin_count,
resources.gpio_int_count,
resources.gpio_io_count,
resources.supports_interrupt,
);
log::debug!(
"intel-gpiod: register model own={PADNFGPIO_OWN_BASE:#x} padcfg={PADNFGPIO_PADCFG_BASE:#x} gpi_int_status={GPI_INT_STATUS:#x} gpi_int_en={GPI_INT_EN:#x}",
);
let info = GpioControllerInfo {
id: 0,
name: format!("intel-gpio:{device}"),
pin_count: resources.pin_count,
supports_interrupt: resources.supports_interrupt,
};
let mut registration = register_with_gpiod(&info)
.with_context(|| format!("failed to register {device} with gpiod"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read gpiod registration response for {device}"))?;
match response {
GpioControlResponse::ControllerRegistered { id } => {
log::info!(
"RB_INTEL_GPIOD_DEVICE device={} hid={} controller_id={} pin_count={} supports_interrupt={}",
device,
hid,
id,
info.pin_count,
info.supports_interrupt,
);
}
GpioControlResponse::Error(message) => {
anyhow::bail!("gpiod rejected Intel GPIO controller {device}: {message}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_with_gpiod(info: &GpioControllerInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/gpio/register")
.context("failed to open /scheme/gpio/register")?;
let payload = ron::ser::to_string(&GpioControlRequest::RegisterController { info: info.clone() })
.context("failed to encode GPIO controller registration")?;
file.write_all(payload.as_bytes())
.context("failed to send GPIO controller registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<GpioControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read GPIO registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("GPIO registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode GPIO registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
-1
View File
@@ -8,7 +8,6 @@ edition = "2021"
drm.workspace = true
orbclient.workspace = true
ransid.workspace = true
psf-rs.workspace = true
graphics-ipc = { path = "../graphics-ipc" }
+40 -146
View File
@@ -1,14 +1,16 @@
extern crate ransid;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
use std::{cmp, io, mem, ptr};
use drm::buffer::{Buffer, DrmFourcc};
use drm::control::{connector, crtc, framebuffer, ClipRect, Device, Mode};
use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
use orbclient::FONT;
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct Damage {
pub x: u32,
pub y: u32,
@@ -49,7 +51,7 @@ impl Damage {
pub struct V2DisplayMap {
pub display_handle: V2GraphicsHandle,
pub connector: connector::Handle,
connector: connector::Handle,
crtc: crtc::Handle,
fb: framebuffer::Handle,
pub buffer: CpuBackedBuffer,
@@ -57,7 +59,8 @@ pub struct V2DisplayMap {
impl V2DisplayMap {
pub fn new(display_handle: V2GraphicsHandle) -> io::Result<Self> {
let connector_info = display_handle.first_display().unwrap();
let connector = display_handle.first_display().unwrap();
let connector_info = display_handle.get_connector(connector, true).unwrap();
let mode = connector_info.modes()[0];
let (width, height) = mode.size();
@@ -78,17 +81,11 @@ impl V2DisplayMap {
)?;
let fb = display_handle.add_framebuffer(buffer.buffer(), 32, 32)?;
display_handle.set_crtc(
crtc,
Some(fb),
(0, 0),
&[connector_info.handle()],
Some(mode),
)?;
display_handle.set_crtc(crtc, Some(fb), (0, 0), &[connector], Some(mode))?;
Ok(Self {
display_handle,
connector: connector_info.handle(),
connector,
crtc,
fb,
buffer,
@@ -131,68 +128,15 @@ struct DisplayMap {
height: usize,
}
#[derive(Clone)]
pub struct ConsoleFont {
glyphs: Vec<u8>,
width: usize,
height: usize,
}
impl ConsoleFont {
pub fn new(glyphs: Vec<u8>, width: usize, height: usize) -> ConsoleFont {
ConsoleFont {
glyphs,
width,
height,
}
}
pub fn from_psf(data: &[u8]) -> ConsoleFont {
let font = psf_rs::Font::load(data);
let width = font.header.glyph_width as usize;
let height = font.header.glyph_height as usize;
let bytes_per_row = (width + 7) / 8;
let glyph_count = font.header.length as usize;
let mut glyphs = vec![0u8; glyph_count * height * bytes_per_row];
for i in 0..glyph_count {
if let Some(c) = char::from_u32(i as u32) {
let glyph_offset = i * height * bytes_per_row;
font.display_glyph(c, |bit, x, y| {
if bit != 0 {
let byte_offset =
glyph_offset + (y as usize) * bytes_per_row + ((x as usize) / 8);
let bit_offset = 7 - (x % 8);
if byte_offset < glyphs.len() {
glyphs[byte_offset] |= 1 << bit_offset;
}
}
});
}
}
Self {
glyphs,
width,
height,
}
}
}
pub struct TextScreen {
console: ransid::Console,
font: ConsoleFont,
}
impl TextScreen {
pub fn new(font: Option<ConsoleFont>) -> TextScreen {
pub fn new() -> TextScreen {
TextScreen {
// Width and height will be filled in on the next write to the console
console: ransid::Console::new(0, 0),
font: font.unwrap_or_else(|| ConsoleFont::new(FONT.to_vec(), 8, 16)),
}
}
@@ -261,19 +205,18 @@ impl TextScreen {
x: usize,
y: usize,
character: char,
font: &ConsoleFont,
color: u32,
_bold: bool,
_italic: bool,
) {
if x + font.width <= map.width && y + font.height <= map.height {
if x + 8 <= map.width && y + 16 <= map.height {
let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4;
let font_i = font.height * (character as usize);
if font_i + font.height <= font.glyphs.len() {
for row in 0..font.height {
let row_data = font.glyphs[font_i + row];
for col in 0..font.width {
let font_i = 16 * (character as usize);
if font_i + 16 <= FONT.len() {
for row in 0..16 {
let row_data = FONT[font_i + row];
for col in 0..8 {
if (row_data >> (7 - col)) & 1 == 1 {
unsafe {
*((dst + col * 4) as *mut u32) = color;
@@ -296,29 +239,18 @@ impl TextScreen {
) -> Damage {
let map = unsafe { &mut map.console_map() };
let mut min_changed_x = map.width;
let mut max_changed_x = 0;
let mut min_changed_y = map.height;
let mut max_changed_y = 0;
let mut col_changed = |col| {
if col < min_changed_x {
min_changed_x = col;
}
if col > max_changed_x {
max_changed_x = col;
}
};
let mut min_changed = map.height;
let mut max_changed = 0;
let mut line_changed = |line| {
if line < min_changed_y {
min_changed_y = line;
if line < min_changed {
min_changed = line;
}
if line > max_changed_y {
max_changed_y = line;
if line > max_changed {
max_changed = line;
}
};
self.console
.resize(map.width / self.font.width, map.height / self.font.height);
self.console.resize(map.width / 8, map.height / 16);
if self.console.state.x >= self.console.state.w {
self.console.state.x = self.console.state.w - 1;
}
@@ -332,14 +264,7 @@ impl TextScreen {
{
let x = self.console.state.x;
let y = self.console.state.y;
Self::invert(
map,
x * self.font.width,
y * self.font.height,
self.font.width,
self.font.height,
);
col_changed(x);
Self::invert(map, x * 8, y * 16, 8, 16);
line_changed(y);
}
@@ -352,35 +277,15 @@ impl TextScreen {
bold,
..
} => {
Self::char(
map,
x * self.font.width,
y * self.font.height,
c,
&self.font,
color.as_rgb(),
bold,
false,
);
col_changed(x);
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
line_changed(y);
}
ransid::Event::Input { data } => input.extend(data),
ransid::Event::Rect { x, y, w, h, color } => {
Self::rect(
map,
x * self.font.width,
y * self.font.height,
w * self.font.width,
h * self.font.height,
color.as_rgb(),
);
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
for y2 in y..y + h {
line_changed(y2);
}
for x2 in x..x + w {
col_changed(x2);
}
}
ransid::Event::ScreenBuffer { .. } => (),
ransid::Event::Move {
@@ -397,13 +302,11 @@ impl TextScreen {
for raw_y in 0..h {
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
for pixel_y in 0..self.font.height {
for pixel_y in 0..16 {
{
let off_from = ((from_y + y) * self.font.height + pixel_y) * width
+ from_x * self.font.width;
let off_to = ((to_y + y) * self.font.height + pixel_y) * width
+ to_x * self.font.width;
let len = w * self.font.width;
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
let len = w * 8;
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
unsafe {
@@ -417,9 +320,7 @@ impl TextScreen {
}
}
}
for col in to_x..to_x + w {
col_changed(col);
}
line_changed(to_y + y);
}
}
@@ -433,23 +334,16 @@ impl TextScreen {
{
let x = self.console.state.x;
let y = self.console.state.y;
Self::invert(
map,
x * self.font.width,
y * self.font.height,
self.font.width,
self.font.height,
);
Self::invert(map, x * 8, y * 16, 8, 16);
line_changed(y);
}
let width = map.width.try_into().unwrap();
let damage = Damage {
x: u32::try_from(min_changed_x).unwrap() * self.font.width as u32,
y: u32::try_from(min_changed_y).unwrap() * self.font.height as u32,
width: u32::try_from(max_changed_x.saturating_sub(min_changed_x) + 1).unwrap()
* self.font.width as u32,
height: u32::try_from(max_changed_y.saturating_sub(min_changed_y) + 1).unwrap()
* self.font.height as u32,
x: 0,
y: u32::try_from(min_changed).unwrap() * 16,
width,
height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16,
};
damage
@@ -503,12 +397,12 @@ impl TextScreen {
copy_row(old_map, new_map, row, row);
}
} else {
let deleted_rows = (old_map.height - new_map.height).div_ceil(self.font.height);
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
for row in 0..new_map.height {
if row + (deleted_rows + 1) * self.font.height >= old_map.height {
if row + (deleted_rows + 1) * 16 >= old_map.height {
break;
}
copy_row(old_map, new_map, row + deleted_rows * self.font.height, row);
copy_row(old_map, new_map, row + deleted_rows * 16, row);
}
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
}
+2 -2
View File
@@ -2,10 +2,10 @@
name = "driver-graphics"
description = "Shared video and graphics code library"
version = "0.1.0"
edition = "2024"
edition = "2021"
[dependencies]
drm-fourcc.workspace = true
drm-fourcc = "2.2.0"
drm-sys.workspace = true
edid.workspace = true #TODO: edid is abandoned, fork it and maintain?
log.workspace = true
@@ -1,145 +0,0 @@
use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use drm_fourcc::DrmFourcc;
use drm_sys::{DRM_MODE_CURSOR_BO, DRM_MODE_CURSOR_MOVE};
use syscall::{EINVAL, ENXIO, Error};
use crate::kms::objects::{KmsFramebuffer, KmsObjectId, KmsObjects, KmsRect};
use crate::{Damage, DrmHandle, GraphicsAdapter, VtState};
pub(super) fn mode_cursor<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
vts: &mut HashMap<usize, VtState<T>>,
handle: &mut DrmHandle<T>,
data: redox_ioctl::drm::DrmModeCursor<'_>,
) -> Result<usize, Error> {
cursor_inner(
adapter,
objects,
active_vt,
vts,
handle,
data.flags(),
data.crtc_id(),
data.x(),
data.y(),
data.width(),
data.height(),
data.handle(),
0,
0,
)
}
pub(super) fn mode_cursor2<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
vts: &mut HashMap<usize, VtState<T>>,
handle: &mut DrmHandle<T>,
data: redox_ioctl::drm::DrmModeCursor2<'_>,
) -> Result<usize, Error> {
cursor_inner(
adapter,
objects,
active_vt,
vts,
handle,
data.flags(),
data.crtc_id(),
data.x(),
data.y(),
data.width(),
data.height(),
data.handle(),
data.hot_x(),
data.hot_y(),
)
}
fn cursor_inner<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
vts: &mut HashMap<usize, VtState<T>>,
handle: &mut DrmHandle<T>,
flags: u32,
crtc_id: u32,
x: i32,
y: i32,
width: u32,
height: u32,
handle_id: u32,
hot_x: i32,
hot_y: i32,
) -> Result<usize, Error> {
let crtc_id = KmsObjectId(crtc_id);
let Some(plane) = objects.get_crtc(crtc_id)?.lock().unwrap().cursor_plane else {
return Err(Error::new(ENXIO));
};
let mut new_state = objects.get_plane(plane)?.lock().unwrap().state.clone();
let old_fb_id = new_state.fb_id;
new_state.crtc_id = Some(crtc_id);
if flags & DRM_MODE_CURSOR_BO != 0 {
if handle_id == 0 {
new_state.fb_id = None;
} else {
let buffer = handle.buffers.get(&handle_id).ok_or(Error::new(EINVAL))?;
let fb = adapter.create_framebuffer(buffer);
let fb_id = objects.add_framebuffer(KmsFramebuffer {
closed: AtomicBool::new(true),
width,
height,
pixel_format: DrmFourcc::Argb8888,
pitch: width * 4,
buffer: buffer.clone(),
driver_data: fb,
});
new_state.fb_id = Some(fb_id);
new_state.src_rect = KmsRect {
x: 0,
y: 0,
width,
height,
};
new_state.crtc_rect.width = width;
new_state.crtc_rect.height = height;
if let Some(hotspot) = &mut new_state.hotspot {
*hotspot = (hot_x, hot_y);
}
}
}
if flags & DRM_MODE_CURSOR_MOVE != 0 {
new_state.crtc_rect.x = x;
new_state.crtc_rect.y = y;
}
let plane = objects.get_plane(plane).unwrap();
if handle.vt == active_vt {
#[rustfmt::skip]
let damage = if flags & DRM_MODE_CURSOR_BO != 0 {
Damage { x: 0, y: 0, width, height }
} else {
Damage { x: 0, y: 0, width: 0, height: 0 }
};
adapter.set_plane(&objects, plane, new_state.clone(), damage)?;
}
vts.get_mut(&handle.vt).unwrap().plane_state[plane.lock().unwrap().plane_index as usize] =
new_state;
if let Some(old_fb_id) = old_fb_id {
if !VtState::fb_has_any_use(vts, old_fb_id) {
objects.remove_framebuffer_if_closed(old_fb_id);
}
}
Ok(0)
}
@@ -1,215 +0,0 @@
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use drm_fourcc::DrmFourcc;
use syscall::{EINVAL, Error};
use crate::kms::objects::{KmsFramebuffer, KmsObjectId, KmsObjects};
use crate::{Damage, DrmHandle, GraphicsAdapter, VtState};
pub(super) fn mode_get_fb<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
handle: &mut DrmHandle<T>,
mut data: redox_ioctl::drm::DrmModeFbCmd<'_>,
) -> Result<usize, Error> {
let fb = objects.get_framebuffer_maybe_closed(KmsObjectId(data.fb_id()))?;
let (bpp, depth) = match fb.pixel_format {
DrmFourcc::Xrgb8888 => (32, 24),
DrmFourcc::Argb8888 => (32, 32),
_ => todo!(),
};
handle.next_id += 1;
handle.buffers.insert(handle.next_id, fb.buffer.clone());
data.set_width(fb.width);
data.set_height(fb.height);
data.set_pitch(fb.pitch);
data.set_bpp(bpp);
data.set_depth(depth);
data.set_handle(handle.next_id);
Ok(0)
}
pub(super) fn mode_add_fb<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
handle: &mut DrmHandle<T>,
mut data: redox_ioctl::drm::DrmModeFbCmd<'_>,
) -> Result<usize, Error> {
let buffer = handle
.buffers
.get(&data.handle())
.ok_or(Error::new(EINVAL))?;
if data.bpp() != 32 {
return Err(Error::new(EINVAL));
}
let pixel_format = match data.depth() {
24 => DrmFourcc::Xrgb8888,
32 => DrmFourcc::Argb8888,
_ => return Err(Error::new(EINVAL)),
};
let fb = adapter.create_framebuffer(buffer);
let id = objects.add_framebuffer(KmsFramebuffer {
closed: AtomicBool::new(false),
width: data.width(),
height: data.height(),
pixel_format,
pitch: data.pitch(),
buffer: buffer.clone(),
driver_data: fb,
});
data.set_fb_id(id.0);
Ok(0)
}
pub(super) fn mode_rm_fb<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
vts: &mut HashMap<usize, VtState<T>>,
data: redox_ioctl::drm::StandinForUint<'_>,
) -> Result<usize, Error> {
let fb_id = KmsObjectId(data.inner());
objects.remove_framebuffer(fb_id)?;
// Disable planes that use this framebuffer.
for (vt, vt_data) in vts {
for (plane_idx, plane_state) in vt_data.plane_state.iter_mut().enumerate() {
if plane_state.fb_id != Some(fb_id) {
continue;
}
plane_state.fb_id = None;
if *vt != active_vt {
continue;
}
let plane = objects.planes().nth(plane_idx).unwrap();
adapter
.set_plane(
&objects,
plane,
plane_state.clone(),
Damage {
x: 0,
y: 0,
width: 0,
height: 0,
},
)
.unwrap();
}
}
Ok(0)
}
pub(super) fn mode_dirtyfb<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
handle: &mut DrmHandle<T>,
data: redox_ioctl::drm::DrmModeFbDirtyCmd<'_>,
) -> Result<usize, Error> {
let fb = objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
let damage = data
.clips_ptr()
.iter()
.map(|rect| Damage {
x: u32::from(rect.x1),
y: u32::from(rect.y1),
width: u32::from(rect.x2 - rect.x1),
height: u32::from(rect.y2 - rect.y1),
})
.reduce(Damage::merge)
.unwrap_or(Damage {
x: 0,
y: 0,
width: fb.width,
height: fb.height,
});
if handle.vt == active_vt {
for plane in objects.planes() {
let state = plane.lock().unwrap().state.clone();
if state.fb_id == Some(KmsObjectId(data.fb_id())) {
adapter.set_plane(&objects, plane, state, damage)?;
}
}
}
Ok(0)
}
pub(super) fn mode_add_fb2<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
handle: &mut DrmHandle<T>,
mut data: redox_ioctl::drm::DrmModeFbCmd2<'_>,
) -> Result<usize, Error> {
// FIXME handle multi-plane framebuffers
let buffer = handle
.buffers
.get(&data.handles()[0])
.ok_or(Error::new(EINVAL))?;
let fb = adapter.create_framebuffer(buffer);
let id = objects.add_framebuffer(KmsFramebuffer {
closed: AtomicBool::new(false),
width: data.width(),
height: data.height(),
pixel_format: DrmFourcc::try_from(data.pixel_format()).map_err(|_| Error::new(EINVAL))?,
pitch: data.pitches()[0],
buffer: buffer.clone(),
driver_data: fb,
});
data.set_fb_id(id.0);
Ok(0)
}
pub(super) fn mode_get_fb2<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
handle: &mut DrmHandle<T>,
mut data: redox_ioctl::drm::DrmModeFbCmd2<'_>,
) -> Result<usize, Error> {
let fb = objects.get_framebuffer_maybe_closed(KmsObjectId(data.fb_id()))?;
handle.next_id += 1;
handle.buffers.insert(handle.next_id, fb.buffer.clone());
data.set_width(fb.width);
data.set_height(fb.height);
data.set_pixel_format(fb.pixel_format as u32);
data.set_handles([handle.next_id, 0, 0, 0]);
data.set_pitches([fb.pitch, 0, 0, 0]);
data.set_offsets([0; 4]);
data.set_modifier([0; 4]);
Ok(0)
}
pub(super) fn mode_close_fb<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
vts: &mut HashMap<usize, VtState<T>>,
data: redox_ioctl::drm::DrmModeClosefb<'_>,
) -> Result<usize, Error> {
let fb_id = KmsObjectId(data.fb_id());
let fb = objects.get_framebuffer(fb_id)?;
fb.closed.store(true, Ordering::SeqCst);
if !VtState::fb_has_any_use(vts, fb_id) {
objects.remove_framebuffer(fb_id).unwrap();
}
Ok(0)
}
@@ -1,393 +0,0 @@
use std::collections::HashMap;
use std::ffi::c_char;
use std::mem;
use std::sync::Arc;
use drm_fourcc::DrmFourcc;
use syscall::{EINVAL, ENOENT, Error};
use crate::kms::objects::{KmsObjectId, KmsObjects, KmsRect};
use crate::{Buffer, Damage, DrmHandle, GraphicsAdapter, MAP_FAKE_OFFSET_MULTIPLIER, VtState};
mod cursor;
mod framebuffer;
mod property;
pub(crate) fn call_ioctl<T: GraphicsAdapter>(
adapter: &mut T,
objects: &mut KmsObjects<T>,
active_vt: usize,
vts: &mut HashMap<usize, VtState<T>>,
handle: &mut DrmHandle<T>,
cmd: u64,
payload: &mut [u8],
) -> syscall::Result<usize> {
use redox_ioctl::drm as ipc;
match cmd {
ipc::VERSION => ipc::DrmVersion::with(payload, |mut data| {
data.set_version_major(1);
data.set_version_minor(4);
data.set_version_patchlevel(0);
data.set_name(unsafe { mem::transmute(adapter.name()) });
data.set_date(unsafe { mem::transmute(&b"0"[..]) });
data.set_desc(unsafe { mem::transmute(adapter.desc()) });
Ok(0)
}),
ipc::GET_UNIQUE => ipc::DrmUnique::with(payload, |mut data| {
if let Some(unique) = &handle.unique {
data.set_unique(unsafe { mem::transmute::<&[u8], &[c_char]>(unique.as_bytes()) });
} else {
data.set_unique_len(0);
}
Ok(0)
}),
ipc::SET_VERSION => ipc::DrmSetVersion::with(payload, |mut data| {
// We only support version 1.4 currently
if data.drm_di_major() != 0 || data.drm_di_minor() != 4 {
return Err(Error::new(EINVAL));
}
if data.drm_dd_major() != 0 || data.drm_dd_minor() != 4 {
return Err(Error::new(EINVAL));
}
data.set_drm_di_major(1);
data.set_drm_di_minor(4);
data.set_drm_dd_major(1);
data.set_drm_dd_minor(4);
handle.unique = Some(adapter.get_unique());
Ok(0)
}),
ipc::GET_CAP => ipc::DrmGetCap::with(payload, |mut data| {
data.set_value(
adapter.get_cap(
data.capability()
.try_into()
.map_err(|_| Error::new(EINVAL))?,
)?,
);
Ok(0)
}),
ipc::SET_CLIENT_CAP => ipc::DrmSetClientCap::with(payload, |data| {
adapter.set_client_cap(
data.capability()
.try_into()
.map_err(|_| Error::new(EINVAL))?,
data.value(),
)?;
Ok(0)
}),
ipc::MODE_CARD_RES => ipc::DrmModeCardRes::with(payload, |mut data| {
let conn_ids = objects
.connector_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
let crtc_ids = objects.crtc_ids().iter().map(|id| id.0).collect::<Vec<_>>();
let enc_ids = objects
.encoder_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
let fb_ids = objects.fb_ids().iter().map(|id| id.0).collect::<Vec<_>>();
data.set_fb_id_ptr(&fb_ids);
data.set_crtc_id_ptr(&crtc_ids);
data.set_connector_id_ptr(&conn_ids);
data.set_encoder_id_ptr(&enc_ids);
data.set_min_width(0);
data.set_max_width(16384);
data.set_min_height(0);
data.set_max_height(16384);
Ok(0)
}),
ipc::MODE_GET_CRTC => ipc::DrmModeCrtc::with(payload, |mut data| {
let crtc = objects
.get_crtc(KmsObjectId(data.crtc_id()))?
.lock()
.unwrap();
// Don't touch set_connectors, that is only used by MODE_SET_CRTC
data.set_fb_id(
objects
.get_plane(crtc.primary_plane)
.unwrap()
.lock()
.unwrap()
.state
.fb_id
.unwrap_or(KmsObjectId::INVALID)
.0,
);
// FIXME fill x and y with the data from the primary plane
data.set_x(0);
data.set_y(0);
data.set_gamma_size(crtc.gamma_size);
if let Some(mode) = crtc.state.mode {
data.set_mode_valid(1);
data.set_mode(mode);
} else {
data.set_mode_valid(0);
data.set_mode(Default::default());
}
Ok(0)
}),
ipc::MODE_SET_CRTC => ipc::DrmModeCrtc::with(payload, |data| {
let crtc_id = KmsObjectId(data.crtc_id());
let crtc = objects.get_crtc(crtc_id)?;
let connector_ids: Vec<KmsObjectId> = data
.set_connectors_ptr()
.iter()
.take(data.count_connectors() as usize)
.map(|&id| KmsObjectId(id))
.collect();
let fb_id = if data.fb_id() != 0 {
let fb_id = KmsObjectId(data.fb_id());
objects.get_framebuffer(fb_id)?;
Some(fb_id)
} else {
None
};
let mode = if data.mode_valid() != 0 {
Some(data.mode())
} else {
None
};
let primary_plane_id = crtc.lock().unwrap().primary_plane;
let plane = objects.get_plane(primary_plane_id)?;
let mut new_crtc_state = crtc.lock().unwrap().state.clone();
new_crtc_state.mode = mode;
let mut new_plane_state = plane.lock().unwrap().state.clone();
let old_fb_id = new_plane_state.fb_id;
new_plane_state.fb_id = fb_id;
new_plane_state.crtc_id = Some(crtc_id);
if handle.vt == active_vt {
adapter.set_crtc(&objects, crtc, new_crtc_state.clone())?;
adapter.set_plane(
&objects,
plane,
new_plane_state.clone(),
Damage {
x: data.x(),
y: data.y(),
width: mode.map_or(0, |m| m.hdisplay as u32),
height: mode.map_or(0, |m| m.vdisplay as u32),
},
)?;
for connector in connector_ids {
objects
.get_connector(connector)?
.lock()
.unwrap()
.state
.crtc_id = crtc_id
}
}
crtc.lock().unwrap().state = new_crtc_state.clone();
plane.lock().unwrap().state = new_plane_state.clone();
vts.get_mut(&handle.vt).unwrap().crtc_state[crtc.lock().unwrap().crtc_index as usize] =
new_crtc_state;
vts.get_mut(&handle.vt).unwrap().plane_state
[plane.lock().unwrap().plane_index as usize] = new_plane_state;
if let Some(old_fb_id) = old_fb_id {
if !VtState::fb_has_any_use(vts, old_fb_id) {
objects.remove_framebuffer_if_closed(old_fb_id);
}
}
Ok(0)
}),
ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| {
cursor::mode_cursor(adapter, objects, active_vt, vts, handle, data)
}),
ipc::MODE_GET_ENCODER => ipc::DrmModeGetEncoder::with(payload, |mut data| {
let encoder = objects.get_encoder(KmsObjectId(data.encoder_id()))?;
data.set_crtc_id(encoder.crtc_id.0);
data.set_possible_crtcs(encoder.possible_crtcs);
data.set_possible_clones(encoder.possible_clones);
Ok(0)
}),
ipc::MODE_GET_CONNECTOR => ipc::DrmModeGetConnector::with(payload, |mut data| {
if data.count_modes() == 0 {
adapter.probe_connector(objects, KmsObjectId(data.connector_id()));
}
let connector = objects
.get_connector(KmsObjectId(data.connector_id()))?
.lock()
.unwrap();
data.set_encoders_ptr(&[connector.encoder_id.0]);
data.set_modes_ptr(&connector.modes);
data.set_connector_type(data.connector_type());
data.set_connector_type_id(data.connector_type_id());
data.set_connection(connector.connection as u32);
data.set_mm_width(connector.mm_width);
data.set_mm_height(connector.mm_width);
data.set_subpixel(connector.subpixel as u32);
drop(connector);
let (props, prop_vals) =
objects.get_object_properties_data(KmsObjectId(data.connector_id()))?;
data.set_props_ptr(&props);
data.set_prop_values_ptr(&prop_vals);
Ok(0)
}),
ipc::MODE_GET_PROPERTY => ipc::DrmModeGetProperty::with(payload, |data| {
property::mode_get_property(objects, data)
}),
ipc::MODE_GET_PROP_BLOB => {
ipc::DrmModeGetBlob::with(payload, |data| property::mode_get_prop_blob(objects, data))
}
ipc::MODE_GET_FB => ipc::DrmModeFbCmd::with(payload, |data| {
framebuffer::mode_get_fb(objects, handle, data)
}),
ipc::MODE_ADD_FB => ipc::DrmModeFbCmd::with(payload, |data| {
framebuffer::mode_add_fb(adapter, objects, handle, data)
}),
ipc::MODE_RM_FB => ipc::StandinForUint::with(payload, |data| {
framebuffer::mode_rm_fb(adapter, objects, active_vt, vts, data)
}),
ipc::MODE_DIRTYFB => ipc::DrmModeFbDirtyCmd::with(payload, |data| {
framebuffer::mode_dirtyfb(adapter, objects, active_vt, handle, data)
}),
ipc::MODE_CREATE_DUMB => ipc::DrmModeCreateDumb::with(payload, |mut data| {
if data.bpp() != 32 || data.flags() != 0 {
return Err(Error::new(EINVAL));
}
let (buffer, pitch) = adapter.create_dumb_buffer(data.width(), data.height());
data.set_pitch(pitch);
data.set_size(buffer.size() as u64);
handle.next_id += 1;
handle.buffers.insert(handle.next_id, Arc::new(buffer));
data.set_handle(handle.next_id as u32);
Ok(0)
}),
ipc::MODE_MAP_DUMB => ipc::DrmModeMapDumb::with(payload, |mut data| {
if data.offset() != 0 {
return Err(Error::new(EINVAL));
}
let buffer_id = data.handle();
if !handle.buffers.contains_key(&buffer_id) {
return Err(Error::new(ENOENT));
}
// FIXME use a better scheme for creating map offsets
assert!(handle.buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER);
data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64);
Ok(0)
}),
ipc::MODE_DESTROY_DUMB => ipc::DrmModeDestroyDumb::with(payload, |data| {
if handle.buffers.remove(&data.handle()).is_none() {
return Err(Error::new(ENOENT));
}
Ok(0)
}),
ipc::MODE_GET_PLANE_RES => ipc::DrmModeGetPlaneRes::with(payload, |mut data| {
let ids = objects
.plane_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
data.set_plane_id_ptr(&ids);
Ok(0)
}),
ipc::MODE_SET_PLANE => ipc::DrmModeSetPlane::with(payload, |data| {
let plane_id = KmsObjectId(data.plane_id());
let plane = objects.get_plane(plane_id)?;
let crtc_id = KmsObjectId(data.crtc_id());
let crtc_index = objects.get_crtc(crtc_id)?.lock().unwrap().crtc_index;
let mut new_state = {
let plane = plane.lock().unwrap();
if plane.possible_crtcs & (1 << crtc_index) == 0 {
return Err(Error::new(EINVAL));
}
plane.state.clone()
};
let fb_id = if data.fb_id() != 0 {
let fb_id = KmsObjectId(data.fb_id());
objects.get_framebuffer(fb_id)?;
Some(fb_id)
} else {
None
};
let old_fb_id = new_state.fb_id;
new_state.fb_id = fb_id;
new_state.crtc_id = Some(crtc_id);
new_state.src_rect = KmsRect {
x: data.src_x(),
y: data.src_y(),
width: data.src_w(),
height: data.src_h(),
};
new_state.crtc_rect = KmsRect {
x: data.crtc_x() as i32,
y: data.crtc_y() as i32,
width: data.crtc_w(),
height: data.crtc_h(),
};
if handle.vt == active_vt {
adapter.set_plane(
&objects,
plane,
new_state.clone(),
Damage {
x: 0,
y: 0,
width: data.src_w(),
height: data.src_h(),
},
)?;
}
vts.get_mut(&handle.vt).unwrap().plane_state
[plane.lock().unwrap().plane_index as usize] = new_state;
if let Some(old_fb_id) = old_fb_id {
if !VtState::fb_has_any_use(vts, old_fb_id) {
objects.remove_framebuffer_if_closed(old_fb_id);
}
}
Ok(0)
}),
ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| {
let plane = objects
.get_plane(KmsObjectId(data.plane_id()))
.unwrap()
.lock()
.unwrap();
data.set_crtc_id(plane.state.crtc_id.map_or(0, |id| id.0));
data.set_fb_id(plane.state.fb_id.unwrap_or(KmsObjectId::INVALID).0);
data.set_possible_crtcs(plane.possible_crtcs);
data.set_format_type_ptr(&[DrmFourcc::Argb8888 as u32]);
Ok(0)
}),
ipc::MODE_ADD_FB2 => ipc::DrmModeFbCmd2::with(payload, |data| {
framebuffer::mode_add_fb2(adapter, objects, handle, data)
}),
ipc::MODE_OBJ_GET_PROPERTIES => ipc::DrmModeObjGetProperties::with(payload, |data| {
property::mode_obj_get_properties(objects, data)
}),
ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| {
cursor::mode_cursor2(adapter, objects, active_vt, vts, handle, data)
}),
ipc::MODE_GET_FB2 => ipc::DrmModeFbCmd2::with(payload, |data| {
framebuffer::mode_get_fb2(objects, handle, data)
}),
ipc::MODE_CLOSE_FB => ipc::DrmModeClosefb::with(payload, |data| {
framebuffer::mode_close_fb(objects, vts, data)
}),
_ => return Err(Error::new(EINVAL)),
}
}
@@ -1,99 +0,0 @@
use drm_sys::{
DRM_MODE_PROP_ATOMIC, DRM_MODE_PROP_BITMASK, DRM_MODE_PROP_BLOB, DRM_MODE_PROP_ENUM,
DRM_MODE_PROP_IMMUTABLE, DRM_MODE_PROP_OBJECT, DRM_MODE_PROP_RANGE, DRM_MODE_PROP_SIGNED_RANGE,
drm_mode_property_enum,
};
use syscall::Error;
use crate::GraphicsAdapter;
use crate::kms::objects::{KmsObjectId, KmsObjects};
use crate::kms::properties::KmsPropertyKind;
pub(super) fn mode_get_property<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
mut data: redox_ioctl::drm::DrmModeGetProperty<'_>,
) -> Result<usize, Error> {
let property = objects.get_property(KmsObjectId(data.prop_id()))?;
data.set_name(property.name.0);
let mut flags = 0;
if property.immutable {
flags |= DRM_MODE_PROP_IMMUTABLE;
}
if property.atomic {
flags |= DRM_MODE_PROP_ATOMIC;
}
match &property.kind {
&KmsPropertyKind::Range(start, end) => {
data.set_flags(flags | DRM_MODE_PROP_RANGE);
data.set_values_ptr(&[start, end]);
data.set_enum_blob_ptr(&[]);
}
KmsPropertyKind::Enum(variants) => {
data.set_flags(flags | DRM_MODE_PROP_ENUM);
data.set_values_ptr(&variants.iter().map(|&(_, value)| value).collect::<Vec<_>>());
data.set_enum_blob_ptr(
&variants
.iter()
.map(|&(name, value)| drm_mode_property_enum {
name: name.0,
value,
})
.collect::<Vec<_>>(),
);
}
KmsPropertyKind::Blob => {
data.set_flags(flags | DRM_MODE_PROP_BLOB);
data.set_values_ptr(&[]);
data.set_enum_blob_ptr(&[]);
}
KmsPropertyKind::Bitmask(bitmask_flags) => {
data.set_flags(flags | DRM_MODE_PROP_BITMASK);
data.set_values_ptr(
&bitmask_flags
.iter()
.map(|&(_, value)| value)
.collect::<Vec<_>>(),
);
data.set_enum_blob_ptr(
&bitmask_flags
.iter()
.map(|&(name, value)| drm_mode_property_enum {
name: name.0,
value,
})
.collect::<Vec<_>>(),
);
}
KmsPropertyKind::Object { type_ } => {
data.set_flags(flags | DRM_MODE_PROP_OBJECT);
data.set_values_ptr(&[u64::from(*type_)]);
data.set_enum_blob_ptr(&[]);
}
&KmsPropertyKind::SignedRange(start, end) => {
data.set_flags(flags | DRM_MODE_PROP_SIGNED_RANGE);
data.set_values_ptr(&[start as u64, end as u64]);
data.set_enum_blob_ptr(&[]);
}
}
Ok(0)
}
pub(super) fn mode_get_prop_blob<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
mut data: redox_ioctl::drm::DrmModeGetBlob<'_>,
) -> Result<usize, Error> {
let blob = objects.get_blob(KmsObjectId(data.blob_id()))?;
data.set_data(&blob);
Ok(0)
}
pub(super) fn mode_obj_get_properties<T: GraphicsAdapter>(
objects: &mut KmsObjects<T>,
mut data: redox_ioctl::drm::DrmModeObjGetProperties<'_>,
) -> Result<usize, Error> {
let (props, prop_vals) = objects.get_object_properties_data(KmsObjectId(data.obj_id()))?;
data.set_props_ptr(&props);
data.set_prop_values_ptr(&prop_vals);
data.set_obj_type(objects.object_type(KmsObjectId(data.obj_id()))?);
Ok(0)
}
@@ -3,14 +3,14 @@ use std::fmt::Debug;
use std::sync::Mutex;
use drm_sys::{
DRM_MODE_CONNECTOR_Unknown, DRM_MODE_DPMS_OFF, DRM_MODE_DPMS_ON, DRM_MODE_DPMS_STANDBY,
DRM_MODE_DPMS_SUSPEND, DRM_MODE_TYPE_PREFERRED, drm_mode_modeinfo,
drm_mode_modeinfo, DRM_MODE_CONNECTOR_Unknown, DRM_MODE_DPMS_OFF, DRM_MODE_DPMS_ON,
DRM_MODE_DPMS_STANDBY, DRM_MODE_DPMS_SUSPEND, DRM_MODE_TYPE_PREFERRED,
};
use syscall::Result;
use crate::GraphicsAdapter;
use crate::kms::objects::{KmsObjectId, KmsObjects};
use crate::kms::properties::{CRTC_ID, DPMS, EDID, KmsPropertyData, TILE, define_object_props};
use crate::kms::properties::{define_object_props, KmsPropertyData, CRTC_ID, DPMS, EDID};
use crate::GraphicsAdapter;
impl<T: GraphicsAdapter> KmsObjects<T> {
pub fn add_connector(
@@ -68,23 +68,6 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
self.get(id)
}
pub fn set_connector_edid(&mut self, id: KmsObjectId, edid: Vec<u8>) {
let mut connector = self.get_connector(id).unwrap().lock().unwrap();
connector.update_from_edid(&edid);
let old_edid = connector.edid;
drop(connector);
if old_edid != KmsObjectId::INVALID {
if self.get_blob(old_edid).unwrap() == edid {
return; // EDID is unchanged; nothing to do
}
self.remove_blob(old_edid).unwrap();
}
let blob = self.add_blob(edid);
self.get_connector(id).unwrap().lock().unwrap().edid = blob;
}
pub fn encoder_ids(&self) -> &[KmsObjectId] {
&self.encoders
}
@@ -142,13 +125,6 @@ define_object_props!(object, KmsConnector<T: GraphicsAdapter> {
DPMS {
get => object.state.dpms as u64,
}
TILE {
get => {
// FIXME set this property once we support DP MST
let _ = object;
0
},
}
CRTC_ID {
get => u64::from(object.state.crtc_id.0),
}
@@ -159,7 +135,7 @@ impl<T: GraphicsAdapter> KmsConnector<T> {
self.modes = vec![modeinfo_for_size(width, height)];
}
fn update_from_edid(&mut self, edid: &[u8]) {
pub fn update_from_edid(&mut self, edid: &[u8]) {
let edid = edid::parse(edid).unwrap().1;
if let Some(first_detailed_timing) =
@@ -202,7 +178,7 @@ impl<T: GraphicsAdapter> KmsConnector<T> {
}
}
fn modeinfo_for_size(width: u32, height: u32) -> drm_mode_modeinfo {
pub(crate) fn modeinfo_for_size(width: u32, height: u32) -> drm_mode_modeinfo {
let mut modeinfo = drm_mode_modeinfo {
// The actual visible display size
hdisplay: width as u16,
@@ -1,33 +1,28 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use drm_fourcc::DrmFourcc;
use drm_sys::{
DRM_MODE_OBJECT_BLOB, DRM_MODE_OBJECT_CONNECTOR, DRM_MODE_OBJECT_CRTC, DRM_MODE_OBJECT_ENCODER,
DRM_MODE_OBJECT_FB, DRM_MODE_OBJECT_PLANE, DRM_MODE_OBJECT_PROPERTY, DRM_PLANE_TYPE_CURSOR,
DRM_PLANE_TYPE_OVERLAY, DRM_PLANE_TYPE_PRIMARY, drm_mode_modeinfo,
drm_mode_modeinfo, DRM_MODE_OBJECT_BLOB, DRM_MODE_OBJECT_CONNECTOR, DRM_MODE_OBJECT_CRTC,
DRM_MODE_OBJECT_ENCODER, DRM_MODE_OBJECT_FB, DRM_MODE_OBJECT_PROPERTY,
};
use syscall::{ENOENT, Error, Result};
use syscall::{Error, Result, EINVAL};
use crate::GraphicsAdapter;
use crate::kms::connector::{KmsConnector, KmsEncoder};
use crate::kms::properties::{
ACTIVE, CRTC_H, CRTC_ID, CRTC_W, CRTC_X, CRTC_Y, FB_ID, KmsBlob, KmsProperty, KmsPropertyData,
SRC_H, SRC_W, SRC_X, SRC_Y, define_object_props, init_standard_props, type_,
define_object_props, init_standard_props, KmsBlob, KmsProperty, KmsPropertyData,
};
use crate::GraphicsAdapter;
#[derive(Debug)]
pub struct KmsObjects<T: GraphicsAdapter> {
next_id: KmsObjectId,
pub(super) connectors: Vec<KmsObjectId>,
pub(super) encoders: Vec<KmsObjectId>,
pub(crate) connectors: Vec<KmsObjectId>,
pub(crate) encoders: Vec<KmsObjectId>,
crtcs: Vec<KmsObjectId>,
planes: Vec<KmsObjectId>,
framebuffers: Vec<KmsObjectId>,
pub(super) objects: HashMap<KmsObjectId, KmsObject<T>>,
pub(crate) objects: HashMap<KmsObjectId, KmsObject<T>>,
_marker: PhantomData<T>,
}
@@ -38,7 +33,6 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
connectors: vec![],
encoders: vec![],
crtcs: vec![],
planes: vec![],
framebuffers: vec![],
objects: HashMap::new(),
_marker: PhantomData,
@@ -47,37 +41,28 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
objects
}
pub(super) fn add<U: KmsObjectKind<T>>(&mut self, data: U) -> KmsObjectId {
pub(crate) fn add<U: Into<KmsObject<T>>>(&mut self, data: U) -> KmsObjectId {
let id = self.next_id;
self.objects.insert(id, data.into_object());
self.objects.insert(id, data.into());
self.next_id.0 += 1;
id
}
pub(super) fn get<U: KmsObjectKind<T>>(&self, id: KmsObjectId) -> Result<&U> {
let object = self.objects.get(&id).ok_or(Error::new(ENOENT))?;
if let Some(object) = U::try_from_object(object) {
pub(crate) fn get<'a, U: 'a>(&'a self, id: KmsObjectId) -> Result<&'a U>
where
&'a U: TryFrom<&'a KmsObject<T>>,
{
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
if let Ok(object) = object.try_into() {
Ok(object)
} else {
Err(Error::new(ENOENT))
Err(Error::new(EINVAL))
}
}
pub(super) fn remove<U: KmsObjectKind<T>>(&mut self, id: KmsObjectId) -> Result<()> {
let Some(object) = self.objects.get(&id) else {
return Err(Error::new(ENOENT));
};
let Some(_) = U::try_from_object(object) else {
return Err(Error::new(ENOENT));
};
self.objects.remove(&id).unwrap();
Ok(())
}
pub(crate) fn object_type(&self, id: KmsObjectId) -> Result<u32> {
let object = self.objects.get(&id).ok_or(Error::new(ENOENT))?;
pub fn object_type(&self, id: KmsObjectId) -> Result<u32> {
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
Ok(object.object_type())
}
@@ -85,25 +70,14 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
&mut self,
driver_data: T::Crtc,
driver_data_state: <T::Crtc as KmsCrtcDriver>::State,
plane_data: T::Plane,
plane_data_state: <T::Plane as KmsPlaneDriver>::State,
) -> (KmsObjectId, KmsObjectId) {
let primary_plane = self.add_plane(
&[],
KmsPlaneType::Primary,
false,
plane_data,
plane_data_state,
);
) -> KmsObjectId {
let crtc_index = self.crtcs.len() as u32;
let id = self.add(Mutex::new(KmsCrtc {
crtc_index,
gamma_size: 0,
properties: KmsCrtc::base_properties(),
primary_plane,
cursor_plane: None,
state: KmsCrtcState {
fb_id: None,
mode: None,
driver_data: driver_data_state,
},
@@ -111,13 +85,7 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
}));
self.crtcs.push(id);
self.get_plane(primary_plane)
.unwrap()
.lock()
.unwrap()
.possible_crtcs = 1 << crtc_index;
(id, primary_plane)
id
}
pub fn crtc_ids(&self) -> &[KmsObjectId] {
@@ -134,67 +102,6 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
self.get(id)
}
pub fn add_plane(
&mut self,
crtcs: &[KmsObjectId],
plane_type: KmsPlaneType,
has_hotspot: bool,
driver_data: T::Plane,
driver_data_state: <T::Plane as KmsPlaneDriver>::State,
) -> KmsObjectId {
if has_hotspot {
assert_eq!(plane_type, KmsPlaneType::Cursor);
}
let mut possible_crtcs = 0u32;
for &crtc in crtcs {
possible_crtcs |= 1 << self.get_crtc(crtc).unwrap().lock().unwrap().crtc_index
}
let plane_index = self.planes.len() as u32;
let id = self.add(Mutex::new(KmsPlane {
plane_index,
possible_crtcs,
plane_type,
properties: KmsPlane::base_properties(),
state: KmsPlaneState {
fb_id: None,
crtc_id: None,
src_rect: KmsRect {
x: 0u32,
y: 0,
width: 0,
height: 0,
},
crtc_rect: KmsRect {
x: 0i32,
y: 0,
width: 0,
height: 0,
},
hotspot: has_hotspot.then_some((0, 0)),
driver_data: driver_data_state,
},
driver_data,
}));
self.planes.push(id);
id
}
pub fn plane_ids(&self) -> &[KmsObjectId] {
&self.planes
}
pub fn planes(&self) -> impl Iterator<Item = &Mutex<KmsPlane<T>>> + use<'_, T> {
self.planes
.iter()
.map(|&id| self.get::<Mutex<KmsPlane<T>>>(id).unwrap())
}
pub fn get_plane(&self, id: KmsObjectId) -> Result<&Mutex<KmsPlane<T>>> {
self.get(id)
}
pub fn add_framebuffer(&mut self, fb: KmsFramebuffer<T>) -> KmsObjectId {
let id = self.add(fb);
self.framebuffers.push(id);
@@ -202,18 +109,15 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
}
pub fn remove_framebuffer(&mut self, id: KmsObjectId) -> Result<()> {
self.remove::<KmsFramebuffer<T>>(id)
}
let Some(object) = self.objects.get(&id) else {
return Err(Error::new(EINVAL));
};
let KmsObject::Framebuffer(_) = object else {
return Err(Error::new(EINVAL));
};
self.objects.remove(&id).unwrap();
pub fn remove_framebuffer_if_closed(&mut self, id: KmsObjectId) {
if self
.get_framebuffer_maybe_closed(id)
.unwrap()
.closed
.load(Ordering::SeqCst)
{
self.remove::<KmsFramebuffer<T>>(id).unwrap();
}
Ok(())
}
pub fn fb_ids(&self) -> &[KmsObjectId] {
@@ -221,15 +125,7 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
}
pub fn get_framebuffer(&self, id: KmsObjectId) -> Result<&KmsFramebuffer<T>> {
let fb = self.get::<KmsFramebuffer<T>>(id)?;
if fb.closed.load(Ordering::SeqCst) {
return Err(Error::new(ENOENT));
}
Ok(fb)
}
pub fn get_framebuffer_maybe_closed(&self, id: KmsObjectId) -> Result<&KmsFramebuffer<T>> {
self.get::<KmsFramebuffer<T>>(id)
self.get(id)
}
}
@@ -246,17 +142,12 @@ impl From<KmsObjectId> for u64 {
}
}
pub(super) trait KmsObjectKind<T: GraphicsAdapter> {
fn into_object(self) -> KmsObject<T>;
fn try_from_object(object: &KmsObject<T>) -> Option<&Self>;
}
macro_rules! define_object_kinds {
(<$T:ident> $(
$variant:ident($data:ty) = $type:ident,
)*) => {
#[derive(Debug)]
pub(super) enum KmsObject<$T: GraphicsAdapter> {
pub(crate) enum KmsObject<$T: GraphicsAdapter> {
$($variant($data),)*
}
@@ -269,15 +160,19 @@ macro_rules! define_object_kinds {
}
$(
impl<$T: GraphicsAdapter> KmsObjectKind<$T> for $data {
fn into_object(self) -> KmsObject<$T> {
KmsObject::$variant(self)
impl<$T: GraphicsAdapter> From<$data> for KmsObject<$T> {
fn from(value: $data) -> Self {
Self::$variant(value)
}
}
fn try_from_object(object: &KmsObject<$T>) -> Option<&$data> {
match object {
KmsObject::$variant(data) => Some(data),
_ => None,
impl<'a, $T: GraphicsAdapter> TryFrom<&'a KmsObject<$T>> for &'a $data {
type Error = ();
fn try_from(value: &'a KmsObject<T>) -> Result<Self, Self::Error> {
match value {
KmsObject::$variant(data) => Ok(data),
_ => Err(()),
}
}
}
@@ -290,7 +185,6 @@ define_object_kinds! { <T>
Connector(Mutex<KmsConnector<T>>) = DRM_MODE_OBJECT_CONNECTOR,
Encoder(KmsEncoder) = DRM_MODE_OBJECT_ENCODER,
Property(KmsProperty) = DRM_MODE_OBJECT_PROPERTY,
Plane(Mutex<KmsPlane<T>>) = DRM_MODE_OBJECT_PLANE,
Framebuffer(KmsFramebuffer<T>) = DRM_MODE_OBJECT_FB,
Blob(KmsBlob) = DRM_MODE_OBJECT_BLOB,
}
@@ -308,14 +202,13 @@ pub struct KmsCrtc<T: GraphicsAdapter> {
pub crtc_index: u32,
pub gamma_size: u32,
pub properties: Vec<KmsPropertyData<Self>>,
pub primary_plane: KmsObjectId,
pub cursor_plane: Option<KmsObjectId>,
pub state: KmsCrtcState<T>,
pub driver_data: T::Crtc,
}
#[derive(Debug)]
pub struct KmsCrtcState<T: GraphicsAdapter> {
pub fb_id: Option<KmsObjectId>,
pub mode: Option<drm_mode_modeinfo>,
pub driver_data: <T::Crtc as KmsCrtcDriver>::State,
}
@@ -323,127 +216,22 @@ pub struct KmsCrtcState<T: GraphicsAdapter> {
impl<T: GraphicsAdapter> Clone for KmsCrtcState<T> {
fn clone(&self) -> Self {
Self {
fb_id: self.fb_id.clone(),
mode: self.mode.clone(),
driver_data: self.driver_data.clone(),
}
}
}
define_object_props!(object, KmsCrtc<T: GraphicsAdapter> {
ACTIVE {
get => u64::from(object.state.mode.is_some()),
}
});
pub trait KmsPlaneDriver: Debug {
type State: Clone + Debug;
}
impl KmsPlaneDriver for () {
type State = ();
}
#[derive(Debug)]
pub struct KmsPlane<T: GraphicsAdapter> {
pub plane_index: u32,
pub possible_crtcs: u32,
pub plane_type: KmsPlaneType,
pub properties: Vec<KmsPropertyData<Self>>,
pub state: KmsPlaneState<T>,
pub driver_data: T::Plane,
}
#[derive(Debug)]
pub struct KmsPlaneState<T: GraphicsAdapter> {
pub fb_id: Option<KmsObjectId>,
pub crtc_id: Option<KmsObjectId>,
pub src_rect: KmsRect<u32>,
pub crtc_rect: KmsRect<i32>,
pub hotspot: Option<(i32, i32)>,
pub driver_data: <T::Plane as KmsPlaneDriver>::State,
}
impl<T: GraphicsAdapter> Clone for KmsPlaneState<T> {
fn clone(&self) -> Self {
Self {
fb_id: self.fb_id.clone(),
crtc_id: self.crtc_id.clone(),
src_rect: self.src_rect.clone(),
crtc_rect: self.crtc_rect.clone(),
hotspot: self.hotspot,
driver_data: self.driver_data.clone(),
}
}
}
define_object_props!(object, KmsPlane<T: GraphicsAdapter> {
type_ {
get => object.plane_type as u64,
}
FB_ID {
get => u64::from(object.state.fb_id.map_or(0, |id| id.0)),
}
CRTC_ID {
get => u64::from(object.state.crtc_id.map_or(0, |id| id.0)),
}
CRTC_X {
get => u64::from(object.state.crtc_rect.x.cast_unsigned()),
}
CRTC_Y {
get => u64::from(object.state.crtc_rect.y.cast_unsigned()),
}
CRTC_W {
get => u64::from(object.state.crtc_rect.width),
}
CRTC_H {
get => u64::from(object.state.crtc_rect.height),
}
SRC_X {
get => u64::from(object.state.src_rect.x),
}
SRC_Y {
get => u64::from(object.state.src_rect.y),
}
SRC_W {
get => u64::from(object.state.src_rect.width),
}
SRC_H {
get => u64::from(object.state.src_rect.height),
}
// FIXME HOTSPOT_X and HOTSPOT_Y if supported by graphics card
});
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(u32)]
pub enum KmsPlaneType {
Primary = DRM_PLANE_TYPE_PRIMARY,
Overlay = DRM_PLANE_TYPE_OVERLAY,
Cursor = DRM_PLANE_TYPE_CURSOR,
}
#[derive(Debug, Clone)]
pub struct KmsRect<T> {
pub x: T,
pub y: T,
pub width: u32,
pub height: u32,
}
define_object_props!(object, KmsCrtc<T: GraphicsAdapter> {});
#[derive(Debug)]
pub struct KmsFramebuffer<T: GraphicsAdapter> {
/// Was this framebuffer closed using the CLOSEFB ioctl or implicitly
/// created by the CURSOR or CURSOR2 ioctls or similar?
///
/// A closed framebuffer will be destroyed as soon as the last plane that
/// uses it switches to a different framebuffer. In the mean time the GETFB
/// and GETFB2 ioctls still function on it, but anything else will result
/// in ENOENT, including another CLOSEFB call.
pub closed: AtomicBool,
pub width: u32,
pub height: u32,
pub pixel_format: DrmFourcc,
pub pitch: u32,
pub bpp: u32,
pub depth: u32,
pub buffer: Arc<T::Buffer>,
pub driver_data: T::Framebuffer,
}
@@ -7,10 +7,10 @@ use drm_sys::{
DRM_MODE_OBJECT_CRTC, DRM_MODE_OBJECT_FB, DRM_PLANE_TYPE_CURSOR, DRM_PLANE_TYPE_OVERLAY,
DRM_PLANE_TYPE_PRIMARY, DRM_PROP_NAME_LEN,
};
use syscall::{ENOENT, Error, Result};
use syscall::{Error, Result, EINVAL};
use crate::GraphicsAdapter;
use crate::kms::objects::{KmsObject, KmsObjectId, KmsObjects};
use crate::GraphicsAdapter;
impl<T: GraphicsAdapter> KmsObjects<T> {
pub fn add_property(
@@ -51,31 +51,29 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
}
pub fn get_object_properties_data(&self, id: KmsObjectId) -> Result<(Vec<u32>, Vec<u64>)> {
let object = self.objects.get(&id).ok_or(Error::new(ENOENT))?;
let object = self.objects.get(&id).ok_or(Error::new(EINVAL))?;
match object {
KmsObject::Crtc(crtc) => {
let crtc = crtc.lock().unwrap();
Ok(crtc
.properties
.iter()
.map(|prop| (prop.id.0, (prop.getter)(&crtc)))
.unzip())
let props = &crtc.properties;
Ok((
props.iter().map(|prop| prop.id.0).collect::<Vec<_>>(),
props
.iter()
.map(|prop| (prop.getter)(&crtc))
.collect::<Vec<_>>(),
))
}
KmsObject::Connector(connector) => {
let connector = connector.lock().unwrap();
Ok(connector
.properties
.iter()
.map(|prop| (prop.id.0, (prop.getter)(&connector)))
.unzip())
}
KmsObject::Plane(plane) => {
let plane = plane.lock().unwrap();
Ok(plane
.properties
.iter()
.map(|prop| (prop.id.0, (prop.getter)(&plane)))
.unzip())
let props = &connector.properties;
Ok((
props.iter().map(|prop| prop.id.0).collect::<Vec<_>>(),
props
.iter()
.map(|prop| (prop.getter)(&connector))
.collect::<Vec<_>>(),
))
}
KmsObject::Encoder(_)
| KmsObject::Property(_)
@@ -88,10 +86,6 @@ impl<T: GraphicsAdapter> KmsObjects<T> {
self.add(KmsBlob { data })
}
pub fn remove_blob(&mut self, id: KmsObjectId) -> Result<()> {
self.remove::<KmsBlob>(id)
}
pub fn get_blob(&self, id: KmsObjectId) -> Result<&[u8]> {
Ok(&self.get::<KmsBlob>(id)?.data)
}
@@ -205,7 +199,6 @@ define_properties! {
Suspend = u64::from(DRM_MODE_DPMS_SUSPEND),
Off = u64::from(DRM_MODE_DPMS_OFF),
} [],
TILE: blob [immutable],
// CRTC
ACTIVE: range { 0,1 } [atomic],
@@ -236,7 +229,7 @@ macro_rules! define_object_props {
}
)* }) => {
impl$(<$($T$(: $bound)?),*>)? $obj$(<$($T),*>)? {
fn base_properties() -> Vec<KmsPropertyData<Self>> {
pub(super) fn base_properties() -> Vec<KmsPropertyData<Self>> {
vec![$(KmsPropertyData {
id: $prop,
getter: |$object| $get
+565 -92
View File
@@ -1,31 +1,35 @@
#![feature(macro_metavar_expr)]
use std::cmp;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::File;
use std::io::{self, Write};
use std::os::fd::BorrowedFd;
use std::sync::{Arc, Mutex};
use std::{cmp, mem};
use drm_fourcc::DrmFourcc;
use drm_sys::{
drm_mode_property_enum, DRM_MODE_CURSOR_BO, DRM_MODE_CURSOR_MOVE, DRM_MODE_PROP_ATOMIC,
DRM_MODE_PROP_BITMASK, DRM_MODE_PROP_BLOB, DRM_MODE_PROP_ENUM, DRM_MODE_PROP_IMMUTABLE,
DRM_MODE_PROP_OBJECT, DRM_MODE_PROP_RANGE, DRM_MODE_PROP_SIGNED_RANGE,
};
use inputd::{DisplayHandle, VtEventKind};
use libredox::Fd;
use redox_scheme::scheme::{SchemeState, SchemeSync, register_scheme_inner};
use redox_scheme::scheme::{register_scheme_inner, SchemeState, SchemeSync};
use redox_scheme::{CallerCtx, OpenResult, RequestKind, SignalBehavior, Socket};
use scheme_utils::{FpathWriter, HandleMap};
use syscall::schemev2::NewFdFlags;
use syscall::{EACCES, EAGAIN, EINVAL, ENOENT, EOPNOTSUPP, Error, MapFlags, Result};
use syscall::{Error, MapFlags, Result, EACCES, EAGAIN, EINVAL, ENOENT, EOPNOTSUPP};
use crate::kms::connector::{KmsConnectorDriver, KmsConnectorState};
use crate::kms::objects::{
KmsCrtc, KmsCrtcDriver, KmsCrtcState, KmsObjectId, KmsObjects, KmsPlane, KmsPlaneDriver,
KmsPlaneState,
};
use crate::kms::objects::{self, KmsCrtc, KmsCrtcDriver, KmsCrtcState, KmsObjectId, KmsObjects};
use crate::kms::properties::KmsPropertyKind;
mod ioctl;
pub mod kms;
#[derive(Debug, Copy, Clone)]
#[repr(C, packed)]
pub struct Damage {
pub x: u32,
pub y: u32,
@@ -77,7 +81,6 @@ impl Damage {
pub trait GraphicsAdapter: Sized + Debug {
type Connector: KmsConnectorDriver;
type Crtc: KmsCrtcDriver;
type Plane: KmsPlaneDriver;
type Buffer: Buffer;
type Framebuffer: Framebuffer;
@@ -87,7 +90,6 @@ pub trait GraphicsAdapter: Sized + Debug {
fn init(&mut self, objects: &mut KmsObjects<Self>);
fn get_unique(&self) -> String;
fn get_cap(&self, cap: u32) -> Result<u64>;
fn set_client_cap(&self, cap: u32, value: u64) -> Result<()>;
@@ -103,15 +105,11 @@ pub trait GraphicsAdapter: Sized + Debug {
objects: &KmsObjects<Self>,
crtc: &Mutex<KmsCrtc<Self>>,
new_state: KmsCrtcState<Self>,
) -> syscall::Result<()>;
fn set_plane(
&mut self,
objects: &KmsObjects<Self>,
plane: &Mutex<KmsPlane<Self>>,
new_plane_state: KmsPlaneState<Self>,
damage: Damage,
) -> syscall::Result<()>;
fn hw_cursor_size(&self) -> Option<(u32, u32)>;
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Buffer>, dirty_fb: bool);
}
pub trait Buffer: Debug {
@@ -122,6 +120,14 @@ pub trait Framebuffer: Debug {}
impl Framebuffer for () {}
pub struct CursorPlane<C: Buffer> {
pub x: i32,
pub y: i32,
pub hot_x: i32,
pub hot_y: i32,
pub buffer: Option<Arc<C>>,
}
pub struct GraphicsScheme<T: GraphicsAdapter> {
inner: GraphicsSchemeInner<T>,
inputd_handle: DisplayHandle,
@@ -267,36 +273,18 @@ struct GraphicsSchemeInner<T: GraphicsAdapter> {
struct VtState<T: GraphicsAdapter> {
connector_state: Vec<KmsConnectorState<T>>,
crtc_state: Vec<KmsCrtcState<T>>,
plane_state: Vec<KmsPlaneState<T>>,
}
impl<T: GraphicsAdapter> VtState<T> {
fn fb_has_any_use(vts: &HashMap<usize, Self>, fb_id: KmsObjectId) -> bool {
let mut has_any_use = false;
for vt_data in vts.values() {
for plane_state in vt_data.plane_state.iter() {
if plane_state.fb_id == Some(fb_id) {
has_any_use = true;
break;
}
}
}
has_any_use
}
cursor_plane: CursorPlane<T::Buffer>,
}
enum Handle<T: GraphicsAdapter> {
V2(DrmHandle<T>),
V2 {
vt: usize,
next_id: u32,
buffers: HashMap<u32, Arc<T::Buffer>>,
},
SchemeRoot,
}
struct DrmHandle<T: GraphicsAdapter> {
vt: usize,
unique: Option<String>,
next_id: u32,
buffers: HashMap<u32, Arc<T::Buffer>>,
}
impl<T: GraphicsAdapter> GraphicsSchemeInner<T> {
fn get_or_create_vt<'a>(
objects: &KmsObjects<T>,
@@ -312,10 +300,13 @@ impl<T: GraphicsAdapter> GraphicsSchemeInner<T> {
.crtcs()
.map(|crtc| crtc.lock().unwrap().state.clone())
.collect(),
plane_state: objects
.planes()
.map(|plane| plane.lock().unwrap().state.clone())
.collect(),
cursor_plane: CursorPlane {
x: 0,
y: 0,
hot_x: 0,
hot_y: 0,
buffer: None,
},
})
}
@@ -350,8 +341,24 @@ impl<T: GraphicsAdapter> GraphicsSchemeInner<T> {
let crtc = self.objects.get_crtc(crtc_id).unwrap();
let connector_id = self.objects.connector_ids()[crtc_idx];
let fb = crtc_state.fb_id.map(|fb_id| {
self.objects
.get_framebuffer(fb_id)
.expect("removed framebuffers should be unset")
});
self.adapter
.set_crtc(&self.objects, crtc, crtc_state.clone())
.set_crtc(
&self.objects,
crtc,
crtc_state.clone(),
Damage {
x: 0,
y: 0,
width: fb.map_or(0, |fb| fb.width),
height: fb.map_or(0, |fb| fb.height),
},
)
.unwrap();
self.objects
@@ -363,29 +370,8 @@ impl<T: GraphicsAdapter> GraphicsSchemeInner<T> {
.crtc_id = crtc_id;
}
for (plane_idx, plane_state) in vt_state.plane_state.iter().enumerate() {
let plane_id = self.objects.plane_ids()[plane_idx];
let plane = self.objects.get_plane(plane_id).unwrap();
let fb = plane_state.fb_id.map(|fb_id| {
self.objects
.get_framebuffer_maybe_closed(fb_id)
.expect("removed framebuffers should be unset")
});
self.adapter
.set_plane(
&self.objects,
plane,
plane_state.clone(),
Damage {
x: 0,
y: 0,
width: fb.map_or(0, |fb| fb.width),
height: fb.map_or(0, |fb| fb.height),
},
)
.unwrap();
if self.adapter.hw_cursor_size().is_some() {
self.adapter.handle_cursor(&vt_state.cursor_plane, true);
}
}
}
@@ -422,12 +408,11 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
// Ensure the VT exists such that the rest of the methods can freely access it.
Self::get_or_create_vt(&self.objects, &mut self.vts, vt);
Handle::V2(DrmHandle {
Handle::V2 {
vt,
unique: None,
next_id: 0,
buffers: HashMap::new(),
})
}
} else {
return Err(Error::new(EINVAL));
};
@@ -438,20 +423,14 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
})
}
fn fstat(&mut self, _id: usize, stat: &mut syscall::Stat, _ctx: &CallerCtx) -> Result<()> {
stat.st_dev = 226 /*DRM_MAJOR*/ << 8;
Ok(())
}
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> syscall::Result<usize> {
FpathWriter::with(buf, &self.scheme_name, |w| {
match self.handles.get(id)? {
Handle::V2(DrmHandle {
Handle::V2 {
vt,
unique: _,
next_id: _,
buffers: _,
}) => write!(w, "v2/{vt}").unwrap(),
} => write!(w, "v2/{vt}").unwrap(),
Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)),
};
Ok(())
@@ -465,17 +444,512 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
metadata: &[u64],
_ctx: &CallerCtx,
) -> Result<usize> {
use redox_ioctl::drm as ipc;
fn id_index(id: u32) -> u32 {
id & 0xFF
}
fn plane_id(i: u32) -> u32 {
id_index(i) | (1 << 13)
}
match self.handles.get_mut(id)? {
Handle::SchemeRoot => return Err(Error::new(EOPNOTSUPP)),
Handle::V2(handle) => ioctl::call_ioctl(
&mut self.adapter,
&mut self.objects,
self.active_vt,
&mut self.vts,
handle,
metadata[0],
payload,
),
Handle::V2 {
vt,
next_id,
buffers,
} => match metadata[0] {
ipc::VERSION => ipc::DrmVersion::with(payload, |mut data| {
data.set_version_major(1);
data.set_version_minor(4);
data.set_version_patchlevel(0);
data.set_name(unsafe { mem::transmute(self.adapter.name()) });
data.set_date(unsafe { mem::transmute(&b"0"[..]) });
data.set_desc(unsafe { mem::transmute(self.adapter.desc()) });
Ok(0)
}),
ipc::GET_CAP => ipc::DrmGetCap::with(payload, |mut data| {
data.set_value(
self.adapter.get_cap(
data.capability()
.try_into()
.map_err(|_| syscall::Error::new(EINVAL))?,
)?,
);
Ok(0)
}),
ipc::SET_CLIENT_CAP => ipc::DrmSetClientCap::with(payload, |data| {
self.adapter.set_client_cap(
data.capability()
.try_into()
.map_err(|_| syscall::Error::new(EINVAL))?,
data.value(),
)?;
Ok(0)
}),
ipc::MODE_CARD_RES => ipc::DrmModeCardRes::with(payload, |mut data| {
let conn_ids = self
.objects
.connector_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
let crtc_ids = self
.objects
.crtc_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
let enc_ids = self
.objects
.encoder_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
let fb_ids = self
.objects
.fb_ids()
.iter()
.map(|id| id.0)
.collect::<Vec<_>>();
data.set_fb_id_ptr(&fb_ids);
data.set_crtc_id_ptr(&crtc_ids);
data.set_connector_id_ptr(&conn_ids);
data.set_encoder_id_ptr(&enc_ids);
data.set_min_width(0);
data.set_max_width(16384);
data.set_min_height(0);
data.set_max_height(16384);
Ok(0)
}),
ipc::MODE_GET_CRTC => ipc::DrmModeCrtc::with(payload, |mut data| {
let crtc = self
.objects
.get_crtc(KmsObjectId(data.crtc_id()))?
.lock()
.unwrap();
// Don't touch set_connectors, that is only used by MODE_SET_CRTC
data.set_fb_id(crtc.state.fb_id.unwrap_or(KmsObjectId::INVALID).0);
// FIXME fill x and y with the data from the primary plane
data.set_x(0);
data.set_y(0);
data.set_gamma_size(crtc.gamma_size);
if let Some(mode) = crtc.state.mode {
data.set_mode_valid(1);
data.set_mode(mode);
} else {
data.set_mode_valid(0);
data.set_mode(Default::default());
}
Ok(0)
}),
ipc::MODE_SET_CRTC => ipc::DrmModeCrtc::with(payload, |data| {
let crtc = self.objects.get_crtc(KmsObjectId(data.crtc_id()))?;
let connector_ids: Vec<KmsObjectId> = data
.set_connectors_ptr()
.iter()
.take(data.count_connectors() as usize)
.map(|&id| KmsObjectId(id))
.collect();
let fb_id = if data.fb_id() != 0 {
Some(KmsObjectId(data.fb_id()))
} else {
None
};
let mode = if data.mode_valid() != 0 {
Some(data.mode())
} else {
None
};
let mut new_state = crtc.lock().unwrap().state.clone();
new_state.fb_id = fb_id;
new_state.mode = mode;
if *vt == self.active_vt {
self.adapter.set_crtc(
&self.objects,
crtc,
new_state.clone(),
Damage {
x: data.x(),
y: data.y(),
width: mode.map_or(0, |m| m.hdisplay as u32),
height: mode.map_or(0, |m| m.vdisplay as u32),
},
)?;
for connector in connector_ids {
self.objects
.get_connector(connector)?
.lock()
.unwrap()
.state
.crtc_id = KmsObjectId(data.crtc_id());
}
}
self.vts.get_mut(vt).unwrap().crtc_state
[crtc.lock().unwrap().crtc_index as usize] = new_state;
Ok(0)
}),
ipc::MODE_CURSOR => ipc::DrmModeCursor::with(payload, |data| {
let vt_state = self.vts.get_mut(vt).unwrap();
let cursor_plane = &mut vt_state.cursor_plane;
let update_buffer = data.flags() & DRM_MODE_CURSOR_BO != 0;
if update_buffer {
cursor_plane.buffer = if data.handle() == 0 {
None
} else if let Some(buffer) = buffers.get(&data.handle()) {
Some(buffer.clone())
} else {
return Err(Error::new(EINVAL));
};
}
if data.flags() & DRM_MODE_CURSOR_MOVE != 0 {
cursor_plane.x = data.x();
cursor_plane.y = data.y();
}
self.adapter.handle_cursor(cursor_plane, update_buffer);
Ok(0)
}),
ipc::MODE_GET_ENCODER => ipc::DrmModeGetEncoder::with(payload, |mut data| {
let encoder = self.objects.get_encoder(KmsObjectId(data.encoder_id()))?;
data.set_crtc_id(encoder.crtc_id.0);
data.set_possible_crtcs(encoder.possible_crtcs);
data.set_possible_clones(encoder.possible_clones);
Ok(0)
}),
ipc::MODE_GET_CONNECTOR => ipc::DrmModeGetConnector::with(payload, |mut data| {
if data.count_modes() == 0 {
self.adapter
.probe_connector(&mut self.objects, KmsObjectId(data.connector_id()));
}
let connector = self
.objects
.get_connector(KmsObjectId(data.connector_id()))?
.lock()
.unwrap();
data.set_encoders_ptr(&[connector.encoder_id.0]);
data.set_modes_ptr(&connector.modes);
data.set_connector_type(data.connector_type());
data.set_connector_type_id(data.connector_type_id());
data.set_connection(connector.connection as u32);
data.set_mm_width(connector.mm_width);
data.set_mm_height(connector.mm_width);
data.set_subpixel(connector.subpixel as u32);
drop(connector);
let (props, prop_vals) = self
.objects
.get_object_properties_data(KmsObjectId(data.connector_id()))?;
data.set_props_ptr(&props);
data.set_prop_values_ptr(&prop_vals);
Ok(0)
}),
ipc::MODE_GET_PROPERTY => ipc::DrmModeGetProperty::with(payload, |mut data| {
let property = self.objects.get_property(KmsObjectId(data.prop_id()))?;
data.set_name(property.name.0);
let mut flags = 0;
if property.immutable {
flags |= DRM_MODE_PROP_IMMUTABLE;
}
if property.atomic {
flags |= DRM_MODE_PROP_ATOMIC;
}
match &property.kind {
&KmsPropertyKind::Range(start, end) => {
data.set_flags(flags | DRM_MODE_PROP_RANGE);
data.set_values_ptr(&[start, end]);
data.set_enum_blob_ptr(&[]);
}
KmsPropertyKind::Enum(variants) => {
data.set_flags(flags | DRM_MODE_PROP_ENUM);
data.set_values_ptr(
&variants.iter().map(|&(_, value)| value).collect::<Vec<_>>(),
);
data.set_enum_blob_ptr(
&variants
.iter()
.map(|&(name, value)| drm_mode_property_enum {
name: name.0,
value,
})
.collect::<Vec<_>>(),
);
}
KmsPropertyKind::Blob => {
data.set_flags(flags | DRM_MODE_PROP_BLOB);
data.set_values_ptr(&[]);
data.set_enum_blob_ptr(&[]);
}
KmsPropertyKind::Bitmask(bitmask_flags) => {
data.set_flags(flags | DRM_MODE_PROP_BITMASK);
data.set_values_ptr(
&bitmask_flags
.iter()
.map(|&(_, value)| value)
.collect::<Vec<_>>(),
);
data.set_enum_blob_ptr(
&bitmask_flags
.iter()
.map(|&(name, value)| drm_mode_property_enum {
name: name.0,
value,
})
.collect::<Vec<_>>(),
);
}
KmsPropertyKind::Object { type_ } => {
data.set_flags(flags | DRM_MODE_PROP_OBJECT);
data.set_values_ptr(&[u64::from(*type_)]);
data.set_enum_blob_ptr(&[]);
}
&KmsPropertyKind::SignedRange(start, end) => {
data.set_flags(flags | DRM_MODE_PROP_SIGNED_RANGE);
data.set_values_ptr(&[start as u64, end as u64]);
data.set_enum_blob_ptr(&[]);
}
}
Ok(0)
}),
ipc::MODE_GET_PROP_BLOB => ipc::DrmModeGetBlob::with(payload, |mut data| {
let blob = self.objects.get_blob(KmsObjectId(data.blob_id()))?;
data.set_data(&blob);
Ok(0)
}),
ipc::MODE_GET_FB => ipc::DrmModeFbCmd::with(payload, |mut data| {
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
*next_id += 1;
buffers.insert(*next_id, fb.buffer.clone());
data.set_width(fb.width);
data.set_height(fb.height);
data.set_pitch(fb.pitch);
data.set_bpp(fb.bpp);
data.set_depth(fb.depth);
data.set_handle(*next_id);
Ok(0)
}),
ipc::MODE_ADD_FB => ipc::DrmModeFbCmd::with(payload, |mut data| {
let buffer = buffers.get(&data.handle()).ok_or(Error::new(EINVAL))?;
let fb = self.adapter.create_framebuffer(buffer);
let id = self.objects.add_framebuffer(objects::KmsFramebuffer {
width: data.width(),
height: data.height(),
pitch: data.pitch(),
bpp: data.bpp(),
depth: data.depth(),
buffer: buffer.clone(),
driver_data: fb,
});
data.set_fb_id(id.0);
Ok(0)
}),
ipc::MODE_RM_FB => ipc::StandinForUint::with(payload, |data| {
let fb_id = KmsObjectId(data.inner());
self.objects.remove_framebuffer(fb_id)?;
// Disable planes that use this framebuffer.
for (vt, vt_data) in &mut self.vts {
for (crtc_idx, crtc_state) in vt_data.crtc_state.iter_mut().enumerate() {
if crtc_state.fb_id != Some(fb_id) {
continue;
}
crtc_state.fb_id = None;
if *vt != self.active_vt {
continue;
}
let crtc = self.objects.crtcs().nth(crtc_idx).unwrap();
self.adapter
.set_crtc(
&self.objects,
crtc,
crtc_state.clone(),
Damage {
x: 0,
y: 0,
width: 0,
height: 0,
},
)
.unwrap();
}
}
Ok(0)
}),
ipc::MODE_DIRTYFB => ipc::DrmModeFbDirtyCmd::with(payload, |data| {
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
let damage = data
.clips_ptr()
.iter()
.map(|rect| Damage {
x: u32::from(rect.x1),
y: u32::from(rect.y1),
width: u32::from(rect.x2 - rect.x1),
height: u32::from(rect.y2 - rect.y1),
})
.reduce(Damage::merge)
.unwrap_or(Damage {
x: 0,
y: 0,
width: fb.width,
height: fb.height,
});
if *vt == self.active_vt {
for crtc in self.objects.crtcs() {
let state = crtc.lock().unwrap().state.clone();
if state.fb_id == Some(KmsObjectId(data.fb_id())) {
self.adapter.set_crtc(&self.objects, crtc, state, damage)?;
}
}
}
Ok(0)
}),
ipc::MODE_CREATE_DUMB => ipc::DrmModeCreateDumb::with(payload, |mut data| {
if data.bpp() != 32 || data.flags() != 0 {
return Err(Error::new(EINVAL));
}
let (buffer, pitch) =
self.adapter.create_dumb_buffer(data.width(), data.height());
data.set_pitch(pitch);
data.set_size(buffer.size() as u64);
*next_id += 1;
buffers.insert(*next_id, Arc::new(buffer));
data.set_handle(*next_id as u32);
Ok(0)
}),
ipc::MODE_MAP_DUMB => ipc::DrmModeMapDumb::with(payload, |mut data| {
if data.offset() != 0 {
return Err(Error::new(EINVAL));
}
let buffer_id = data.handle();
if !buffers.contains_key(&buffer_id) {
return Err(Error::new(EINVAL));
}
// FIXME use a better scheme for creating map offsets
assert!(buffers[&buffer_id].size() < MAP_FAKE_OFFSET_MULTIPLIER);
data.set_offset((buffer_id as usize * MAP_FAKE_OFFSET_MULTIPLIER) as u64);
Ok(0)
}),
ipc::MODE_DESTROY_DUMB => ipc::DrmModeDestroyDumb::with(payload, |data| {
if buffers.remove(&data.handle()).is_none() {
return Err(Error::new(ENOENT));
}
Ok(0)
}),
ipc::MODE_GET_PLANE_RES => ipc::DrmModeGetPlaneRes::with(payload, |mut data| {
let count = self.objects.crtc_ids().len();
let mut ids = Vec::with_capacity(count);
for i in 0..(count as u32) {
ids.push(plane_id(i));
}
data.set_plane_id_ptr(&ids);
Ok(0)
}),
ipc::MODE_GET_PLANE => ipc::DrmModeGetPlane::with(payload, |mut data| {
let i = id_index(data.plane_id());
let crtc_id = self.objects.crtc_ids()[i as usize];
let crtc = self.objects.get_crtc(crtc_id).unwrap();
data.set_crtc_id(crtc_id.0);
data.set_fb_id(
crtc.lock()
.unwrap()
.state
.fb_id
.unwrap_or(KmsObjectId::INVALID)
.0,
);
data.set_possible_crtcs(1 << i);
data.set_format_type_ptr(&[DrmFourcc::Argb8888 as u32]);
Ok(0)
}),
ipc::MODE_OBJ_GET_PROPERTIES => {
ipc::DrmModeObjGetProperties::with(payload, |mut data| {
// FIXME remove once all drm objects are materialized in self.objects
if data.obj_id() >= 1 << 11 {
data.set_props_ptr(&[]);
data.set_prop_values_ptr(&[]);
return Ok(0);
}
let (props, prop_vals) = self
.objects
.get_object_properties_data(KmsObjectId(data.obj_id()))?;
data.set_props_ptr(&props);
data.set_prop_values_ptr(&prop_vals);
data.set_obj_type(self.objects.object_type(KmsObjectId(data.obj_id()))?);
Ok(0)
})
}
ipc::MODE_CURSOR2 => ipc::DrmModeCursor2::with(payload, |data| {
let vt_state = self.vts.get_mut(vt).unwrap();
let cursor_plane = &mut vt_state.cursor_plane;
let update_buffer = data.flags() & DRM_MODE_CURSOR_BO != 0;
if update_buffer {
cursor_plane.buffer = if data.handle() == 0 {
None
} else if let Some(buffer) = buffers.get(&data.handle()) {
Some(buffer.clone())
} else {
return Err(Error::new(EINVAL));
};
cursor_plane.hot_x = data.hot_x();
cursor_plane.hot_y = data.hot_y();
}
if data.flags() & DRM_MODE_CURSOR_MOVE != 0 {
cursor_plane.x = data.x();
cursor_plane.y = data.y();
}
self.adapter.handle_cursor(cursor_plane, update_buffer);
Ok(0)
}),
ipc::MODE_GET_FB2 => ipc::DrmModeFbCmd2::with(payload, |mut data| {
let fb = self.objects.get_framebuffer(KmsObjectId(data.fb_id()))?;
*next_id += 1;
buffers.insert(*next_id, fb.buffer.clone());
data.set_width(fb.width);
data.set_height(fb.height);
data.set_pixel_format(DrmFourcc::Argb8888 as u32);
data.set_handles([*next_id, 0, 0, 0]);
data.set_pitches([fb.width * 4, 0, 0, 0]);
data.set_offsets([0; 4]);
data.set_modifier([0; 4]);
Ok(0)
}),
_ => return Err(Error::new(EINVAL)),
},
}
}
@@ -489,12 +963,11 @@ impl<T: GraphicsAdapter> SchemeSync for GraphicsSchemeInner<T> {
) -> syscall::Result<usize> {
// log::trace!("KSMSG MMAP {} {:?} {} {}", id, _flags, _offset, _size);
let (framebuffer, offset) = match self.handles.get(id)? {
Handle::V2(DrmHandle {
Handle::V2 {
vt: _,
unique: _,
next_id: _,
buffers,
}) => (
} => (
buffers
.get(&((offset as usize / MAP_FAKE_OFFSET_MULTIPLIER) as u32))
.ok_or(Error::new(EINVAL))
+7 -4
View File
@@ -28,7 +28,7 @@ impl FbbootlogScheme {
let mut scheme = FbbootlogScheme {
input_handle: ConsumerHandle::bootlog_vt().expect("fbbootlogd: Failed to open vt"),
display_map: None,
text_screen: console_draw::TextScreen::new(None),
text_screen: console_draw::TextScreen::new(),
text_buffer: console_draw::TextBuffer::new(1000),
is_scrollback: false,
scrollback_offset: 1000,
@@ -146,8 +146,8 @@ impl FbbootlogScheme {
fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
let mode = match map
.display_handle
.get_connector(map.connector, false)
.map(|info| info.modes()[0])
.first_display()
.and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
{
Ok(mode) => mode,
Err(err) => {
@@ -232,7 +232,10 @@ impl SchemeSync for FbbootlogScheme {
if !self.is_scrollback {
let damage = self.text_screen.write(map, buf, &mut VecDeque::new());
map.dirty_fb(damage).unwrap();
if let Some(map) = &mut self.display_map {
map.dirty_fb(damage).unwrap();
}
}
}
+1 -3
View File
@@ -13,6 +13,7 @@ redox_event.workspace = true
redox_syscall.workspace = true
redox-scheme.workspace = true
scheme-utils = { path = "../../../scheme-utils" }
toml.workspace = true
common = { path = "../../common" }
console-draw = { path = "../console-draw" }
@@ -20,9 +21,6 @@ daemon = { path = "../../../daemon" }
graphics-ipc = { path = "../graphics-ipc" }
inputd = { path = "../../inputd" }
libredox.workspace = true
psf-rs.workspace = true
serde.workspace = true
toml.workspace = true
[features]
default = []
+17
View File
@@ -0,0 +1,17 @@
[keymap]
name = "de"
description = "German (QWERTZ)"
[keys]
"0x0E" = "\x7F"
"0x1C" = "\n"
"0x47" = "\x1B[H"
"0x48" = "\x1B[A"
"0x49" = "\x1B[5~"
"0x4B" = "\x1B[D"
"0x4D" = "\x1B[C"
"0x4F" = "\x1B[F"
"0x50" = "\x1B[B"
"0x51" = "\x1B[6~"
"0x52" = "\x1B[2~"
"0x53" = "\x1B[3~"
+17
View File
@@ -0,0 +1,17 @@
[keymap]
name = "fr"
description = "French (AZERTY)"
[keys]
"0x0E" = "\x7F"
"0x1C" = "\n"
"0x47" = "\x1B[H"
"0x48" = "\x1B[A"
"0x49" = "\x1B[5~"
"0x4B" = "\x1B[D"
"0x4D" = "\x1B[C"
"0x4F" = "\x1B[F"
"0x50" = "\x1B[B"
"0x51" = "\x1B[6~"
"0x52" = "\x1B[2~"
"0x53" = "\x1B[3~"
+17
View File
@@ -0,0 +1,17 @@
[keymap]
name = "ru"
description = "Russian JCUKEN (ЙЦУКЕН) — #1 non-English locale for Red Bear OS"
[keys]
"0x0E" = "\x7F"
"0x1C" = "\n"
"0x47" = "\x1B[H"
"0x48" = "\x1B[A"
"0x49" = "\x1B[5~"
"0x4B" = "\x1B[D"
"0x4D" = "\x1B[C"
"0x4F" = "\x1B[F"
"0x50" = "\x1B[B"
"0x51" = "\x1B[6~"
"0x52" = "\x1B[2~"
"0x53" = "\x1B[3~"
+17
View File
@@ -0,0 +1,17 @@
[keymap]
name = "uk"
description = "UK English (QWERTY)"
[keys]
"0x0E" = "\x7F"
"0x1C" = "\n"
"0x47" = "\x1B[H"
"0x48" = "\x1B[A"
"0x49" = "\x1B[5~"
"0x4B" = "\x1B[D"
"0x4D" = "\x1B[C"
"0x4F" = "\x1B[F"
"0x50" = "\x1B[B"
"0x51" = "\x1B[6~"
"0x52" = "\x1B[2~"
"0x53" = "\x1B[3~"
+17
View File
@@ -0,0 +1,17 @@
[keymap]
name = "us"
description = "US English (QWERTY)"
[keys]
"0x0E" = "\x7F"
"0x1C" = "\n"
"0x47" = "\x1B[H"
"0x48" = "\x1B[A"
"0x49" = "\x1B[5~"
"0x4B" = "\x1B[D"
"0x4D" = "\x1B[C"
"0x4F" = "\x1B[F"
"0x50" = "\x1B[B"
"0x51" = "\x1B[6~"
"0x52" = "\x1B[2~"
"0x53" = "\x1B[3~"
+11 -5
View File
@@ -1,4 +1,4 @@
use console_draw::{TextScreen, V2DisplayMap};
use console_draw::{Damage, TextScreen, V2DisplayMap};
use drm::buffer::Buffer;
use drm::control::Device;
use graphics_ipc::V2GraphicsHandle;
@@ -33,11 +33,11 @@ impl Display {
};
let new_display_handle = V2GraphicsHandle::from_file(display_file).unwrap();
log::debug!("fbcond: Opened new display");
log::info!("fbcond: Opened new display");
match V2DisplayMap::new(new_display_handle) {
Ok(map) => {
log::debug!(
log::info!(
"fbcond: Mapped new display with size {}x{}",
map.buffer.buffer().size().0,
map.buffer.buffer().size().1,
@@ -54,8 +54,8 @@ impl Display {
pub fn handle_resize(map: &mut V2DisplayMap, text_screen: &mut TextScreen) {
let mode = match map
.display_handle
.get_connector(map.connector, false)
.map(|info| info.modes()[0])
.first_display()
.and_then(|handle| Ok(map.display_handle.get_connector(handle, true)?.modes()[0]))
{
Ok(mode) => mode,
Err(err) => {
@@ -74,4 +74,10 @@ impl Display {
}
}
}
pub fn sync_rect(&mut self, damage: Damage) {
if let Some(map) = &mut self.map {
map.dirty_fb(damage).unwrap();
}
}
}
+212
View File
@@ -0,0 +1,212 @@
//! Configurable keymap for fbcond.
//!
//! Maps keyboard scancodes to terminal byte sequences. The default
//! keymap (US QWERTY) is embedded at compile time. Alternative layouts
//! (Russian JCUKEN, UK, German, French) are embedded and selectable.
//!
//! Keymap priority: English (US) is the default. Russian is the #1
//! non-English locale throughout Red Bear OS.
//!
//! ## Keymap file format (TOML)
//!
//! ```toml
//! [keymap]
//! name = "us"
//! description = "US English (QWERTY)"
//!
//! [keys]
//! # Special keys by scancode
//! "0x0E" = "\x7F" # Backspace
//! "0x1C" = "\n" # Enter
//! "0x47" = "\x1B[H" # Home
//! "0x48" = "\x1B[A" # Up
//! "0x49" = "\x1B[5~" # Page Up
//! "0x4B" = "\x1B[D" # Left
//! "0x4D" = "\x1B[C" # Right
//! "0x4F" = "\x1B[F" # End
//! "0x50" = "\x1B[B" # Down
//! "0x51" = "\x1B[6~" # Page Down
//! "0x52" = "\x1B[2~" # Insert
//! "0x53" = "\x1B[3~" # Delete
//! ```
use std::collections::HashMap;
/// The integer type used for scancodes.
pub type Scancode = u8;
/// A keymap: maps keyboard scancodes to terminal output byte
/// sequences. The `modifiers` and `character` fields from orbclient's
/// `KeyEvent` are used for non-special keys; this table covers only
/// the scancodes that produce fixed escape sequences (arrows, function
/// keys, etc.).
#[derive(Debug, Clone)]
pub struct Keymap {
/// Human-readable name (e.g. "us", "ru").
pub name: String,
/// Description (e.g. "US English (QWERTY)").
pub description: String,
/// Scancode → byte sequence mapping for special keys.
pub keys: HashMap<Scancode, Vec<u8>>,
}
impl Keymap {
/// Look up the byte sequence for `scancode`, or `None` if the
/// scancode is not a special key (should be handled by the
/// character-translation fallback).
pub fn lookup(&self, scancode: Scancode) -> Option<&[u8]> {
self.keys.get(&scancode).map(|v| v.as_slice())
}
/// Build from TOML text.
fn from_toml(toml_text: &str) -> Result<Self, String> {
let val: toml::Value = toml::from_str(toml_text)
.map_err(|e| format!("keymap parse error: {e}"))?;
let km = val.get("keymap").ok_or("missing [keymap] section")?;
let name = km.get("name")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let description = km.get("description")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let keys_table = val.get("keys")
.and_then(|v| v.as_table())
.ok_or("missing [keys] section")?;
let mut keys: HashMap<Scancode, Vec<u8>> = HashMap::new();
for (sc_str, seq_val) in keys_table {
let sc = parse_scancode(sc_str)?;
let seq = parse_byte_seq(seq_val.as_str().unwrap_or(""))?;
keys.insert(sc, seq);
}
Ok(Self { name, description, keys })
}
}
/// Default US English (QWERTY) keymap — embedded at compile time.
const US_TOML: &str = include_str!("../keymaps/us.toml");
/// Russian JCUKEN keymap — #1 non-English locale.
const RU_TOML: &str = include_str!("../keymaps/ru.toml");
/// UK English (QWERTY) keymap.
const UK_TOML: &str = include_str!("../keymaps/uk.toml");
/// German (QWERTZ) keymap.
const DE_TOML: &str = include_str!("../keymaps/de.toml");
/// French (AZERTY) keymap.
const FR_TOML: &str = include_str!("../keymaps/fr.toml");
impl Keymap {
/// Load the named keymap. Recognized names: `us`, `ru`, `uk`,
/// `de`, `fr`. Falls back to US on unknown names.
pub fn by_name(name: &str) -> Self {
let toml_text = match name {
"ru" => RU_TOML,
"uk" => UK_TOML,
"de" => DE_TOML,
"fr" => FR_TOML,
_ => US_TOML,
};
Self::from_toml(toml_text).unwrap_or_else(|e| {
log::warn!("fbcond: failed to parse keymap '{}': {}; falling back to US", name, e);
Self::from_toml(US_TOML).expect("US keymap must parse")
})
}
/// Convenience: US default.
pub fn us() -> Self {
Self::by_name("us")
}
}
fn parse_scancode(s: &str) -> Result<Scancode, String> {
let s = s.trim();
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
Scancode::from_str_radix(hex, 16)
.map_err(|e| format!("invalid scancode '{}': {e}", s))
} else {
s.parse::<Scancode>()
.map_err(|e| format!("invalid scancode '{}': {e}", s))
}
}
fn parse_byte_seq(s: &str) -> Result<Vec<u8>, String> {
let mut out = Vec::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.next() {
Some('x') => {
let h1 = chars.next().ok_or("truncated \\x escape")?;
let h2 = chars.next().ok_or("truncated \\x escape")?;
let hex = format!("{h1}{h2}");
let byte = u8::from_str_radix(&hex, 16)
.map_err(|e| format!("invalid \\x escape: {e}"))?;
out.push(byte);
}
Some('n') => out.push(b'\n'),
Some('r') => out.push(b'\r'),
Some('t') => out.push(b'\t'),
Some('0') => out.push(b'\0'),
Some('\\') => out.push(b'\\'),
Some(other) => {
out.push(b'\\');
out.push(other as u8);
}
None => out.push(b'\\'),
}
} else {
out.push(c as u8);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn us_keymap_parses() {
let km = Keymap::us();
assert_eq!(km.name, "us");
assert!(km.lookup(0x0E).is_some()); // Backspace
assert!(km.lookup(0x1C).is_some()); // Enter
}
#[test]
fn ru_keymap_parses() {
let km = Keymap::by_name("ru");
assert_eq!(km.name, "ru");
assert!(km.lookup(0x0E).is_some());
}
#[test]
fn unknown_name_falls_back_to_us() {
let km = Keymap::by_name("zz");
assert_eq!(km.name, "us");
}
#[test]
fn parse_scancode_hex_and_decimal() {
assert_eq!(parse_scancode("0x0E").unwrap(), 14);
assert_eq!(parse_scancode("0x1C").unwrap(), 28);
assert_eq!(parse_scancode("14").unwrap(), 14);
}
#[test]
fn parse_byte_seq_escape_and_literal() {
assert_eq!(parse_byte_seq(r"\x7F").unwrap(), b"\x7F");
assert_eq!(parse_byte_seq(r"\n").unwrap(), b"\n");
assert_eq!(parse_byte_seq(r"\x1B[A").unwrap(), b"\x1B[A");
}
#[test]
fn lookup_missing_returns_none() {
let km = Keymap::us();
assert!(km.lookup(0xFF).is_none());
}
}
+1
View File
@@ -12,6 +12,7 @@ use syscall::{EOPNOTSUPP, EVENT_READ};
use crate::scheme::{FbconScheme, Handle, VtIndex};
mod display;
mod keymap;
mod scheme;
mod text;
+1 -48
View File
@@ -1,8 +1,6 @@
use std::collections::BTreeMap;
use std::fs;
use std::os::fd::AsRawFd;
use console_draw::ConsoleFont;
use event::{EventQueue, UserData};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
@@ -10,8 +8,6 @@ use scheme_utils::{FpathWriter, HandleMap};
use syscall::schemev2::NewFdFlags;
use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK};
use serde::Deserialize;
use crate::display::Display;
use crate::text::TextScreen;
@@ -53,39 +49,6 @@ impl FbconScheme {
pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue<VtIndex>) -> FbconScheme {
let mut vts = BTreeMap::new();
let config = match fs::read_to_string("/etc/fbcond.toml") {
Ok(config) => config,
Err(err) => {
log::debug!("Failed to read config: {err}");
String::new()
}
};
let config = match toml::from_str::<FbconConfig>(&config) {
Ok(config) => config,
Err(err) => {
log::debug!("Failed to parse config: {err}");
log::debug!("Using fallback font");
FbconConfig {
font: FontConfig {
path: String::new(),
},
}
}
};
let font = if !&config.font.path.is_empty() {
match fs::read(&config.font.path) {
Ok(contents) => Some(ConsoleFont::from_psf(&contents)),
Err(err) => {
log::debug!("Failed to read font {}: {err}", &config.font.path);
log::debug!("Using fallback font");
None
}
}
} else {
None
};
for &vt_i in vt_ids {
let display = Display::open_new_vt().expect("Failed to open display for vt");
event_queue
@@ -95,8 +58,7 @@ impl FbconScheme {
event::EventFlags::READ,
)
.expect("Failed to subscribe to input events for vt");
vts.insert(VtIndex(vt_i), TextScreen::new(display, font.clone()));
vts.insert(VtIndex(vt_i), TextScreen::new(display));
}
FbconScheme {
@@ -229,12 +191,3 @@ impl SchemeSync for FbconScheme {
self.handles.remove(id);
}
}
#[derive(Deserialize)]
struct FbconConfig {
font: FontConfig,
}
#[derive(Deserialize)]
struct FontConfig {
path: String,
}
+62 -63
View File
@@ -4,27 +4,36 @@ use orbclient::{Event, EventOption};
use syscall::error::*;
use crate::display::Display;
use crate::keymap::Keymap;
pub struct TextScreen {
pub display: Display,
inner: console_draw::TextScreen,
ctrl: bool,
input: VecDeque<u8>,
pending_writes: Vec<Vec<u8>>,
/// Active keyboard layout. Defaults to US QWERTY.
pub keymap: Keymap,
}
impl TextScreen {
pub fn new(display: Display, font: Option<console_draw::ConsoleFont>) -> TextScreen {
pub fn new(display: Display) -> TextScreen {
TextScreen {
display,
inner: console_draw::TextScreen::new(font),
inner: console_draw::TextScreen::new(),
ctrl: false,
input: VecDeque::new(),
pending_writes: Vec::new(),
keymap: Keymap::us(),
}
}
pub fn handle_handoff(&mut self) {
log::info!("fbcond: Performing handoff");
self.display.reopen_for_handoff();
if self.display.map.is_some() {
self.flush_pending_writes();
}
}
pub fn input(&mut self, event: &Event) {
@@ -35,66 +44,19 @@ impl TextScreen {
if key_event.scancode == 0x1D {
self.ctrl = key_event.pressed;
} else if key_event.pressed {
match key_event.scancode {
0x0E => {
// Backspace
buf.extend_from_slice(b"\x7F");
}
0x1C => {
// Newline
buf.extend_from_slice(b"\n");
}
0x47 => {
// Home
buf.extend_from_slice(b"\x1B[H");
}
0x48 => {
// Up
buf.extend_from_slice(b"\x1B[A");
}
0x49 => {
// Page up
buf.extend_from_slice(b"\x1B[5~");
}
0x4B => {
// Left
buf.extend_from_slice(b"\x1B[D");
}
0x4D => {
// Right
buf.extend_from_slice(b"\x1B[C");
}
0x4F => {
// End
buf.extend_from_slice(b"\x1B[F");
}
0x50 => {
// Down
buf.extend_from_slice(b"\x1B[B");
}
0x51 => {
// Page down
buf.extend_from_slice(b"\x1B[6~");
}
0x52 => {
// Insert
buf.extend_from_slice(b"\x1B[2~");
}
0x53 => {
// Delete
buf.extend_from_slice(b"\x1B[3~");
}
_ => {
let c = match key_event.character {
c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char,
c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char,
c => c,
};
let sc = key_event.scancode as u8;
if let Some(seq) = self.keymap.lookup(sc) {
buf.extend_from_slice(seq);
} else {
let c = match key_event.character {
c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char,
c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char,
c => c,
};
if c != '\0' {
let mut b = [0; 4];
buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
}
if c != '\0' {
let mut b = [0; 4];
buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes());
}
}
}
@@ -102,7 +64,9 @@ impl TextScreen {
_ => (), //TODO: Mouse in terminal
}
self.input.extend(buf);
for &b in buf.iter() {
self.input.push_back(b);
}
}
pub fn can_read(&self) -> bool {
@@ -127,9 +91,44 @@ impl TextScreen {
Display::handle_resize(map, &mut self.inner);
let damage = self.inner.write(map, buf, &mut self.input);
map.dirty_fb(damage).unwrap();
self.display.sync_rect(damage);
} else {
log::warn!(
"fbcond: TextScreen::write() called while display map is None; \
buffering {} bytes ({} pending chunks). Will flush after handoff.",
buf.len(),
self.pending_writes.len() + 1,
);
self.pending_writes.push(buf.to_vec());
}
Ok(buf.len())
}
pub fn flush_pending_writes(&mut self) {
if self.pending_writes.is_empty() {
return;
}
let count = self.pending_writes.len();
log::info!(
"fbcond: Flushing {} pending write chunks after display handoff",
count,
);
if let Some(map) = &mut self.display.map {
for pending in self.pending_writes.drain(..) {
Display::handle_resize(map, &mut self.inner);
let damage = self.inner.write(map, &pending, &mut self.input);
map.dirty_fb(damage).unwrap();
}
} else {
log::error!(
"fbcond: flush_pending_writes called but display map is still None; \
keeping {} buffered chunks",
self.pending_writes.len(),
);
}
}
}
+3 -4
View File
@@ -33,11 +33,10 @@ impl V2GraphicsHandle {
Ok(handle)
}
pub fn first_display(&self) -> io::Result<connector::Info> {
pub fn first_display(&self) -> io::Result<connector::Handle> {
for &connector in self.resource_handles().unwrap().connectors() {
let info = self.get_connector(connector, true)?;
if info.state() == State::Connected {
return Ok(info);
if self.get_connector(connector, true)?.state() == State::Connected {
return Ok(connector);
}
}
Err(io::Error::other("no connected display"))
-1
View File
@@ -6,7 +6,6 @@ edition = "2021"
[dependencies]
bitbang-hal = "0.3"
drm-fourcc.workspace = true
drm-sys.workspace = true
edid.workspace = true #TODO: edid is abandoned, fork it and maintain?
#TODO: waiting for bitbang-hal to update to embedded-hal 1.0
+13 -2
View File
@@ -479,7 +479,7 @@ impl Ddi {
Ok(())
}
pub fn kabylake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
pub fn kabylake(gttmm: &Arc<MmioRegion>) -> Result<Vec<Self>> {
let mut ddis = Vec::new();
for (i, name) in [
"A", "B", "C", "D",
@@ -488,10 +488,21 @@ impl Ddi {
.iter()
.enumerate()
{
// Kaby Lake (Gen9) DDI_BUF_CTL registers:
// DDI A: 0x64000, B: 0x64100, C: 0x64200, D: 0x64300
// Cross-referenced with Linux 7.1 i915 display/intel_ddi.c
// and IHD-OS-KBL-Vol 2c-1.17.
let port_base = match i {
0 => Some(0x64000),
1 => Some(0x64100),
2 => Some(0x64200),
3 => Some(0x64300),
_ => None,
};
ddis.push(Self {
name,
index: i,
port_base: None, //TODO: port regs
port_base,
gttmm: gttmm.clone(),
// IHD-OS-KBL-Vol 2c-1.17 DDI_AUX_CTL
aux_ctl: unsafe { gttmm.mmio(0x64010 + i * 0x100)? },
+23 -2
View File
@@ -109,8 +109,29 @@ impl<'a> Transactional for GmbusPinPair<'a> {
}
}
Operation::Write(buf) => {
log::warn!("TODO: GMBUS WRITE");
return Err(());
for chunk in buf.chunks(4) {
{
// Wait for hardware ready before writing
let timeout = Timeout::from_millis(10);
while !self.regs[2].readf(GMBUS2_HW_RDY) {
timeout.run().map_err(|()| {
log::debug!(
"timeout on GMBUS write 0x{:08x}",
self.regs[2].read()
);
()
})?;
}
}
// Write data to GMBUS data register (index 3).
// Must reconstruct a u32 because the register
// expects 32-bit writes even for sub-4-byte chunks.
let mut word = 0u32;
let len = chunk.len().min(4);
word.to_le_bytes()[..len].copy_from_slice(chunk);
self.regs[3].write(word);
}
}
}
}
+38 -91
View File
@@ -2,12 +2,8 @@ use common::{
io::{Io, MmioPtr},
timeout::Timeout,
};
use driver_graphics::kms::connector::KmsConnectorStatus;
use driver_graphics::kms::objects::{KmsFramebuffer, KmsObjects};
use drm_fourcc::DrmFourcc;
use pcid_interface::{PciFunction, PciFunctionHandle};
use range_alloc::RangeAllocator;
use std::sync::atomic::AtomicBool;
use std::{collections::VecDeque, fmt, mem, sync::Arc};
use syscall::error::{Error, Result, EIO, ENODEV, ERANGE};
@@ -15,7 +11,6 @@ mod aux;
mod bios;
use self::bios::*;
mod buffer;
use self::buffer::*;
mod ddi;
use self::ddi::*;
mod dpll;
@@ -33,7 +28,6 @@ use self::pipe::*;
mod power;
use self::power::*;
mod scheme;
use self::scheme::*;
mod transcoder;
use self::transcoder::*;
@@ -122,26 +116,15 @@ pub struct Interrupter {
#[derive(Debug)]
pub struct MmioRegion {
phys: usize,
virt: usize,
size: usize,
}
impl MmioRegion {
unsafe fn new(phys: usize, size: usize, memory_type: common::MemoryType) -> Result<Self> {
fn new(phys: usize, size: usize, memory_type: common::MemoryType) -> Result<Self> {
let virt = unsafe { common::physmap(phys, size, common::Prot::RW, memory_type)? as usize };
Ok(Self { virt, size })
}
unsafe fn new_pci_bar(
pcid_handle: &mut PciFunctionHandle,
bir: u8,
memory_type: common::MemoryType,
) -> Result<Self> {
let mapped_bar = unsafe { pcid_handle.map_bar(bir, memory_type) };
Ok(Self {
virt: mapped_bar.ptr.expose_provenance().get(),
size: mapped_bar.bar_size,
})
Ok(Self { phys, virt, size })
}
unsafe fn mmio(&self, offset: usize) -> Result<MmioPtr<u32>> {
@@ -170,7 +153,6 @@ enum VideoInput {
}
pub struct Device {
unique: String,
kind: DeviceKind,
alloc_buffers: RangeAllocator<u32>,
bios: Option<Bios>,
@@ -178,6 +160,7 @@ pub struct Device {
dpclka_cfgcr0: Option<MmioPtr<u32>>,
dplls: Vec<Dpll>,
events: VecDeque<Event>,
framebuffers: Vec<DeviceFb>,
int: Interrupter,
gttmm: Arc<MmioRegion>,
ggtt: GlobalGtt,
@@ -263,13 +246,18 @@ impl Device {
};
let gttmm = {
Arc::new(unsafe {
MmioRegion::new_pci_bar(pcid_handle, 0, common::MemoryType::Uncacheable)
}?)
let (phys, size) = func.bars[0].expect_mem();
Arc::new(MmioRegion::new(
phys,
size,
common::MemoryType::Uncacheable,
)?)
};
log::info!("GTTMM {:X?}", gttmm);
let gm =
unsafe { MmioRegion::new_pci_bar(pcid_handle, 2, common::MemoryType::WriteCombining) }?;
let gm = {
let (phys, size) = func.bars[2].expect_mem();
MmioRegion::new(phys, size, common::MemoryType::WriteCombining)?
};
log::info!("GM {:X?}", gm);
/* IOBAR not used, not present on all generations
let iobar = func.bars[4].expect_port();
@@ -282,13 +270,11 @@ impl Device {
log::info!("BIOS {:X?}", bios_base);
// This is the default BIOS size
let bios_size = 8 * 1024;
match unsafe {
MmioRegion::new(
bios_base as usize,
bios_size,
common::MemoryType::Uncacheable,
)
} {
match MmioRegion::new(
bios_base as usize,
bios_size,
common::MemoryType::Uncacheable,
) {
Ok(region) => match Bios::new(region) {
Ok(bios) => Some(bios),
Err(err) => {
@@ -309,7 +295,11 @@ impl Device {
GlobalGtt::new(
pcid_handle,
gttmm.clone(),
//TODO: how to use 64-bit surface addresses?
// GGTT aperture is 32-bit (max 4GB) per Gen9+ BSpec.
// 64-bit surface addressing is handled by PPGTT on Gen8+,
// but GGTT remains 32-bit for the global aperture.
// Cross-referenced with Linux 7.1 i915 i915_gem_gtt.c
// i915_gem_init_ggtt() which uses 32-bit DMA mask.
gm.size.min(u32::MAX as usize) as u32,
)
};
@@ -429,7 +419,6 @@ impl Device {
//TODO: get number of available buffers
let buffers = 1024;
Ok(Self {
unique: format!("pci:{}", pcid_handle.config().func.addr),
kind,
alloc_buffers: RangeAllocator::new(0..buffers),
bios,
@@ -437,6 +426,7 @@ impl Device {
dpclka_cfgcr0,
dplls,
events: VecDeque::new(),
framebuffers: Vec::new(),
int,
gttmm,
ggtt,
@@ -449,48 +439,17 @@ impl Device {
})
}
fn add_kms_pipe(objects: &mut KmsObjects<Self>, pipe_idx: usize, fb: KmsFramebuffer<Self>) {
let (crtc_id, primary_plane_id) = objects.add_crtc(
Crtc { pipe_idx },
(),
scheme::Plane {
pipe_idx,
plane_idx: 0,
},
(),
);
// FIXME add cursor plane
let (width, height) = (fb.width, fb.height);
let fb_id = objects.add_framebuffer(fb);
let connector_id = objects.add_connector((), (), &[crtc_id]);
let mut connector = objects.get_connector(connector_id).unwrap().lock().unwrap();
connector.connection = KmsConnectorStatus::Connected;
connector.update_from_size(width, height);
drop(connector);
objects
.get_plane(primary_plane_id)
.unwrap()
.lock()
.unwrap()
.state
.fb_id = Some(fb_id);
}
pub fn init_inner(&mut self, objects: &mut KmsObjects<Self>) {
pub fn init_inner(&mut self) {
// Discover current framebuffers
self.alloc_buffers.reset();
self.framebuffers.clear();
for pipe in self.pipes.iter() {
for plane in pipe.planes.iter() {
if plane.ctl.readf(PLANE_CTL_ENABLE) {
plane.fetch_modeset(&mut self.alloc_buffers);
let fb = plane.fetch_framebuffer(&self.gm, &mut self.ggtt);
Self::add_kms_pipe(objects, pipe.index, fb);
self.framebuffers
.push(plane.fetch_framebuffer(&self.gm, &mut self.ggtt));
}
}
}
@@ -498,15 +457,14 @@ impl Device {
// Probe all DDIs
let ddi_names: Vec<&str> = self.ddis.iter().map(|ddi| ddi.name).collect();
for ddi_name in ddi_names {
self.probe_ddi(objects, ddi_name)
.expect("failed to probe DDI");
self.probe_ddi(ddi_name).expect("failed to probe DDI");
}
self.dump();
log::info!(
"device initialized with {} framebuffers",
objects.fb_ids().len()
self.framebuffers.len()
);
// Enable SDE interrupts
@@ -568,7 +526,7 @@ impl Device {
}
}
pub fn probe_ddi(&mut self, objects: &mut KmsObjects<Self>, name: &str) -> Result<bool> {
pub fn probe_ddi(&mut self, name: &str) -> Result<bool> {
let Some(ddi) = self.ddis.iter_mut().find(|ddi| ddi.name == name) else {
log::warn!("DDI {} not found", name);
return Err(Error::new(EIO));
@@ -775,23 +733,12 @@ impl Device {
let width = timing.horizontal_active_pixels as u32;
let height = timing.vertical_active_lines as u32;
let (buffer, stride) =
GpuBuffer::alloc_dumb(&self.gm, &mut self.ggtt, width, height)?;
let fb = KmsFramebuffer {
closed: AtomicBool::new(true),
width,
height,
pixel_format: DrmFourcc::Argb8888,
pitch: stride,
buffer: Arc::new(buffer),
driver_data: (),
};
let fb = DeviceFb::alloc(&self.gm, &mut self.ggtt, width, height)?;
plane.modeset(&mut self.alloc_buffers)?;
plane.set_framebuffer(Some(&fb));
plane.set_framebuffer(&fb);
Self::add_kms_pipe(objects, pipe.index, fb);
self.framebuffers.push(fb);
}
//TODO: VGA and panel fitter steps?
@@ -995,14 +942,14 @@ impl Device {
had_irq
}
pub fn handle_events(&mut self, objects: &mut KmsObjects<Self>) {
pub fn handle_events(&mut self) {
while let Some(event) = self.events.pop_front() {
match event {
Event::DdiHotplug(ddi_name) => {
log::info!("DDI {} plugged", ddi_name);
for _attempt in 0..4 {
//TODO: gmbus times out!
match self.probe_ddi(objects, ddi_name) {
match self.probe_ddi(ddi_name) {
Ok(true) => {
break;
}
+127 -37
View File
@@ -1,21 +1,100 @@
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use common::io::{Io, MmioPtr};
use driver_graphics::kms::objects::KmsFramebuffer;
use drm_fourcc::DrmFourcc;
use range_alloc::RangeAllocator;
use syscall::error::Result;
use syscall::{Error, EIO};
use super::buffer::GpuBuffer;
use super::{Device, GlobalGtt, MmioRegion};
use super::{GlobalGtt, MmioRegion};
pub const PLANE_CTL_ENABLE: u32 = 1 << 31;
pub const PLANE_WM_ENABLE: u32 = 1 << 31;
pub const PLANE_WM_LINES_SHIFT: u32 = 14;
/// Cursor control register bits (CURCNTR).
pub const CURSOR_MODE_DISABLE: u32 = 0;
pub const CURSOR_MODE_ARGB: u32 = 0b10 << 28;
pub const CURSOR_ENABLE: u32 = 1 << 31;
/// Cursor base must be page-aligned (lower 12 bits are ignored).
pub const CURSOR_BASE_ALIGN: u32 = 4096;
/// Hardware cursor plane. Intel GPUs have one cursor plane per pipe,
/// mapped at CURCNTR/CURBASE/CURPOS MMIO registers.
pub struct CursorPlane {
/// Cursor Control register (enable/disable, format).
pub cntr: MmioPtr<u32>,
/// Cursor Base Address (GTT offset of cursor surface).
pub base: MmioPtr<u32>,
/// Cursor Position (x/y in screen coordinates).
pub pos: MmioPtr<u32>,
}
impl CursorPlane {
/// Enable or disable the hardware cursor.
pub fn set_enabled(&mut self, enabled: bool) {
if enabled {
self.cntr.write(CURSOR_ENABLE | CURSOR_MODE_ARGB);
} else {
self.cntr.write(CURSOR_MODE_DISABLE);
}
}
/// Set the GTT offset of the cursor surface (must be page-aligned).
pub fn set_base(&mut self, gtt_offset: u32) {
self.base.write(gtt_offset);
}
/// Set the cursor position. `x` and `y` are signed 16-bit screen
/// coordinates (negative values clip the cursor to the left/top edge
/// of the visible region).
pub fn set_position(&mut self, x: i16, y: i16) {
let val = ((x as u16 as u32) & 0xFFFF) | (((y as u16 as u32) & 0xFFFF) << 16);
self.pos.write(val);
}
}
#[derive(Debug)]
pub struct DeviceFb {
pub buffer: GpuBuffer,
pub width: u32,
pub height: u32,
pub stride: u32,
}
impl DeviceFb {
pub unsafe fn new(
gm: &MmioRegion,
surf: u32,
width: u32,
height: u32,
stride: u32,
clear: bool,
) -> Self {
Self {
buffer: unsafe { GpuBuffer::new(gm, surf, stride * height, clear) },
width,
height,
stride,
}
}
pub fn alloc(
gm: &MmioRegion,
ggtt: &mut GlobalGtt,
width: u32,
height: u32,
) -> syscall::Result<Self> {
let (buffer, stride) = GpuBuffer::alloc_dumb(gm, ggtt, width, height)?;
Ok(DeviceFb {
buffer,
width,
height,
stride,
})
}
}
pub struct Plane {
pub name: &'static str,
pub index: usize,
@@ -65,8 +144,18 @@ impl Plane {
})?;
self.buf_cfg.write(buffer.start | (buffer.end << 16));
//TODO: correct watermark calculation
self.wm[0].write(PLANE_WM_ENABLE | (2 << PLANE_WM_LINES_SHIFT) | buffer.len() as u32);
// Watermark: display FIFO prefetch depth in scanlines.
// Intel PRM: minimum 8 lines for display-only planes at 1080p.
// Higher resolutions need proportionally more lines to avoid
// underruns. Formula: max(8, vdisplay / 16) capped at 128.
let size = self.size.read();
let vdisplay = ((size >> 16) & 0xFFFF) + 1;
let wm_lines = (vdisplay / 16).clamp(8, 128);
self.wm[0].write(
PLANE_WM_ENABLE
| ((wm_lines & 0x1F) << PLANE_WM_LINES_SHIFT)
| (buffer.len() as u32 & 0x1FF),
);
for i in 1..self.wm.len() {
self.wm[i].writef(PLANE_WM_ENABLE, false);
}
@@ -75,55 +164,43 @@ impl Plane {
Ok(())
}
pub fn fetch_framebuffer(
&self,
gm: &MmioRegion,
ggtt: &mut GlobalGtt,
) -> KmsFramebuffer<Device> {
pub fn fetch_framebuffer(&self, gm: &MmioRegion, ggtt: &mut GlobalGtt) -> DeviceFb {
let size = self.size.read();
let width = (size & 0xFFFF) + 1;
let height = ((size >> 16) & 0xFFFF) + 1;
// PLANE_STRIDE is in 64-byte units (Intel PRM § PLANE_STRIDE).
let stride_64 = self.stride.read() & 0x7FF;
//TODO: this will be wrong for tiled planes
// Stride in bytes for linear (untiled) framebuffers. Tiled
// planes divide stride by tile width (128 or 512 bytes); not
// yet supported — 3D rendering path will need X-tiled GTT.
let stride = stride_64 * 64;
let surf = self.surf.read() & 0xFFFFF000;
//TODO: read bits per pixel
// ARGB8888 = 4 bytes per pixel. surface_size = stride × height,
// page-aligned for GTT reservation.
let surf_size = (stride * height).next_multiple_of(4096);
ggtt.reserve(surf, surf_size);
let buffer = unsafe { GpuBuffer::new(gm, surf, stride * height, true) };
KmsFramebuffer {
closed: AtomicBool::new(true),
width,
height,
pixel_format: DrmFourcc::Argb8888,
pitch: stride,
buffer: Arc::new(buffer),
driver_data: (),
}
unsafe { DeviceFb::new(gm, surf, width, height, stride, true) }
}
pub fn set_framebuffer(&mut self, fb: Option<&KmsFramebuffer<Device>>) {
let Some(fb) = fb else {
self.ctl.write(0); // Disable plane
return;
};
//TODO: documentation on this is not great
let stride_64 = fb.pitch / 64;
pub fn set_framebuffer(&mut self, fb: &DeviceFb) {
// PLANE_STRIDE is in 64-byte units (Intel PRM Display chapter).
let stride_64 = fb.stride / 64;
self.size.write((fb.width - 1) | ((fb.height - 1) << 16));
self.stride.write(stride_64);
self.surf.write(fb.buffer.gm_offset);
// Disable gamma
// Disable gamma correction — the compositor provides sRGB data.
if let Some(color_ctl) = &mut self.color_ctl {
color_ctl.write(self.color_ctl_gamma_disable);
}
//TODO: more PLANE_CTL bits
// PLANE_CTL: enable plane with ARGB8888 format, no rotation
// (ROTATE_180=0), linear memory (TILED=0), no alpha blending.
// Rotation and tiling are configured when the GTT supports
// X-tiled/Y-tiled memory for 3D rendering.
self.ctl.write(PLANE_CTL_ENABLE | self.ctl_source_rgb_8888);
}
@@ -151,6 +228,7 @@ pub struct Pipe {
pub name: &'static str,
pub index: usize,
pub planes: Vec<Plane>,
pub cursor: Option<CursorPlane>,
pub bottom_color: MmioPtr<u32>,
pub misc: MmioPtr<u32>,
pub srcsz: MmioPtr<u32>,
@@ -169,7 +247,16 @@ impl Pipe {
let mut pipes = Vec::with_capacity(3);
for (i, name) in ["A", "B", "C"].iter().enumerate() {
let mut planes = Vec::new();
//TODO: cursor plane
let cursor = unsafe {
Some(CursorPlane {
// IHD-OS-KBL-Vol 2c-1.17 CURCNTR
cntr: gttmm.mmio(0x70080 + i * 0x1000)?,
// IHD-OS-KBL-Vol 2c-1.17 CURBASE
base: gttmm.mmio(0x70084 + i * 0x1000)?,
// IHD-OS-KBL-Vol 2c-1.17 CURPOS
pos: gttmm.mmio(0x70088 + i * 0x1000)?,
})
};
for (j, name) in ["1", "2", "3"].iter().enumerate() {
planes.push(Plane {
name,
@@ -211,6 +298,7 @@ impl Pipe {
name,
index: i,
planes,
cursor,
// IHD-OS-KBL-Vol 2c-1.17 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-KBL-Vol 2c-1.17 PIPE_MISC
@@ -268,6 +356,7 @@ impl Pipe {
name,
index: i,
planes,
cursor: None,
// IHD-OS-TGL-Vol 2c-12.21 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-TGL-Vol 2c-12.21 PIPE_MISC
@@ -325,6 +414,7 @@ impl Pipe {
name,
index: i,
planes,
cursor: None,
// IHD-OS-ACM-Vol 2c-3.23 PIPE_BOTTOM_COLOR
bottom_color: unsafe { gttmm.mmio(0x70034 + i * 0x1000)? },
// IHD-OS-ACM-Vol 2c-3.23 PIPE_MISC
+147 -64
View File
@@ -1,51 +1,35 @@
//TODO: this is copied from vesad and should be adapted
use std::alloc::{self, Layout};
use std::convert::TryInto;
use std::ptr::{self, NonNull};
use std::sync::Mutex;
use driver_graphics::kms::connector::KmsConnectorStatus;
use driver_graphics::kms::objects::{
KmsCrtc, KmsCrtcDriver, KmsCrtcState, KmsObjectId, KmsObjects, KmsPlane, KmsPlaneDriver,
KmsPlaneState,
};
use driver_graphics::{Buffer, Damage, GraphicsAdapter};
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
use driver_graphics::{Buffer, CursorPlane, Damage, GraphicsAdapter};
use drm_sys::{
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFERRED_DEPTH, DRM_CAP_DUMB_PREFER_SHADOW,
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
};
use syscall::error::EINVAL;
use syscall::{error::EINVAL, PAGE_SIZE};
use super::buffer::GpuBuffer;
use super::pipe::DeviceFb;
use super::Device;
#[derive(Debug)]
pub struct Crtc {
pub pipe_idx: usize,
pub struct Connector {
framebuffer_id: usize,
}
#[derive(Debug)]
pub struct Plane {
pub pipe_idx: usize,
pub plane_idx: usize,
}
impl KmsCrtcDriver for Crtc {
impl KmsConnectorDriver for Connector {
type State = ();
}
impl KmsPlaneDriver for Plane {
type State = ();
}
impl Buffer for GpuBuffer {
fn size(&self) -> usize {
self.size as usize
}
}
impl GraphicsAdapter for Device {
type Connector = ();
type Crtc = Crtc;
type Plane = Plane;
type Connector = Connector;
type Crtc = ();
type Buffer = GpuBuffer;
type Buffer = DumbFb;
type Framebuffer = ();
fn name(&self) -> &'static [u8] {
@@ -57,18 +41,20 @@ impl GraphicsAdapter for Device {
}
fn init(&mut self, objects: &mut KmsObjects<Self>) {
self.init_inner(objects);
}
self.init_inner();
fn get_unique(&self) -> String {
self.unique.clone()
// FIXME enumerate actual connectors
for (framebuffer_id, _) in self.framebuffers.iter().enumerate() {
let crtc = objects.add_crtc((), ());
objects.add_connector(Connector { framebuffer_id }, (), &[crtc]);
}
}
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
match cap {
DRM_CAP_DUMB_BUFFER => Ok(1),
DRM_CAP_DUMB_PREFERRED_DEPTH => Ok(24),
DRM_CAP_DUMB_PREFER_SHADOW => Ok(1),
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
_ => Err(syscall::Error::new(EINVAL)),
}
}
@@ -83,16 +69,18 @@ impl GraphicsAdapter for Device {
fn probe_connector(&mut self, objects: &mut KmsObjects<Self>, id: KmsObjectId) {
let mut connector = objects.get_connector(id).unwrap().lock().unwrap();
let framebuffer = &self.framebuffers[connector.driver_data.framebuffer_id];
connector.connection = KmsConnectorStatus::Connected;
connector.update_from_size(framebuffer.width as u32, framebuffer.height as u32);
// FIXME fetch EDID
}
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
GpuBuffer::alloc_dumb(&self.gm, &mut self.ggtt, width, height).unwrap()
(DumbFb::new(width as usize, height as usize), width * 4)
}
fn map_dumb_buffer(&mut self, buffer: &Self::Buffer) -> *mut u8 {
buffer.virt
fn map_dumb_buffer(&mut self, framebuffer: &Self::Buffer) -> *mut u8 {
framebuffer.ptr.as_ptr().cast::<u8>()
}
fn create_framebuffer(&mut self, _buffer: &Self::Buffer) -> Self::Framebuffer {
@@ -101,38 +89,133 @@ impl GraphicsAdapter for Device {
fn set_crtc(
&mut self,
_objects: &KmsObjects<Self>,
objects: &KmsObjects<Self>,
crtc: &Mutex<KmsCrtc<Self>>,
state: KmsCrtcState<Self>,
damage: Damage,
) -> syscall::Result<()> {
let mut crtc = crtc.lock().unwrap();
crtc.state = state;
Ok(())
}
fn set_plane(
&mut self,
objects: &KmsObjects<Self>,
plane: &Mutex<KmsPlane<Self>>,
new_plane_state: KmsPlaneState<Self>,
_damage: Damage,
) -> syscall::Result<()> {
let mut plane = plane.lock().unwrap();
let buffer = new_plane_state
let buffer = state
.fb_id
.map(|fb_id| objects.get_framebuffer_maybe_closed(fb_id))
.map(|fb_id| objects.get_framebuffer(fb_id))
.transpose()?;
crtc.state = state;
plane.state = new_plane_state;
for connector in objects.connectors() {
let connector = connector.lock().unwrap();
if let Some(plane_hw) = self.pipes[plane.driver_data.pipe_idx]
.planes
.get_mut(plane.driver_data.plane_idx)
{
plane_hw.set_framebuffer(buffer);
if connector.state.crtc_id != objects.crtc_ids()[crtc.crtc_index as usize] {
continue;
}
let framebuffer_id = connector.driver_data.framebuffer_id;
let framebuffer = &mut self.framebuffers[framebuffer_id];
if let Some(buffer) = buffer {
buffer.buffer.sync(framebuffer, damage)
} else {
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
for row in 0..framebuffer.height {
unsafe {
ptr::write_bytes(
onscreen_ptr.add((row * framebuffer.stride) as usize),
0,
framebuffer.width as usize,
);
}
}
}
}
Ok(())
}
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
// Intel hardware cursor is 64x64 ARGB8888 on all Gen9+ platforms.
// The cursor surface is 256 pixels wide (4 bytes per pixel) × 64 rows.
Some((64, 64))
}
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Buffer>, _dirty_fb: bool) {
let Some(pipe) = self.pipes.first_mut() else {
return;
};
let Some(ref mut cursor_plane) = pipe.cursor else {
return;
};
if cursor.buffer.is_some() {
cursor_plane.set_enabled(true);
cursor_plane.set_position(cursor.x as i16, cursor.y as i16);
} else {
cursor_plane.set_enabled(false);
}
}
}
#[derive(Debug)]
pub struct DumbFb {
width: usize,
height: usize,
ptr: NonNull<[u32]>,
}
impl DumbFb {
fn new(width: usize, height: usize) -> DumbFb {
let len = width * height;
let layout = Self::layout(len);
let ptr = unsafe { alloc::alloc_zeroed(layout) };
let ptr = ptr::slice_from_raw_parts_mut(ptr.cast(), len);
let ptr = NonNull::new(ptr).unwrap_or_else(|| alloc::handle_alloc_error(layout));
DumbFb { width, height, ptr }
}
#[inline]
fn layout(len: usize) -> Layout {
// optimizes to an integer mul
Layout::array::<u32>(len)
.unwrap()
.align_to(PAGE_SIZE)
.unwrap()
}
}
impl Drop for DumbFb {
fn drop(&mut self) {
let layout = Self::layout(self.ptr.len());
unsafe { alloc::dealloc(self.ptr.as_ptr().cast(), layout) };
}
}
impl Buffer for DumbFb {
fn size(&self) -> usize {
self.width * self.height * 4
}
}
impl DumbFb {
fn sync(&self, framebuffer: &mut DeviceFb, sync_rect: Damage) {
let sync_rect = sync_rect.clip(
self.width.try_into().unwrap(),
self.height.try_into().unwrap(),
);
let start_x: usize = sync_rect.x.try_into().unwrap();
let start_y: usize = sync_rect.y.try_into().unwrap();
let w: usize = sync_rect.width.try_into().unwrap();
let h: usize = sync_rect.height.try_into().unwrap();
let offscreen_ptr = self.ptr.as_ptr() as *mut u32;
let onscreen_ptr = framebuffer.buffer.virt.cast::<u32>();
for row in start_y..start_y + h {
unsafe {
ptr::copy(
offscreen_ptr.add(row * self.width + start_x),
onscreen_ptr.add(row * framebuffer.stride as usize / 4 + start_x),
w,
);
}
}
}
}
+1 -2
View File
@@ -89,8 +89,7 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
if scheme.adapter_mut().handle_irq() {
irq_file.irq_handle().write(&mut irq).unwrap();
let (adapter, objects) = scheme.adapter_and_kms_objects_mut();
adapter.handle_events(objects);
scheme.adapter_mut().handle_events();
scheme.tick().unwrap();
}
}
+19 -35
View File
@@ -4,13 +4,10 @@ use std::ptr::{self, NonNull};
use std::sync::Mutex;
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
use driver_graphics::kms::objects::{
KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects, KmsPlane, KmsPlaneState,
};
use driver_graphics::{Buffer, Damage, GraphicsAdapter};
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
use driver_graphics::{Buffer, CursorPlane, Damage, GraphicsAdapter};
use drm_sys::{
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFERRED_DEPTH, DRM_CAP_DUMB_PREFER_SHADOW,
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
};
use syscall::{EINVAL, PAGE_SIZE};
@@ -33,7 +30,6 @@ impl KmsConnectorDriver for Connector {
impl GraphicsAdapter for FbAdapter {
type Connector = Connector;
type Crtc = ();
type Plane = ();
type Buffer = GraphicScreen;
type Framebuffer = ();
@@ -48,7 +44,7 @@ impl GraphicsAdapter for FbAdapter {
fn init(&mut self, objects: &mut KmsObjects<Self>) {
for (framebuffer_id, framebuffer) in self.framebuffers.iter().enumerate() {
let (crtc, _primary_plane_id) = objects.add_crtc((), (), (), ());
let crtc = objects.add_crtc((), ());
objects.add_connector(
Connector {
@@ -62,14 +58,9 @@ impl GraphicsAdapter for FbAdapter {
}
}
fn get_unique(&self) -> String {
format!("vesad:{:x}", self.framebuffers[0].phys)
}
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
match cap {
DRM_CAP_DUMB_BUFFER => Ok(1),
DRM_CAP_DUMB_PREFERRED_DEPTH => Ok(24),
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
_ => Err(syscall::Error::new(EINVAL)),
}
@@ -106,34 +97,17 @@ impl GraphicsAdapter for FbAdapter {
fn set_crtc(
&mut self,
_objects: &KmsObjects<Self>,
objects: &KmsObjects<Self>,
crtc: &Mutex<KmsCrtc<Self>>,
state: KmsCrtcState<Self>,
) -> syscall::Result<()> {
let mut crtc = crtc.lock().unwrap();
crtc.state = state;
Ok(())
}
fn set_plane(
&mut self,
objects: &KmsObjects<Self>,
plane: &Mutex<KmsPlane<Self>>,
new_plane_state: KmsPlaneState<Self>,
damage: Damage,
) -> syscall::Result<()> {
let Some(crtc_id) = new_plane_state.crtc_id else {
return Ok(());
};
let crtc = objects.get_crtc(crtc_id).unwrap().lock().unwrap();
let mut plane = plane.lock().unwrap();
let buffer = new_plane_state
let mut crtc = crtc.lock().unwrap();
let buffer = state
.fb_id
.map(|fb_id| objects.get_framebuffer_maybe_closed(fb_id))
.map(|fb_id| objects.get_framebuffer(fb_id))
.transpose()?;
plane.state = new_plane_state;
crtc.state = state;
for connector in objects.connectors() {
let connector = connector.lock().unwrap();
@@ -163,6 +137,16 @@ impl GraphicsAdapter for FbAdapter {
Ok(())
}
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
None
}
fn handle_cursor(&mut self, _cursor: &CursorPlane<Self::Buffer>, _dirty_fb: bool) {
// VESA BIOS does not provide hardware cursor support.
// Software cursor rendering is handled by the console layer.
// This is a no-op — not an error condition.
}
}
#[derive(Debug)]
+10 -6
View File
@@ -32,7 +32,7 @@ use virtio_core::MSIX_PRIMARY_VECTOR;
mod scheme;
//const VIRTIO_GPU_F_VIRGL: u32 = 0;
const VIRTIO_GPU_F_VIRGL: u32 = 0;
const VIRTIO_GPU_F_EDID: u32 = 1;
//const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2;
//const VIRTIO_GPU_F_RESOURCE_BLOB: u32 = 3;
@@ -481,12 +481,12 @@ fn main() {
pcid_interface::pci_daemon(daemon_runner);
}
fn daemon_runner(redox_daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
daemon(redox_daemon, pcid_handle).unwrap();
fn daemon_runner(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
deamon(daemon, pcid_handle).unwrap();
unreachable!();
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> {
fn deamon(deamon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow::Result<()> {
common::setup_logging(
"graphics",
"pci",
@@ -511,6 +511,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
if has_edid {
device.transport.ack_driver_feature(VIRTIO_GPU_F_EDID);
}
let has_virgl = device.transport.check_device_feature(VIRTIO_GPU_F_VIRGL);
if has_virgl {
device.transport.ack_driver_feature(VIRTIO_GPU_F_VIRGL);
log::info!("virtio-gpud: VirGL 3D acceleration enabled");
}
device.transport.finalize_features();
// Queue for sending control commands.
@@ -534,14 +539,13 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> anyhow:
EventQueue::new().expect("virtio-gpud: failed to create event queue");
let mut scheme = scheme::GpuScheme::new(
format!("pci:{}", pcid_handle.config().func.addr),
config,
control_queue.clone(),
cursor_queue.clone(),
device.transport.clone(),
has_edid,
)?;
daemon.ready();
deamon.ready();
user_data! {
enum Source {
+124 -196
View File
@@ -3,14 +3,11 @@ use std::sync::{Arc, Mutex};
use common::{dma::Dma, sgl};
use driver_graphics::kms::connector::{KmsConnectorDriver, KmsConnectorStatus};
use driver_graphics::kms::objects::{
KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects, KmsPlane, KmsPlaneDriver, KmsPlaneState,
KmsPlaneType,
};
use driver_graphics::{Buffer as DrmBuffer, Damage, GraphicsAdapter, GraphicsScheme};
use driver_graphics::kms::objects::{KmsCrtc, KmsCrtcState, KmsObjectId, KmsObjects};
use driver_graphics::{Buffer as DrmBuffer, CursorPlane, Damage, GraphicsAdapter, GraphicsScheme};
use drm_sys::{
DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFERRED_DEPTH,
DRM_CAP_DUMB_PREFER_SHADOW, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
DRM_CAP_CURSOR_HEIGHT, DRM_CAP_CURSOR_WIDTH, DRM_CAP_DUMB_BUFFER, DRM_CAP_DUMB_PREFER_SHADOW,
DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT,
};
use syscall::{EINVAL, PAGE_SIZE};
@@ -40,15 +37,6 @@ impl KmsConnectorDriver for VirtGpuConnector {
type State = ();
}
#[derive(Debug)]
pub struct VirtGpuPlane {
is_cursor: bool,
}
impl KmsPlaneDriver for VirtGpuPlane {
type State = ();
}
pub struct VirtGpuFramebuffer<'a> {
queue: Arc<Queue<'a>>,
id: ResourceId,
@@ -100,7 +88,6 @@ pub struct Display {
}
pub struct VirtGpuAdapter<'a> {
unique: String,
pub config: &'a mut GpuConfig,
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
@@ -118,7 +105,7 @@ impl<'a> fmt::Debug for VirtGpuAdapter<'a> {
}
}
impl<'a> VirtGpuAdapter<'a> {
impl VirtGpuAdapter<'_> {
pub async fn update_displays(&mut self) -> Result<(), Error> {
let display_info = self.get_display_info().await?;
let raw_displays = &display_info.display_info[..self.config.num_scanouts() as usize];
@@ -185,12 +172,6 @@ impl<'a> VirtGpuAdapter<'a> {
Ok(header)
}
async fn send_request_cursor<T>(&self, request: Dma<T>) -> Result<(), Error> {
let command = ChainBuilder::new().chain(Buffer::new(&request)).build();
self.cursor_queue.send(command).await;
Ok(())
}
async fn get_display_info(&self) -> Result<Dma<GetDisplayInfo>, Error> {
let header = Dma::new(ControlHeader::with_ty(CommandTy::GetDisplayInfo))?;
@@ -221,120 +202,52 @@ impl<'a> VirtGpuAdapter<'a> {
Ok(response)
}
async fn resource_create_2d(&mut self, width: u32, height: u32) -> Result<ResourceId, Error> {
let res_id = ResourceId::alloc();
let request = Dma::new(ResourceCreate2d::new(
res_id,
ResourceFormat::Bgrx,
width,
height,
))?;
let header = self.send_request(request).await?;
assert_eq!(header.ty, CommandTy::RespOkNodata);
Ok(res_id)
}
async fn resource_attach_backing(
fn update_cursor(
&mut self,
res_id: ResourceId,
sgl: &sgl::Sgl,
) -> Result<(), Error> {
let mut mem_entries = unsafe { Dma::zeroed_slice(sgl.chunks().len())?.assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) {
*entry = MemEntry {
address: chunk.phys as u64,
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
padding: 0,
};
}
let attach_request = Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32))?;
let header = Dma::new(ControlHeader::default())?;
let command = ChainBuilder::new()
.chain(Buffer::new(&attach_request))
.chain(Buffer::new_unsized(&mem_entries))
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.control_queue.send(command).await;
assert_eq!(header.ty, CommandTy::RespOkNodata);
Ok(())
}
async fn create_dumb_buffer_inner(
&mut self,
width: u32,
height: u32,
) -> Result<(VirtGpuFramebuffer<'a>, u32), Error> {
let bpp = 32;
let fb_size = width as usize * height as usize * bpp / 8;
let sgl = sgl::Sgl::new(fb_size)?;
unsafe {
core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size);
}
// Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`.
let res_id = self.resource_create_2d(width, height).await?;
// Use the allocated framebuffer from the guest ram, and attach it as backing
// storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`.
self.resource_attach_backing(res_id, &sgl).await?;
Ok((
VirtGpuFramebuffer {
queue: self.control_queue.clone(),
id: res_id,
sgl,
width,
height,
},
width * 4,
))
}
async fn update_cursor(
&mut self,
cursor: &VirtGpuFramebuffer<'_>,
cursor: &VirtGpuFramebuffer,
x: i32,
y: i32,
hot_x: i32,
hot_y: i32,
) {
//Transfering cursor resource to host
let transfer_request = Dma::new(XferToHost2d::new(
cursor.id,
GpuRect {
x: 0,
y: 0,
width: 64,
height: 64,
},
0,
))
.unwrap();
let header = self.send_request_fenced(transfer_request).await.unwrap();
assert_eq!(header.ty, CommandTy::RespOkNodata);
futures::executor::block_on(async {
let transfer_request = Dma::new(XferToHost2d::new(
cursor.id,
GpuRect {
x: 0,
y: 0,
width: 64,
height: 64,
},
0,
))
.unwrap();
let header = self.send_request_fenced(transfer_request).await.unwrap();
assert_eq!(header.ty, CommandTy::RespOkNodata);
});
//Update the cursor position
self.send_request_cursor(
Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap(),
)
.await
.unwrap();
let request = Dma::new(UpdateCursor::update_cursor(x, y, hot_x, hot_y, cursor.id)).unwrap();
futures::executor::block_on(async {
let command = ChainBuilder::new().chain(Buffer::new(&request)).build();
self.cursor_queue.send(command).await;
});
}
async fn move_cursor(&mut self, x: i32, y: i32) {
self.send_request_cursor(Dma::new(MoveCursor::move_cursor(x, y)).unwrap())
.await
.unwrap();
fn move_cursor(&mut self, x: i32, y: i32) {
let request = Dma::new(MoveCursor::move_cursor(x, y)).unwrap();
futures::executor::block_on(async {
let command = ChainBuilder::new().chain(Buffer::new(&request)).build();
self.cursor_queue.send(command).await;
});
}
async fn disable_cursor(&mut self) {
fn disable_cursor(&mut self) {
if self.hidden_cursor.is_none() {
let (width, height) = (64, 64);
let (cursor, stride) = self.create_dumb_buffer_inner(width, height).await.unwrap();
let (width, height) = self.hw_cursor_size().unwrap();
let (cursor, stride) = self.create_dumb_buffer(width, height);
unsafe {
core::ptr::write_bytes(
cursor.sgl.as_ptr() as *mut u8,
@@ -346,14 +259,13 @@ impl<'a> VirtGpuAdapter<'a> {
}
let hidden_cursor = self.hidden_cursor.as_ref().unwrap().clone();
self.update_cursor(&hidden_cursor, 0, 0, 0, 0).await;
self.update_cursor(&hidden_cursor, 0, 0, 0, 0);
}
}
impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
type Connector = VirtGpuConnector;
type Crtc = ();
type Plane = VirtGpuPlane;
type Buffer = VirtGpuFramebuffer<'a>;
type Framebuffer = ();
@@ -372,31 +284,15 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
});
for display_id in 0..self.config.num_scanouts.get() {
let (crtc, _primary_plane_id) =
objects.add_crtc((), (), VirtGpuPlane { is_cursor: false }, ());
let cursor_plane = objects.add_plane(
&[crtc],
KmsPlaneType::Cursor,
true,
VirtGpuPlane { is_cursor: true },
(),
);
objects.get_crtc(crtc).unwrap().lock().unwrap().cursor_plane = Some(cursor_plane);
let crtc = objects.add_crtc((), ());
objects.add_connector(VirtGpuConnector { display_id }, (), &[crtc]);
}
}
fn get_unique(&self) -> String {
self.unique.clone()
}
fn get_cap(&self, cap: u32) -> syscall::Result<u64> {
match cap {
DRM_CAP_DUMB_BUFFER => Ok(1),
DRM_CAP_DUMB_PREFERRED_DEPTH => Ok(24),
DRM_CAP_DUMB_PREFER_SHADOW => Ok(0),
DRM_CAP_CURSOR_WIDTH => Ok(64),
DRM_CAP_CURSOR_HEIGHT => Ok(64),
@@ -424,8 +320,12 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
};
if self.has_edid {
connector.update_from_edid(&display.edid);
drop(connector);
objects.set_connector_edid(id, display.edid.clone());
let blob = objects.add_blob(display.edid.clone());
objects.get_connector(id).unwrap().lock().unwrap().edid = blob;
} else {
connector.update_from_size(display.width, display.height);
}
@@ -434,7 +334,63 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
fn create_dumb_buffer(&mut self, width: u32, height: u32) -> (Self::Buffer, u32) {
futures::executor::block_on(async {
self.create_dumb_buffer_inner(width, height).await.unwrap()
let bpp = 32;
let fb_size = width as usize * height as usize * bpp / 8;
let sgl = sgl::Sgl::new(fb_size).unwrap();
unsafe {
core::ptr::write_bytes(sgl.as_ptr() as *mut u8, 255, fb_size);
}
let res_id = ResourceId::alloc();
// Create a host resource using `VIRTIO_GPU_CMD_RESOURCE_CREATE_2D`.
let request = Dma::new(ResourceCreate2d::new(
res_id,
ResourceFormat::Bgrx,
width,
height,
))
.unwrap();
let header = self.send_request(request).await.unwrap();
assert_eq!(header.ty, CommandTy::RespOkNodata);
// Use the allocated framebuffer from the guest ram, and attach it as backing
// storage to the resource just created, using `VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING`.
let mut mem_entries =
unsafe { Dma::zeroed_slice(sgl.chunks().len()).unwrap().assume_init() };
for (entry, chunk) in mem_entries.iter_mut().zip(sgl.chunks().iter()) {
*entry = MemEntry {
address: chunk.phys as u64,
length: chunk.length.next_multiple_of(PAGE_SIZE) as u32,
padding: 0,
};
}
let attach_request =
Dma::new(AttachBacking::new(res_id, mem_entries.len() as u32)).unwrap();
let header = Dma::new(ControlHeader::default()).unwrap();
let command = ChainBuilder::new()
.chain(Buffer::new(&attach_request))
.chain(Buffer::new_unsized(&mem_entries))
.chain(Buffer::new(&header).flags(DescriptorFlags::WRITE_ONLY))
.build();
self.control_queue.send(command).await;
assert_eq!(header.ty, CommandTy::RespOkNodata);
(
VirtGpuFramebuffer {
queue: self.control_queue.clone(),
id: res_id,
sgl,
width,
height,
},
width * 4,
)
})
}
@@ -448,62 +404,18 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
fn set_crtc(
&mut self,
_objects: &KmsObjects<Self>,
objects: &KmsObjects<Self>,
crtc: &Mutex<KmsCrtc<Self>>,
state: KmsCrtcState<Self>,
) -> syscall::Result<()> {
let mut crtc = crtc.lock().unwrap();
crtc.state = state;
Ok(())
}
fn set_plane(
&mut self,
objects: &KmsObjects<Self>,
plane: &Mutex<KmsPlane<Self>>,
new_plane_state: KmsPlaneState<Self>,
damage: Damage,
) -> syscall::Result<()> {
futures::executor::block_on(async {
let mut plane = plane.lock().unwrap();
let framebuffer = new_plane_state
let mut crtc = crtc.lock().unwrap();
let framebuffer = state
.fb_id
.map(|fb_id| objects.get_framebuffer_maybe_closed(fb_id))
.map(|fb_id| objects.get_framebuffer(fb_id))
.transpose()?;
if plane.driver_data.is_cursor {
if let Some(framebuffer) = framebuffer {
if damage.width != 0 || damage.height != 0 {
self.update_cursor(
&framebuffer.buffer,
new_plane_state.crtc_rect.x,
new_plane_state.crtc_rect.y,
new_plane_state.hotspot.unwrap().0,
new_plane_state.hotspot.unwrap().1,
)
.await;
} else {
self.move_cursor(new_plane_state.crtc_rect.x, new_plane_state.crtc_rect.y)
.await;
}
} else {
if plane.state.fb_id.is_some() {
self.disable_cursor().await;
}
}
plane.state = new_plane_state;
return Ok(());
}
let Some(crtc_id) = new_plane_state.crtc_id else {
return Ok(());
};
let crtc = objects.get_crtc(crtc_id).unwrap().lock().unwrap();
plane.state = new_plane_state;
crtc.state = state;
for connector in objects.connectors() {
let connector = connector.lock().unwrap();
@@ -567,13 +479,30 @@ impl<'a> GraphicsAdapter for VirtGpuAdapter<'a> {
Ok(())
})
}
fn hw_cursor_size(&self) -> Option<(u32, u32)> {
Some((64, 64))
}
fn handle_cursor(&mut self, cursor: &CursorPlane<Self::Buffer>, dirty_fb: bool) {
if let Some(buffer) = &cursor.buffer {
if dirty_fb {
self.update_cursor(buffer, cursor.x, cursor.y, cursor.hot_x, cursor.hot_y);
} else {
self.move_cursor(cursor.x, cursor.y);
}
} else {
if dirty_fb {
self.disable_cursor();
}
}
}
}
pub struct GpuScheme {}
impl<'a> GpuScheme {
pub fn new(
unique: String,
config: &'a mut GpuConfig,
control_queue: Arc<Queue<'a>>,
cursor_queue: Arc<Queue<'a>>,
@@ -581,7 +510,6 @@ impl<'a> GpuScheme {
has_edid: bool,
) -> Result<GraphicsScheme<VirtGpuAdapter<'a>>, Error> {
let adapter = VirtGpuAdapter {
unique,
config,
control_queue,
cursor_queue,
+1 -1
View File
@@ -8,8 +8,8 @@ edition = "2018"
fdt.workspace = true
log.workspace = true
ron.workspace = true
redox_syscall.workspace = true
libredox = { workspace = true, default-features = false, features = ["std", "call"] }
redox_syscall.workspace = true
amlserde = { path = "../amlserde" }
common = { path = "../common" }
+80 -12
View File
@@ -1,7 +1,6 @@
use amlserde::{AmlSerde, AmlSerdeValue};
use std::{error::Error, fs, process::Command};
use libredox::Fd;
use std::{error::Error, fs, process::Command};
use syscall::flag::{AcpiVerb, CallFlags};
use super::Backend;
@@ -13,20 +12,13 @@ pub struct AcpiBackend {
impl Backend for AcpiBackend {
fn new() -> Result<Self, Box<dyn Error>> {
let kernel_handle = Fd::open("/scheme/kernel.acpi", libredox::flag::O_CLOEXEC, 0)?;
let len =
kernel_handle.call_ro(&mut [], CallFlags::empty(), &[AcpiVerb::ReadRxsdt as u64])?;
let len = kernel_handle.call_ro(&mut [], CallFlags::empty(), &[AcpiVerb::ReadRxsdt as u64])?;
let mut rxsdt = vec![0_u8; len];
kernel_handle.call_ro(
&mut rxsdt,
CallFlags::empty(),
&[AcpiVerb::ReadRxsdt as u64],
)?;
kernel_handle.call_ro(&mut rxsdt, CallFlags::empty(), &[AcpiVerb::ReadRxsdt as u64])?;
// Spawn acpid
// TODO: rather than put it in the namespace, have init pass the acpi handle to hwd, and
// then pass it to acpid?
#[allow(deprecated, reason = "we can't yet move this to init")]
daemon::Daemon::spawn(Command::new("acpid"));
@@ -101,7 +93,6 @@ impl Backend for AcpiBackend {
"PNP0A03" | "PNP0A08" => "PCI bus",
"PNP0A05" => "Generic ACPI bus",
"PNP0A06" => "Generic ACPI Extended-IO bus (EIO bus)",
"PNP0B00" => "AT real-time clock",
"PNP0C01" => "System board",
"PNP0C02" => "Reserved resources",
"PNP0C04" => "Math coprocessor",
@@ -120,6 +111,83 @@ impl Backend for AcpiBackend {
}
}
}
// PCI interrupt link objects: LNK A, B, C, D and the system-level _PIC.
// Linux's acpi_irq.c walks these during boot to resolve PIRQ routing;
// we surface them as info-level logs so the same diagnostic is
// available without full _PRT evaluation. Format mirrors the
// Linux dmesg: "\_SB_.LNKC: Enabled at IRQ 10".
if let Ok(link_entries) = fs::read_dir("/scheme/acpi/symbols") {
for link_entry in link_entries.flatten() {
let Some(name) = link_entry.file_name().to_str().map(|s| s.to_owned()) else {
continue;
};
if !name.contains(".LNK") {
continue;
}
let Ok(link_fd) = symbols_fd.openat(&name, libredox::flag::O_RDONLY, 0) else {
continue;
};
let Ok(stat) = link_fd.stat() else { continue };
let mut buf = vec![0u8; stat.st_size as usize];
let Ok(n) = link_fd.read(&mut buf) else { continue };
buf.truncate(n);
let Ok(s) = std::str::from_utf8(&buf) else { continue };
let Ok(parsed) = ron::from_str::<AmlSerde>(s) else { continue };
let (irq, enabled) = match parsed.value {
AmlSerdeValue::OpRegion { .. } | AmlSerdeValue::Field { .. }
| AmlSerdeValue::Buffer { .. } | AmlSerdeValue::Package { .. } => continue,
_ => parse_lnk_irc(&parsed),
};
let state = if enabled { "Enabled" } else { "Disabled" };
let irq_s = irq
.map(|i| i.to_string())
.unwrap_or_else(|| "?".to_owned());
log::info!("{}: {} at IRQ {}", parsed.name, state, irq_s);
}
}
Ok(())
}
}
// Parse a LNK object's serialized AML value into (IRQ, enabled).
// The LNK object's value here is the result of evaluating its _CRS,
// which on QEMU/PIIX4 returns a serialized resource descriptor that
// resolves to the current IRQ assignment. We accept the integer IRQ
// value directly when the serializer hands us one, but only when the
// value is in the valid IRQ range (0..=255 for PIC, 0..=2047 for
// legacy-compatible IOAPIC) so we don't pollute the routing table
// with garbage values like 0xD041 misparsed from FieldUnit accessors.
fn parse_lnk_irc(aml: &amlserde::AmlSerde) -> (Option<u32>, bool) {
match &aml.value {
AmlSerdeValue::Integer(n) => {
let irq = *n & 0xFFFF;
if irq <= 2047 {
(Some(irq as u32), true)
} else {
log::debug!(
"{}: AML integer IRQ value {} out of valid range, skipping",
aml.name,
irq
);
(None, false)
}
}
AmlSerdeValue::Buffer(bytes) => {
// The AML resource-descriptor "IRQ" small resource type is
// 0x21 (type 0x04, big-endian 16-bit IRQ bitmask). Two-byte
// header + variable-length bitmask; we extract a single IRQ
// bit when possible.
if bytes.len() >= 4 && bytes[0] == 0x21 {
let lo = bytes[2] as u16 | ((bytes[3] as u16) << 8);
if lo != 0 {
let bit = lo.trailing_zeros();
if bit <= 15 {
return (Some(bit as u32), true);
}
}
}
(None, true)
}
_ => (None, false),
}
}
+19 -1
View File
@@ -1,4 +1,5 @@
use std::error::Error;
use std::{fs, process};
use super::Backend;
@@ -10,7 +11,24 @@ impl Backend for LegacyBackend {
}
fn probe(&mut self) -> Result<(), Box<dyn Error>> {
log::info!("TODO: handle driver spawning from legacy backend");
// Legacy backend: non-ACPI systems (BIOS, coreboot, etc.).
// Enumerate available schemes and spawn essential drivers.
// Cross-referenced with Linux 7.1 drivers/pci/probe.c pci_scan_child_bus()
// and arch/x86/kernel/devicetree.c for non-ACPI device enumeration.
// Step 1: enumerate /scheme/ for known device types
if let Ok(entries) = fs::read_dir("/scheme") {
for entry in entries.flatten() {
if let Ok(name) = entry.file_name().into_string() {
log::info!("legacy: found scheme {}", name);
}
}
}
// Step 2: spawn pcid for PCI bus enumeration
process::Command::new("pcid").spawn().ok();
log::info!("legacy: spawned pcid for PCI bus enumeration");
Ok(())
}
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "amd-mp2-i2cd"
description = "AMD MP2 PCI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
pcid = { path = "../../pcid" }
[lints]
workspace = true
+106
View File
@@ -0,0 +1,106 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::process;
use anyhow::{Context, Result};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use pcid_interface::PciFunctionHandle;
const MP2_MAILBOX_STATUS: usize = 0x00;
const MP2_MAILBOX_COMMAND: usize = 0x04;
const MP2_MAILBOX_ARGUMENT0: usize = 0x08;
const MP2_MAILBOX_ARGUMENT1: usize = 0x0C;
fn main() {
pcid_interface::pci_daemon(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
if let Err(err) = daemon_main(daemon, &mut pcid_handle) {
log::error!("amd-mp2-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
let pci_config = pcid_handle.config();
let log_name = format!("{}_amd-mp2-i2c", pci_config.func.name());
common::setup_logging(
"bus",
"i2c",
&log_name,
common::output_level(),
common::file_level(),
);
let mapped_bar = unsafe { pcid_handle.map_bar(0) };
let bar_addr = mapped_bar.ptr.as_ptr() as usize;
let bar_size = mapped_bar.bar_size;
log::info!(
"amd-mp2-i2cd: {} BAR0={:#x}+{:#x} mapped={:p}+{:#x}",
pci_config.func.display(),
bar_addr,
bar_size,
mapped_bar.ptr.as_ptr(),
mapped_bar.bar_size,
);
log::debug!(
"amd-mp2-i2cd: MP2 mailbox regs status={MP2_MAILBOX_STATUS:#x} cmd={MP2_MAILBOX_COMMAND:#x} arg0={MP2_MAILBOX_ARGUMENT0:#x} arg1={MP2_MAILBOX_ARGUMENT1:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("amd-mp2:{}", pci_config.func.name()),
max_transaction_size: 0,
supports_10bit_addr: false,
};
let mut registration = register_adapter(&info)
.context("failed to register AMD MP2 controller with i2cd")?;
let response = read_registration_response(&mut registration)
.context("failed to read AMD MP2 i2cd registration response")?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("amd-mp2-i2cd: controller registered with i2cd as adapter {id}");
}
other => anyhow::bail!("unexpected i2cd registration response: {other:?}"),
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
let _keep_registration = registration;
let _keep_pcid = pcid_handle;
loop {
std::thread::park();
}
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode AMD MP2 I2C registration payload")?;
file.write_all(payload.as_bytes())
.context("failed to send AMD MP2 I2C registration payload")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read AMD MP2 I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer)
.context("AMD MP2 I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode AMD MP2 I2C registration response")
}
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "dw-acpi-i2cd"
description = "Generic DesignWare ACPI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
+361
View File
@@ -0,0 +1,361 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use serde::Deserialize;
const SUPPORTED_IDS: &[&str] = &["80860F41", "808622C1", "AMDI0010", "AMDI0019", "AMDI0510"];
const DW_IC_CON: usize = 0x00;
const DW_IC_TAR: usize = 0x04;
const DW_IC_SS_SCL_HCNT: usize = 0x14;
const DW_IC_SS_SCL_LCNT: usize = 0x18;
const DW_IC_DATA_CMD: usize = 0x10;
const DW_IC_INTR_MASK: usize = 0x30;
const DW_IC_CLR_INTR: usize = 0x40;
const DW_IC_ENABLE: usize = 0x6C;
const DW_IC_STATUS: usize = 0x70;
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
irq: Option<u32>,
serial_bus: Option<I2cSerialBusDescriptor>,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"dw-acpi-i2cd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("dw-acpi-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers = discover_controllers(SUPPORTED_IDS)
.context("failed to discover DesignWare ACPI I2C controllers")?;
if controllers.is_empty() {
log::info!("dw-acpi-i2cd: no supported ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller("dw-acpi", controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("dw-acpi-i2cd: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("dw-acpi-i2cd: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err)
if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) =>
{
log::debug!("dw-acpi-i2cd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"dw-acpi-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("dw-acpi-i2cd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut irq = None;
let mut serial_bus = None;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
irq = interrupts.first().copied().map(u32::from);
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
if irq.is_none() =>
{
irq = interrupts.first().copied();
}
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
serial_bus = Some(descriptor.clone());
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
irq,
serial_bus,
})
}
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"dw-acpi-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"dw-acpi-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
resources.mmio_base,
resources.mmio_len,
resources.irq,
);
log::debug!(
"dw-acpi-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("{prefix}:{device}"),
max_transaction_size: 0,
supports_10bit_addr: resources
.serial_bus
.as_ref()
.map(|bus| bus.access_mode_10bit)
.unwrap_or(false),
};
let mut registration = register_adapter(&info)
.with_context(|| format!("failed to register {device} with i2cd"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("dw-acpi-i2cd: adapter {device} registered with i2cd as {id}");
}
other => {
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode I2C adapter registration")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C adapter registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode I2C registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "i2c-interface"
description = "Shared I2C transfer and registry types"
version = "0.1.0"
edition = "2021"
[dependencies]
serde.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
[lints]
workspace = true
+92
View File
@@ -0,0 +1,92 @@
use serde::{Deserialize, Serialize};
pub use syscall;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cTransferOp {
Write(Vec<u8>),
Read(usize),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferSegment {
pub address: u16,
pub ten_bit_address: bool,
pub op: I2cTransferOp,
}
impl I2cTransferSegment {
pub fn write(address: u16, payload: impl Into<Vec<u8>>) -> Self {
Self {
address,
ten_bit_address: false,
op: I2cTransferOp::Write(payload.into()),
}
}
pub fn read(address: u16, len: usize) -> Self {
Self {
address,
ten_bit_address: false,
op: I2cTransferOp::Read(len),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferRequest {
pub adapter: String,
pub segments: Vec<I2cTransferSegment>,
pub stop: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cTransferResponse {
pub ok: bool,
pub read_data: Vec<Vec<u8>>,
pub error: Option<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct I2cAdapterRegistration {
pub name: String,
pub description: String,
pub acpi_companion: Option<String>,
pub slave_address_override: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct I2cAdapterInfo {
pub id: u32,
pub name: String,
pub max_transaction_size: usize,
pub supports_10bit_addr: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cTransferStatus {
Ok,
Nack,
Timeout,
Error,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cControlRequest {
RegisterAdapter { info: I2cAdapterInfo },
OpenAdapter { id: u32 },
Transfer {
adapter_id: u32,
request: I2cTransferRequest,
},
ListAdapters,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum I2cControlResponse {
AdapterRegistered { id: u32 },
AdapterOpened,
TransferResult(I2cTransferResponse),
AdapterList(Vec<I2cAdapterInfo>),
Error(String),
}
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "i2cd"
description = "I2C adapter registry scheme daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
serde.workspace = true
ron.workspace = true
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
scheme-utils = { path = "../../../scheme-utils" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
+377
View File
@@ -0,0 +1,377 @@
use std::collections::BTreeMap;
use std::process;
use anyhow::{Context, Result};
use i2c_interface::{
I2cAdapterInfo, I2cControlRequest, I2cControlResponse, I2cTransferRequest,
I2cTransferResponse,
};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, Socket};
use scheme_utils::{Blocking, HandleMap};
use syscall::schemev2::NewFdFlags;
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
enum Handle {
SchemeRoot,
Register { pending: Vec<u8> },
Provider { adapter_id: u32, pending: Vec<u8> },
Adapters { pending: Vec<u8> },
AdapterDetail { id: u32, pending: Vec<u8> },
Transfer { pending: Vec<u8> },
}
struct AdapterEntry {
info: I2cAdapterInfo,
provider_handle: usize,
}
struct I2cDaemon {
handles: HandleMap<Handle>,
adapters: BTreeMap<u32, AdapterEntry>,
next_id: u32,
}
impl I2cDaemon {
fn new() -> Self {
Self {
handles: HandleMap::new(),
adapters: BTreeMap::new(),
next_id: 0,
}
}
fn adapter_list(&self) -> Vec<I2cAdapterInfo> {
self.adapters.values().map(|entry| entry.info.clone()).collect()
}
fn serialize_response(response: &I2cControlResponse) -> syscall::Result<Vec<u8>> {
ron::ser::to_string(response)
.map(|text| text.into_bytes())
.map_err(|err| {
log::error!("i2cd: failed to serialize control response: {err}");
SysError::new(EINVAL)
})
}
fn deserialize_request(buf: &[u8]) -> syscall::Result<I2cControlRequest> {
let text = std::str::from_utf8(buf).map_err(|err| {
log::warn!("i2cd: invalid UTF-8 control payload: {err}");
SysError::new(EINVAL)
})?;
ron::from_str(text).map_err(|err| {
log::warn!("i2cd: failed to decode control request: {err}");
SysError::new(EINVAL)
})
}
fn set_pending_response(handle: &mut Handle, response: I2cControlResponse) -> syscall::Result<()> {
let pending = Self::serialize_response(&response)?;
match handle {
Handle::Register { pending: slot }
| Handle::Provider { pending: slot, .. }
| Handle::Adapters { pending: slot }
| Handle::AdapterDetail { pending: slot, .. }
| Handle::Transfer { pending: slot } => {
*slot = pending;
Ok(())
}
Handle::SchemeRoot => Err(SysError::new(EBADF)),
}
}
fn queue_adapter_list(handle: &mut Handle, adapters: Vec<I2cAdapterInfo>) -> syscall::Result<()> {
Self::set_pending_response(handle, I2cControlResponse::AdapterList(adapters))
}
fn queue_transfer_stub(
handle: &mut Handle,
adapter: &I2cAdapterInfo,
request: &I2cTransferRequest,
) -> syscall::Result<()> {
let write_bytes = request
.segments
.iter()
.filter_map(|segment| match &segment.op {
i2c_interface::I2cTransferOp::Write(bytes) => Some(bytes.len()),
i2c_interface::I2cTransferOp::Read(_) => None,
})
.sum::<usize>();
let read_segments = request
.segments
.iter()
.filter(|segment| matches!(segment.op, i2c_interface::I2cTransferOp::Read(_)))
.count();
log::info!(
"i2cd: routing transfer to adapter {} ({}) name={} segments={} write_bytes={} read_segments={} stop={} (stubbed)",
adapter.id,
adapter.name,
request.adapter,
request.segments.len(),
write_bytes,
read_segments,
request.stop,
);
Self::set_pending_response(
handle,
I2cControlResponse::TransferResult(I2cTransferResponse {
ok: false,
read_data: Vec::new(),
error: Some(String::from("I2C controller transfer path is not implemented yet")),
}),
)
}
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
let pending = match handle {
Handle::Register { pending }
| Handle::Provider { pending, .. }
| Handle::Adapters { pending }
| Handle::AdapterDetail { pending, .. }
| Handle::Transfer { pending } => pending,
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
};
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
if offset >= pending.len() {
return Ok(0);
}
let copy_len = buf.len().min(pending.len() - offset);
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
Ok(copy_len)
}
}
impl SchemeSync for I2cDaemon {
fn scheme_root(&mut self) -> syscall::Result<usize> {
Ok(self.handles.insert(Handle::SchemeRoot))
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<OpenResult> {
let handle = self.handles.get(dirfd)?;
let segments = path.trim_matches('/');
let new_handle = match handle {
Handle::SchemeRoot => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let mut parts = segments.split('/');
match parts.next() {
Some("register") if parts.next().is_none() => Handle::Register {
pending: Vec::new(),
},
Some("adapters") => match parts.next() {
None => Handle::Adapters {
pending: Vec::new(),
},
Some(id) if parts.next().is_none() => {
let id = id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?;
Handle::AdapterDetail {
id,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EINVAL)),
},
Some("transfer") if parts.next().is_none() => Handle::Transfer {
pending: Vec::new(),
},
_ => return Err(SysError::new(ENOENT)),
}
}
Handle::Adapters { .. } => {
if segments.is_empty() {
return Err(SysError::new(EINVAL));
}
let id = segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?;
Handle::AdapterDetail {
id,
pending: Vec::new(),
}
}
_ => return Err(SysError::new(EACCES)),
};
let fd = self.handles.insert(new_handle);
Ok(OpenResult::ThisScheme {
number: fd,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let adapters = self.adapter_list();
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Adapters { pending } if pending.is_empty() => {
*pending = Self::serialize_response(&I2cControlResponse::AdapterList(adapters))?;
}
Handle::AdapterDetail { id, pending } if pending.is_empty() => {
let info = self
.adapters
.get(id)
.map(|entry| entry.info.clone())
.ok_or(SysError::new(ENOENT))?;
*pending = Self::serialize_response(&I2cControlResponse::AdapterList(vec![info]))?;
}
_ => {}
}
Self::copy_pending(handle, buf, offset)
}
fn write(
&mut self,
id: usize,
buf: &[u8],
_offset: u64,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> syscall::Result<usize> {
let request = Self::deserialize_request(buf)?;
match request {
I2cControlRequest::RegisterAdapter { mut info } => {
let adapter_id = self.next_id;
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
info.id = adapter_id;
self.adapters.insert(
adapter_id,
AdapterEntry {
info: info.clone(),
provider_handle: id,
},
);
let handle = self.handles.get_mut(id)?;
*handle = Handle::Provider {
adapter_id,
pending: Self::serialize_response(&I2cControlResponse::AdapterRegistered {
id: adapter_id,
})?,
};
log::info!(
"RB_I2CD_ADAPTER_REGISTERED id={} name={} max_transaction_size={} supports_10bit_addr={}",
info.id,
info.name,
info.max_transaction_size,
info.supports_10bit_addr,
);
Ok(buf.len())
}
I2cControlRequest::ListAdapters => {
let adapters = self.adapter_list();
let handle = self.handles.get_mut(id)?;
Self::queue_adapter_list(handle, adapters)?;
Ok(buf.len())
}
I2cControlRequest::OpenAdapter { id: adapter_id } => {
if !self.adapters.contains_key(&adapter_id) {
return Err(SysError::new(ENOENT));
}
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Adapters { .. } | Handle::AdapterDetail { .. } => {
Self::set_pending_response(handle, I2cControlResponse::AdapterOpened)?;
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
I2cControlRequest::Transfer {
adapter_id,
request,
} => {
let entry = self.adapters.get(&adapter_id).ok_or(SysError::new(ENOENT))?;
log::debug!(
"i2cd: transfer requested for adapter {} via provider fd {}",
adapter_id,
entry.provider_handle,
);
let adapter_info = entry.info.clone();
let handle = self.handles.get_mut(id)?;
match handle {
Handle::Transfer { .. } => {
Self::queue_transfer_stub(handle, &adapter_info, &request)?;
Ok(buf.len())
}
_ => Err(SysError::new(EINVAL)),
}
}
}
}
fn on_close(&mut self, id: usize) {
let Some(handle) = self.handles.remove(id) else {
return;
};
if let Handle::Provider { adapter_id, .. } = handle {
self.adapters.remove(&adapter_id);
}
}
}
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
let socket = Socket::create().context("failed to create i2c scheme socket")?;
let mut scheme = I2cDaemon::new();
let handler = Blocking::new(&socket, 16);
daemon
.ready_sync_scheme(&socket, &mut scheme)
.context("failed to publish i2c scheme root")?;
log::info!("RB_I2CD_SCHEMA");
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
handler
.process_requests_blocking(scheme)
.context("failed to process i2cd requests")?;
}
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
if let Err(err) = run_daemon(daemon) {
log::error!("i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"i2cd",
common::output_level(),
common::file_level(),
);
daemon::SchemeDaemon::new(daemon_runner);
}
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "intel-lpss-i2cd"
description = "Intel LPSS ACPI I2C controller driver"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
serde.workspace = true
ron.workspace = true
acpi-resource = { path = "../../acpi-resource" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../i2c-interface" }
[lints]
workspace = true
+361
View File
@@ -0,0 +1,361 @@
use std::collections::BTreeMap;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Write};
use std::path::Path;
use std::process;
use acpi_resource::{
AddressResourceType, ExtendedIrqDescriptor, FixedMemory32Descriptor, I2cSerialBusDescriptor,
IrqDescriptor, Memory32RangeDescriptor, ResourceDescriptor,
};
use anyhow::{Context, Result};
use common::{MemoryType, PhysBorrowed, Prot};
use i2c_interface::{I2cAdapterInfo, I2cControlRequest, I2cControlResponse};
use serde::Deserialize;
const SUPPORTED_IDS: &[&str] = &["INT33C2", "INT33C3", "INT3432", "INT3433", "INTC10EF"];
const DW_IC_CON: usize = 0x00;
const DW_IC_TAR: usize = 0x04;
const DW_IC_SS_SCL_HCNT: usize = 0x14;
const DW_IC_SS_SCL_LCNT: usize = 0x18;
const DW_IC_DATA_CMD: usize = 0x10;
const DW_IC_INTR_MASK: usize = 0x30;
const DW_IC_CLR_INTR: usize = 0x40;
const DW_IC_ENABLE: usize = 0x6C;
const DW_IC_STATUS: usize = 0x70;
const DW_MMIO_WINDOW: usize = DW_IC_STATUS + core::mem::size_of::<u32>();
#[derive(Debug, Deserialize)]
struct AmlSymbol {
name: String,
value: AmlValue,
}
#[derive(Debug, Deserialize)]
enum AmlValue {
Integer(u64),
String(String),
}
#[derive(Clone, Debug)]
struct ControllerResources {
mmio_base: usize,
mmio_len: usize,
irq: Option<u32>,
serial_bus: Option<I2cSerialBusDescriptor>,
}
#[derive(Debug)]
struct ControllerDescriptor {
device: String,
hid: String,
resources: ControllerResources,
}
struct RegisteredController {
_mmio: Option<PhysBorrowed>,
_registration: File,
}
fn main() {
common::setup_logging(
"bus",
"i2c",
"intel-lpss-i2cd",
common::output_level(),
common::file_level(),
);
daemon::Daemon::new(daemon_runner);
}
fn daemon_runner(daemon: daemon::Daemon) -> ! {
if let Err(err) = daemon_main(daemon) {
log::error!("intel-lpss-i2cd: {err:#}");
process::exit(1);
}
process::exit(0);
}
fn daemon_main(daemon: daemon::Daemon) -> Result<()> {
common::init();
let controllers = discover_controllers(SUPPORTED_IDS)
.context("failed to discover Intel LPSS ACPI I2C controllers")?;
if controllers.is_empty() {
log::info!("intel-lpss-i2cd: no supported ACPI controllers found");
}
let mut registered = Vec::new();
for controller in controllers {
match register_controller("intel-lpss", controller) {
Ok(controller) => registered.push(controller),
Err(err) => log::warn!("intel-lpss-i2cd: controller registration skipped: {err:#}"),
}
}
daemon.ready();
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
log::info!("intel-lpss-i2cd: registered {} controller(s)", registered.len());
loop {
std::thread::park();
}
}
fn discover_controllers(supported_ids: &[&str]) -> Result<Vec<ControllerDescriptor>> {
let mut matched = BTreeMap::new();
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err)
if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) =>
{
log::debug!("intel-lpss-i2cd: ACPI symbols are not ready yet");
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
for entry in entries {
let entry = entry.context("failed to read ACPI symbol directory entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("_HID") && !file_name.ends_with("_CID") {
continue;
}
let Some(id) = read_symbol_id(&entry.path())? else {
continue;
};
if !supported_ids.iter().any(|candidate| *candidate == id) {
continue;
}
let device = file_name
.strip_suffix("_HID")
.or_else(|| file_name.strip_suffix("_CID"))
.map(str::to_owned);
if let Some(device) = device {
matched.entry(device).or_insert(id);
}
}
let mut controllers = Vec::new();
for (device, hid) in matched {
let resources = read_controller_resources(&device)
.with_context(|| format!("failed to read resources for {device}"))?;
controllers.push(ControllerDescriptor {
device,
hid,
resources,
});
}
Ok(controllers)
}
fn read_symbol_id(path: &Path) -> Result<Option<String>> {
let contents = fs::read_to_string(path)
.with_context(|| format!("failed to read ACPI symbol {}", path.display()))?;
let symbol = match ron::from_str::<AmlSymbol>(&contents) {
Ok(symbol) => symbol,
Err(err) => {
log::debug!(
"intel-lpss-i2cd: skipping {} because the symbol payload was not a scalar ID: {err}",
path.display(),
);
return Ok(None);
}
};
let id = match symbol.value {
AmlValue::Integer(integer) => eisa_id_from_integer(integer),
AmlValue::String(string) => string,
};
log::debug!("intel-lpss-i2cd: {} -> {id}", symbol.name);
Ok(Some(id))
}
fn read_controller_resources(device: &str) -> Result<ControllerResources> {
let contents = fs::read_to_string(format!("/scheme/acpi/resources/{device}"))
.with_context(|| format!("failed to read /scheme/acpi/resources/{device}"))?;
let resources = ron::from_str::<Vec<ResourceDescriptor>>(&contents)
.with_context(|| format!("failed to decode RON resources for {device}"))?;
let mut mmio = None;
let mut irq = None;
let mut serial_bus = None;
for resource in &resources {
match resource {
ResourceDescriptor::FixedMemory32(FixedMemory32Descriptor {
address,
address_length,
..
}) if mmio.is_none() => {
mmio = Some((*address as usize, (*address_length as usize).max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Memory32Range(Memory32RangeDescriptor {
minimum,
maximum,
address_length,
..
}) if mmio.is_none() && maximum >= minimum => {
let span = maximum.saturating_sub(*minimum).saturating_add(1) as usize;
mmio = Some((
*minimum as usize,
span.max((*address_length as usize).max(DW_MMIO_WINDOW)),
));
}
ResourceDescriptor::Address32(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
mmio = Some((
descriptor.minimum as usize,
(descriptor.address_length as usize).max(DW_MMIO_WINDOW),
));
}
ResourceDescriptor::Address64(descriptor)
if mmio.is_none()
&& matches!(descriptor.resource_type, AddressResourceType::MemoryRange) =>
{
let base = usize::try_from(descriptor.minimum)
.context("64-bit MMIO base does not fit in usize")?;
let len = usize::try_from(descriptor.address_length)
.context("64-bit MMIO length does not fit in usize")?;
mmio = Some((base, len.max(DW_MMIO_WINDOW)));
}
ResourceDescriptor::Irq(IrqDescriptor { interrupts, .. }) if irq.is_none() => {
irq = interrupts.first().copied().map(u32::from);
}
ResourceDescriptor::ExtendedIrq(ExtendedIrqDescriptor { interrupts, .. })
if irq.is_none() =>
{
irq = interrupts.first().copied();
}
ResourceDescriptor::I2cSerialBus(descriptor) if serial_bus.is_none() => {
serial_bus = Some(descriptor.clone());
}
_ => {}
}
}
let (mmio_base, mmio_len) = mmio.context("no MMIO resource was found")?;
Ok(ControllerResources {
mmio_base,
mmio_len,
irq,
serial_bus,
})
}
fn register_controller(prefix: &str, controller: ControllerDescriptor) -> Result<RegisteredController> {
let ControllerDescriptor {
device,
hid,
resources,
} = controller;
let mmio = match PhysBorrowed::map(
resources.mmio_base,
resources.mmio_len,
Prot::RW,
MemoryType::Uncacheable,
) {
Ok(mapping) => Some(mapping),
Err(err) => {
log::warn!(
"intel-lpss-i2cd: failed to map MMIO for {device} ({:#x}, len {:#x}): {err}",
resources.mmio_base,
resources.mmio_len,
);
None
}
};
log::info!(
"intel-lpss-i2cd: discovered {device} hid={hid} mmio={:#x}+{:#x} irq={:?}",
resources.mmio_base,
resources.mmio_len,
resources.irq,
);
log::debug!(
"intel-lpss-i2cd: DesignWare regs con={DW_IC_CON:#x} tar={DW_IC_TAR:#x} data_cmd={DW_IC_DATA_CMD:#x} intr_mask={DW_IC_INTR_MASK:#x} clr_intr={DW_IC_CLR_INTR:#x} enable={DW_IC_ENABLE:#x} ss_hcnt={DW_IC_SS_SCL_HCNT:#x} ss_lcnt={DW_IC_SS_SCL_LCNT:#x}",
);
let info = I2cAdapterInfo {
id: 0,
name: format!("{prefix}:{device}"),
max_transaction_size: 0,
supports_10bit_addr: resources
.serial_bus
.as_ref()
.map(|bus| bus.access_mode_10bit)
.unwrap_or(false),
};
let mut registration = register_adapter(&info)
.with_context(|| format!("failed to register {device} with i2cd"))?;
let response = read_registration_response(&mut registration)
.with_context(|| format!("failed to read i2cd registration response for {device}"))?;
match response {
I2cControlResponse::AdapterRegistered { id } => {
log::info!("intel-lpss-i2cd: adapter {device} registered with i2cd as {id}");
}
other => {
anyhow::bail!("unexpected i2cd registration response for {device}: {other:?}");
}
}
Ok(RegisteredController {
_mmio: mmio,
_registration: registration,
})
}
fn register_adapter(info: &I2cAdapterInfo) -> Result<File> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/register")
.context("failed to open /scheme/i2c/register")?;
let payload = ron::ser::to_string(&I2cControlRequest::RegisterAdapter { info: info.clone() })
.context("failed to encode I2C adapter registration")?;
file.write_all(payload.as_bytes())
.context("failed to send I2C adapter registration")?;
Ok(file)
}
fn read_registration_response(file: &mut File) -> Result<I2cControlResponse> {
let mut buffer = vec![0_u8; 4096];
let count = file
.read(&mut buffer)
.context("failed to read I2C registration response")?;
buffer.truncate(count);
let text = std::str::from_utf8(&buffer).context("I2C registration response was not UTF-8")?;
ron::from_str(text).context("failed to decode I2C registration response")
}
fn eisa_id_from_integer(integer: u64) -> String {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1F) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1F) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1F) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
format!(
"{vendor_1}{vendor_2}{vendor_3}{device_1:01X}{device_2:01X}{device_3:01X}{device_4:01X}"
)
}
+27
View File
@@ -0,0 +1,27 @@
## Storage drivers for InitFS (pcid format) ##
[[drivers]]
name = "AHCI storage"
class = 1
subclass = 6
command = ["/scheme/initfs/lib/drivers/ahcid"]
[[drivers]]
name = "IDE storage"
class = 1
subclass = 1
command = ["/scheme/initfs/lib/drivers/ided"]
[[drivers]]
name = "NVME storage"
class = 1
subclass = 8
command = ["/scheme/initfs/lib/drivers/nvmed"]
[[drivers]]
name = "virtio-blk"
class = 1
subclass = 0
vendor = 0x1AF4
device = 0x1001
command = ["/scheme/initfs/lib/drivers/virtio-blkd"]
+43
View File
@@ -0,0 +1,43 @@
## Storage drivers for InitFS (pcid-spawner format) ##
[[driver]]
name = "ahcid"
description = "AHCI SATA driver"
priority = 100
command = ["/scheme/initfs/lib/drivers/ahcid"]
[[driver.match]]
class = 1
subclass = 6
[[driver]]
name = "ided"
description = "PATA IDE driver"
priority = 100
command = ["/scheme/initfs/lib/drivers/ided"]
[[driver.match]]
class = 1
subclass = 1
[[driver]]
name = "nvmed"
description = "NVMe storage driver"
priority = 100
command = ["/scheme/initfs/lib/drivers/nvmed"]
[[driver.match]]
class = 1
subclass = 8
[[driver]]
name = "virtio-blkd"
description = "VirtIO block device driver"
priority = 100
command = ["/scheme/initfs/lib/drivers/virtio-blkd"]
[[driver.match]]
vendor = 0x1AF4
device = 0x1001
class = 1
subclass = 0
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "i2c-hidd"
description = "I2C HID client daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
orbclient.workspace = true
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
acpi-resource = { path = "../../acpi-resource" }
amlserde = { path = "../../amlserde" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
inputd = { path = "../../inputd" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
+307
View File
@@ -0,0 +1,307 @@
use acpi_resource::{GpioDescriptor, ResourceDescriptor};
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{anyhow, bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::{ErrorKind, Read};
use crate::quirks::ProbeFailureQuirk;
const I2C_HID_DSM_GUID: [u8; 16] = [
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76,
];
#[derive(Clone, Debug)]
pub struct I2cBinding {
pub adapter: String,
pub address: u16,
}
#[derive(Clone, Debug)]
pub struct AcpiDeviceResources {
pub i2c: I2cBinding,
pub irq: Option<u32>,
pub gpio_int: Vec<GpioDescriptor>,
pub gpio_io: Vec<GpioDescriptor>,
}
pub fn scan_acpi_i2c_hid_devices() -> Result<Vec<String>> {
let entries = match fs::read_dir("/scheme/acpi/symbols") {
Ok(entries) => entries,
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
return Ok(Vec::new());
}
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
};
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
continue;
}
let symbol = read_aml_symbol(&file_name)
.with_context(|| format!("failed to read ACPI symbol {file_name}"))?;
let Some(id) = decode_hardware_id(&symbol.value) else {
continue;
};
if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") {
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".");
if !device.is_empty() {
devices.insert(device);
}
}
}
Ok(devices.into_iter().collect())
}
pub fn read_decoded_resources(path: &str) -> Result<AcpiDeviceResources> {
let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path));
let serialized = fs::read_to_string(&resource_path)
.with_context(|| format!("failed to read {resource_path}"))?;
let descriptors: Vec<ResourceDescriptor> = ron::from_str(&serialized)
.with_context(|| format!("invalid ACPI resources in {resource_path}"))?;
let mut i2c = None;
let mut irq = None;
let mut gpio_int = Vec::new();
let mut gpio_io = Vec::new();
for descriptor in descriptors {
match descriptor {
ResourceDescriptor::I2cSerialBus(bus) => {
if i2c.is_none() {
let adapter = bus
.resource_source
.as_ref()
.map(|source| source.source.clone())
.filter(|source| !source.is_empty())
.unwrap_or_else(|| "ACPI-I2C".to_string());
i2c = Some(I2cBinding {
adapter,
address: bus.slave_address,
});
}
}
ResourceDescriptor::Irq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied().map(u32::from);
}
}
ResourceDescriptor::ExtendedIrq(descriptor) => {
if irq.is_none() {
irq = descriptor.interrupts.first().copied();
}
}
ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor),
ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor),
_ => {}
}
}
let mut resources = AcpiDeviceResources {
i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?,
irq,
gpio_int,
gpio_io,
};
if let Some(override_address) = companion_icrs_override(path)? {
log::info!(
"{}: applying THC companion ICRS override {:04x} -> {:04x}",
path,
resources.i2c.address,
override_address
);
resources.i2c.address = override_address;
}
Ok(resources)
}
pub fn prepare_acpi_device(path: &str) -> Result<()> {
let sta = evaluate_integer_method(path, "_STA").ok();
if let Some(sta) = sta {
if sta & 0x01 == 0 {
bail!("ACPI device is not present according to _STA={sta:#x}");
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn recover_acpi_device(
path: &str,
resources: &AcpiDeviceResources,
quirk: Option<&ProbeFailureQuirk>,
) -> Result<()> {
let _ = evaluate_method(path, "_PS3", &[]);
if let Some(quirk) = quirk {
if !resources.gpio_io.is_empty() {
log::warn!(
"{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources",
path,
quirk.name,
quirk.system_vendor,
quirk.product_name,
quirk.board_name,
resources.gpio_io.len()
);
} else {
log::warn!(
"{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed",
path,
quirk.name
,
quirk.system_vendor,
quirk.product_name,
quirk.board_name
);
}
}
let _ = evaluate_method(path, "_PS0", &[]);
let _ = evaluate_method(path, "_INI", &[]);
Ok(())
}
pub fn hid_descriptor_address(path: &str) -> Result<u16> {
let args = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &args) {
Ok(AmlSerdeValue::Integer(value)) => {
return u16::try_from(value).context("_DSM descriptor address out of range")
}
Ok(other) => log::warn!(
"{}._DSM returned unexpected value {:?}; retrying fallback index",
path,
other
),
Err(err) => log::warn!(
"{}._DSM function 1 failed: {err}; retrying function 0",
path
),
}
let fallback = [
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
AmlSerdeValue::Integer(1),
AmlSerdeValue::Integer(0),
AmlSerdeValue::Package {
contents: Vec::new(),
},
];
match evaluate_method(path, "_DSM", &fallback)? {
AmlSerdeValue::Integer(value) => {
u16::try_from(value).context("fallback _DSM descriptor address out of range")
}
other => bail!("fallback _DSM returned unexpected value {other:?}"),
}
}
fn companion_icrs_override(path: &str) -> Result<Option<u16>> {
let value = match evaluate_integer_method(path, "ICRS") {
Ok(value) => value,
Err(_) => return Ok(None),
};
Ok(Some(
u16::try_from(value).context("ICRS override out of range")?,
))
}
pub fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
match evaluate_method(path, method, &[])? {
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!(
"{}.{} returned non-integer AML value {other:?}",
path,
method
),
}
}
pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result<AmlSerdeValue> {
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
.with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?;
let serialized = ron::to_string(args)
.with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?;
let mut payload = serialized.into_bytes();
payload.resize(payload.len() + 4096, 0);
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
let response = std::str::from_utf8(&payload[..used])
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
ron::from_str(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))
}
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
let path = format!("/scheme/acpi/symbols/{file_name}");
let mut file = OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("failed to open {path}"))?;
let mut ron_text = String::new();
file.read_to_string(&mut ron_text)
.with_context(|| format!("failed to read {path}"))?;
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
}
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
match value {
AmlSerdeValue::String(value) => Some(value.clone()),
AmlSerdeValue::Integer(integer) => {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
Some(format!(
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
))
}
_ => None,
}
}
pub fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}
+195
View File
@@ -0,0 +1,195 @@
use std::fs::OpenOptions;
use std::io::{Read, Write};
use anyhow::{bail, Context, Result};
use i2c_interface::{I2cTransferRequest, I2cTransferResponse, I2cTransferSegment};
use serde::{Deserialize, Serialize};
use crate::acpi::I2cBinding;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct HidDescriptor {
pub hid_desc_length: u16,
pub bcd_version: u16,
pub report_desc_length: u16,
pub report_desc_register: u16,
pub input_register: u16,
pub max_input_length: u16,
pub output_register: u16,
pub max_output_length: u16,
pub command_register: u16,
pub data_register: u16,
}
#[derive(Clone, Debug, Default)]
pub struct ReportDescriptorSummary {
pub has_keyboard_page: bool,
pub has_pointer_page: bool,
pub report_ids: bool,
}
#[derive(Clone, Debug)]
pub struct I2cAdapterClient {
binding: I2cBinding,
}
impl I2cAdapterClient {
pub fn new(binding: I2cBinding) -> Self {
Self { binding }
}
pub fn transfer(&self, segments: Vec<I2cTransferSegment>) -> Result<I2cTransferResponse> {
let request = I2cTransferRequest {
adapter: self.binding.adapter.clone(),
segments,
stop: true,
};
let serialized = ron::to_string(&request).context("failed to serialize I2C request")?;
let mut handle = OpenOptions::new()
.read(true)
.write(true)
.open("/scheme/i2c/transfer")
.context("failed to open /scheme/i2c/transfer")?;
handle
.write_all(serialized.as_bytes())
.context("failed to write I2C transfer request")?;
let mut response = String::new();
handle
.read_to_string(&mut response)
.context("failed to read I2C transfer response")?;
let transfer: I2cTransferResponse =
ron::from_str(&response).context("failed to decode I2C transfer response")?;
if !transfer.ok {
bail!(
"I2C transfer failed: {}",
transfer
.error
.unwrap_or_else(|| "unspecified transfer error".to_string())
);
}
Ok(transfer)
}
pub fn write_read(&self, address: u16, write_data: &[u8], read_len: usize) -> Result<Vec<u8>> {
let response = self.transfer(vec![
I2cTransferSegment::write(address, write_data.to_vec()),
I2cTransferSegment::read(address, read_len),
])?;
response
.read_data
.last()
.cloned()
.ok_or_else(|| anyhow::anyhow!("I2C transfer returned no readable segment payload"))
}
}
pub fn fetch_hid_descriptor(
adapter: &I2cAdapterClient,
address: u16,
hid_desc_addr: u16,
) -> Result<HidDescriptor> {
let prefix = adapter
.write_read(address, &hid_desc_addr.to_le_bytes(), 2)
.context("failed to read HID descriptor length prefix")?;
if prefix.len() < 2 {
bail!("short HID descriptor prefix: {} bytes", prefix.len());
}
let hid_desc_length = u16::from_le_bytes([prefix[0], prefix[1]]);
if hid_desc_length < 18 {
bail!("invalid HID descriptor length {hid_desc_length}");
}
let raw = adapter
.write_read(
address,
&hid_desc_addr.to_le_bytes(),
usize::from(hid_desc_length),
)
.context("failed to read full HID descriptor")?;
parse_hid_descriptor(&raw)
}
pub fn fetch_report_descriptor(
adapter: &I2cAdapterClient,
address: u16,
desc: &HidDescriptor,
) -> Result<Vec<u8>> {
adapter
.write_read(
address,
&desc.report_desc_register.to_le_bytes(),
usize::from(desc.report_desc_length),
)
.context("failed to read HID report descriptor")
}
pub fn stream_input_reports(
adapter: &I2cAdapterClient,
address: u16,
desc: &HidDescriptor,
report_desc: &[u8],
sink: &mut crate::input::InputForwarder,
) -> Result<()> {
let summary = summarize_report_descriptor(report_desc);
let input_len = usize::from(desc.max_input_length.max(4));
loop {
let report = adapter
.write_read(address, &desc.input_register.to_le_bytes(), input_len)
.context("failed to fetch I2C HID input report")?;
sink.forward_report(&summary, &report)?;
}
}
fn parse_hid_descriptor(bytes: &[u8]) -> Result<HidDescriptor> {
if bytes.len() < 18 {
bail!("short HID descriptor: {} bytes", bytes.len());
}
Ok(HidDescriptor {
hid_desc_length: le16(bytes, 0)?,
bcd_version: le16(bytes, 2)?,
report_desc_length: le16(bytes, 4)?,
report_desc_register: le16(bytes, 6)?,
input_register: le16(bytes, 8)?,
max_input_length: le16(bytes, 10)?,
output_register: le16(bytes, 12)?,
max_output_length: le16(bytes, 14)?,
command_register: le16(bytes, 16)?,
data_register: if bytes.len() >= 20 {
le16(bytes, 18)?
} else {
0
},
})
}
fn summarize_report_descriptor(report_desc: &[u8]) -> ReportDescriptorSummary {
let mut summary = ReportDescriptorSummary::default();
for window in report_desc.windows(2) {
match window {
[0x05, 0x01] => summary.has_pointer_page = true,
[0x05, 0x07] => summary.has_keyboard_page = true,
[0x85, _] => summary.report_ids = true,
_ => {}
}
}
if !summary.has_keyboard_page && !summary.has_pointer_page {
summary.has_pointer_page = true;
}
summary
}
fn le16(bytes: &[u8], offset: usize) -> Result<u16> {
let slice = bytes
.get(offset..offset + 2)
.ok_or_else(|| anyhow::anyhow!("short LE16 field at offset {offset}"))?;
Ok(u16::from_le_bytes([slice[0], slice[1]]))
}
+175
View File
@@ -0,0 +1,175 @@
use std::collections::BTreeSet;
use anyhow::Result;
use inputd::ProducerHandle;
use orbclient::{
ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE,
K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT,
K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER,
K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
};
use crate::hid::ReportDescriptorSummary;
pub struct InputForwarder {
producer: ProducerHandle,
keyboard_state: BTreeSet<u8>,
last_buttons: u8,
}
impl InputForwarder {
pub fn new() -> Result<Self> {
Ok(Self {
producer: ProducerHandle::new()?,
keyboard_state: BTreeSet::new(),
last_buttons: 0,
})
}
pub fn forward_report(
&mut self,
summary: &ReportDescriptorSummary,
report: &[u8],
) -> Result<()> {
if report.is_empty() {
return Ok(());
}
if summary.has_keyboard_page && report.len() >= 8 {
self.forward_boot_keyboard(report)?;
return Ok(());
}
if summary.has_pointer_page && report.len() >= 3 {
self.forward_boot_pointer(report)?;
return Ok(());
}
Ok(())
}
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
let modifiers = report[0];
for (bit, scancode) in [
(0_u8, K_LEFT_CTRL),
(1, K_LEFT_SHIFT),
(2, K_ALT),
(3, K_LEFT_SUPER),
(4, K_RIGHT_CTRL),
(5, K_RIGHT_SHIFT),
(6, K_ALT_GR),
(7, K_RIGHT_SUPER),
] {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: modifiers & (1 << bit) != 0,
}
.to_event(),
)?;
}
let current = report[2..8]
.iter()
.copied()
.filter(|code| *code != 0)
.collect::<BTreeSet<_>>();
for code in current.difference(&self.keyboard_state) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: true,
}
.to_event(),
)?;
}
}
for code in self.keyboard_state.difference(&current) {
if let Some(scancode) = map_boot_keyboard_usage(*code) {
self.producer.write_event(
KeyEvent {
character: '\0',
scancode,
pressed: false,
}
.to_event(),
)?;
}
}
self.keyboard_state = current;
Ok(())
}
fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> {
let dx = i8::from_ne_bytes([report[1]]) as i32;
let dy = i8::from_ne_bytes([report[2]]) as i32;
if dx != 0 || dy != 0 {
self.producer
.write_event(MouseRelativeEvent { dx, dy }.to_event())?;
}
if let Some(scroll) = report.get(3).copied() {
let scroll = i8::from_ne_bytes([scroll]) as i32;
if scroll != 0 {
self.producer
.write_event(ScrollEvent { x: 0, y: scroll }.to_event())?;
}
}
let buttons = report[0] & 0x07;
for index in 0..3 {
let mask = 1 << index;
if (buttons & mask) != (self.last_buttons & mask) {
self.producer.write_event(
ButtonEvent {
left: buttons & 0x01 != 0,
middle: buttons & 0x04 != 0,
right: buttons & 0x02 != 0,
}
.to_event(),
)?;
break;
}
}
self.last_buttons = buttons;
Ok(())
}
}
fn map_boot_keyboard_usage(usage: u8) -> Option<u8> {
Some(match usage {
0x04..=0x1D => b'a' + (usage - 0x04),
0x1E => b'1',
0x1F => b'2',
0x20 => b'3',
0x21 => b'4',
0x22 => b'5',
0x23 => b'6',
0x24 => b'7',
0x25 => b'8',
0x26 => b'9',
0x27 => b'0',
0x28 => K_ENTER,
0x29 => K_ESC,
0x2A => K_BKSP,
0x2B => K_TAB,
0x2C => K_SPACE,
0x2D => K_MINUS,
0x2E => K_EQUALS,
0x2F => K_BRACE_OPEN,
0x30 => K_BRACE_CLOSE,
0x33 => K_SEMICOLON,
0x34 => K_QUOTE,
0x35 => K_TICK,
0x36 => K_COMMA,
0x37 => K_PERIOD,
0x38 => K_SLASH,
0x39 => K_CAPS,
_ => return None,
})
}
+114
View File
@@ -0,0 +1,114 @@
use std::process;
use std::thread;
use std::time::Duration;
use anyhow::{Context, Result};
mod acpi;
mod hid;
mod input;
mod quirks;
use acpi::{
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
scan_acpi_i2c_hid_devices,
};
use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
use input::InputForwarder;
use quirks::match_probe_failure_quirk;
fn main() {
daemon::Daemon::new(daemon);
}
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"input",
"i2c-hid",
"i2c-hidd",
common::output_level(),
common::file_level(),
);
if let Err(err) = run(daemon) {
log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
process::exit(1);
}
process::exit(0);
}
fn run(daemon: daemon::Daemon) -> Result<()> {
log::info!("RB_I2C_HIDD_SCHEMA version=1");
let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
if devices.is_empty() {
log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
}
let mut workers = Vec::new();
for device in devices {
log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
workers.push(thread::spawn(move || {
if let Err(err) = bind_device(&device) {
log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
}
}));
}
daemon.ready();
if workers.is_empty() {
loop {
thread::sleep(Duration::from_secs(5));
}
}
for worker in workers {
let _ = worker.join();
}
Ok(())
}
pub fn bind_device(device_path: &str) -> Result<()> {
prepare_acpi_device(device_path)
.with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
let resources = read_decoded_resources(device_path)
.with_context(|| format!("failed to decode _CRS for {device_path}"))?;
log::info!(
"RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
device_path,
resources.i2c.adapter,
resources.i2c.address,
resources.irq,
resources.gpio_int.len(),
resources.gpio_io.len()
);
let hid_desc_addr = hid_descriptor_address(device_path)
.with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
let adapter = I2cAdapterClient::new(resources.i2c.clone());
let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
.with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
.with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
match stream_input_reports(
&adapter,
resources.i2c.address,
&hid_desc,
&report_desc,
&mut forwarder,
) {
Ok(()) => Ok(()),
Err(err) => {
let quirk =
match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
recover_acpi_device(device_path, &resources, quirk.as_ref())
.with_context(|| format!("failed ACPI recovery for {device_path}"))?;
Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
}
}
}
+88
View File
@@ -0,0 +1,88 @@
use std::fs;
use anyhow::{Context, Result};
use serde::Deserialize;
#[derive(Clone, Debug)]
pub struct ProbeFailureQuirk {
pub name: String,
pub system_vendor: Option<String>,
pub product_name: Option<String>,
pub board_name: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
struct ProbeFailureQuirkFile {
quirks: Vec<ProbeFailureQuirkEntry>,
}
#[derive(Clone, Debug, Deserialize)]
struct ProbeFailureQuirkEntry {
name: String,
system_vendor: Option<String>,
product_name: Option<String>,
board_name: Option<String>,
}
#[derive(Default)]
struct DmiSnapshot {
system_vendor: String,
product_name: String,
board_name: String,
}
pub fn match_probe_failure_quirk() -> Result<Option<ProbeFailureQuirk>> {
let snapshot = read_dmi_snapshot()?;
for entry in load_quirks()? {
if field_matches(&entry.system_vendor, &snapshot.system_vendor)
&& field_matches(&entry.product_name, &snapshot.product_name)
&& field_matches(&entry.board_name, &snapshot.board_name)
{
return Ok(Some(ProbeFailureQuirk {
name: entry.name,
system_vendor: entry.system_vendor,
product_name: entry.product_name,
board_name: entry.board_name,
}));
}
}
Ok(None)
}
fn load_quirks() -> Result<Vec<ProbeFailureQuirkEntry>> {
let path = "/etc/i2c-hidd-quirks.ron";
let text = match fs::read_to_string(path) {
Ok(text) => text,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(err) => return Err(err).with_context(|| format!("failed to read {path}")),
};
let file: ProbeFailureQuirkFile =
ron::from_str(&text).with_context(|| format!("failed to decode {path}"))?;
Ok(file.quirks)
}
fn read_dmi_snapshot() -> Result<DmiSnapshot> {
Ok(DmiSnapshot {
system_vendor: read_dmi_field("system_vendor")?,
product_name: read_dmi_field("product_name")?,
board_name: read_dmi_field("board_name")?,
})
}
fn read_dmi_field(field: &str) -> Result<String> {
let path = format!("/scheme/acpi/dmi/{field}");
match fs::read_to_string(&path) {
Ok(value) => Ok(value.trim().to_string()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
Err(err) => Err(err).with_context(|| format!("failed to read {path}")),
}
}
fn field_matches(expected: &Option<String>, actual: &str) -> bool {
expected
.as_deref()
.map(|expected| actual.eq_ignore_ascii_case(expected))
.unwrap_or(true)
}
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "intel-thc-hidd"
description = "Intel THC QuickI2C HID transport daemon"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
log.workspace = true
pci_types = "0.10.1"
redox_syscall = { workspace = true, features = ["std"] }
libredox.workspace = true
redox-scheme.workspace = true
ron.workspace = true
serde.workspace = true
acpi-resource = { path = "../../acpi-resource" }
amlserde = { path = "../../amlserde" }
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
i2c-interface = { path = "../../i2c/i2c-interface" }
pcid = { path = "../../pcid" }
scheme-utils = { path = "../../../scheme-utils" }
[lints]
workspace = true
+260
View File
@@ -0,0 +1,260 @@
use std::collections::BTreeSet;
use std::fs::{self, OpenOptions};
use std::io::Read;
use std::process;
use std::thread;
use std::time::Duration;
use acpi_resource::ResourceDescriptor;
use amlserde::{AmlSerde, AmlSerdeValue};
use anyhow::{bail, Context, Result};
use libredox::flag::{O_CLOEXEC, O_RDWR};
use pcid_interface::PciFunctionHandle;
mod quicki2c;
mod thc;
use quicki2c::QuickI2cTransport;
use thc::{ThcController, SUPPORTED_PCI_IDS};
fn main() {
pcid_interface::pci_daemon(daemon);
}
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
common::setup_logging(
"input",
"intel-thc",
"intel-thc-hidd",
common::output_level(),
common::file_level(),
);
if let Err(err) = run(daemon, &mut pcid_handle) {
log::error!("RB_THC_HIDD_FATAL error={err:#}");
process::exit(1);
}
process::exit(0);
}
fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
log::info!("RB_THC_HIDD_SCHEMA version=1");
let pci_config = pcid_handle.config();
let id = (
pci_config.func.full_device_id.vendor_id,
pci_config.func.full_device_id.device_id,
);
if !SUPPORTED_PCI_IDS.contains(&id) {
bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
}
pcid_handle.enable_device();
let bar = unsafe { pcid_handle.map_bar(0) };
let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
.context("failed to create THC controller")?;
let companion = resolve_acpi_companion(&pci_config.func.addr)
.context("failed to resolve ACPI companion for THC device")?;
let override_address = companion
.as_deref()
.map(companion_slave_address_override)
.transpose()
.context("failed to evaluate THC slave-address override")?
.flatten();
let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
.context("failed to scan PNP0C50 devices for THC controller")?;
let effective_address = override_address.unwrap_or(0x0015);
let transport = QuickI2cTransport::new(controller, effective_address);
transport.prime_controller();
transport.emulate_transfer(&[]);
log::debug!("RB_THC_HIDD status={:#x}", transport.status());
match transport.register_with_i2cd(companion.as_deref(), override_address) {
Ok(()) => {}
Err(err) => {
log::warn!("RB_THC_HIDD registration error={err:#}");
}
}
log::info!(
"RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
pci_config.func.name(),
companion,
override_address,
hid_devices.len()
);
daemon.ready();
loop {
thread::sleep(Duration::from_secs(5));
}
}
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
for entry in entries {
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._ADR") {
continue;
}
let symbol = read_aml_symbol(&file_name)?;
if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
continue;
}
let device = symbol
.name
.strip_suffix("._ADR")
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
return Ok(Some(device));
}
Ok(None)
}
fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
let icrs = evaluate_integer_method(path, "ICRS").ok();
let isub = evaluate_integer_method(path, "ISUB").ok();
Ok(icrs
.or(isub)
.map(|value| u16::try_from(value))
.transpose()
.context("THC ACPI override out of range")?)
}
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
let entries =
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
let mut devices = BTreeSet::new();
for entry in entries {
let entry = entry.context("failed to enumerate ACPI HID entry")?;
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
continue;
};
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
continue;
}
let symbol = read_aml_symbol(&file_name)?;
let is_hid = matches!(
decode_hardware_id(&symbol.value).as_deref(),
Some("PNP0C50" | "ACPI0C50")
);
if !is_hid {
continue;
}
let device = symbol
.name
.strip_suffix("._HID")
.or_else(|| symbol.name.strip_suffix("._CID"))
.unwrap_or(&symbol.name)
.trim_start_matches('\\')
.replace('/', ".");
if let Some(companion) = companion {
if !is_bound_to_companion(&device, companion)? {
continue;
}
}
devices.insert(device);
}
Ok(devices.into_iter().collect())
}
fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
let resource_path = format!("/scheme/acpi/resources/{device}");
let serialized = match fs::read_to_string(&resource_path) {
Ok(serialized) => serialized,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
};
let resources: Vec<ResourceDescriptor> =
ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
Ok(resources.into_iter().any(|resource| match resource {
ResourceDescriptor::I2cSerialBus(bus) => bus
.resource_source
.as_ref()
.map(|source| source.source == companion)
.unwrap_or(false),
_ => false,
}))
}
fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
.with_context(|| format!("failed to open {symbol_path}"))?;
let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
.context("failed to serialize ACPI call arguments")?
.into_bytes();
payload.resize(payload.len() + 2048, 0);
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
let response = std::str::from_utf8(&payload[..used])
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
match ron::from_str::<AmlSerdeValue>(response)
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
{
AmlSerdeValue::Integer(value) => Ok(value),
other => bail!("{}.{} returned non-integer value {other:?}", path, method),
}
}
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
let path = format!("/scheme/acpi/symbols/{file_name}");
let mut file = OpenOptions::new()
.read(true)
.open(&path)
.with_context(|| format!("failed to open {path}"))?;
let mut ron_text = String::new();
file.read_to_string(&mut ron_text)
.with_context(|| format!("failed to read {path}"))?;
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
}
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
match value {
AmlSerdeValue::String(value) => Some(value.clone()),
AmlSerdeValue::Integer(integer) => {
let vendor = integer & 0xFFFF;
let device = (integer >> 16) & 0xFFFF;
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
let device_1 = (device >> 4) & 0xF;
let device_2 = (device >> 0) & 0xF;
let device_3 = (device >> 12) & 0xF;
let device_4 = (device >> 8) & 0xF;
Some(format!(
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
))
}
_ => None,
}
}
fn normalize_device_path(path: &str) -> String {
path.trim_start_matches('\\')
.trim_matches('/')
.replace('/', ".")
}

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