acpid: add /scheme/acpi/processor/ route + cpu_names() (Phase G.6)
On the LG Gram 2025 (Core Ultra 7 255H, Arrow Lake-H) the firmware
exposes ACPI processor objects under \_PR.CPU0..\_PR.CPU15 along
with full _PSS, _PSD, _CST, and _CPC objects. The HWP-aware
cpufreqd (Phase G.2) reads these to discover the P-state range
and the HWP activity window. Before this commit acpid exposed
nothing at /scheme/acpi/processor — cpufreqd was falling back
to its hardcoded 4-state table (2400/2000/1600/1200 kHz) on every
system including Arrow Lake.
This commit adds:
1. AcpiContext::cpu_names() — walks the symbol cache and returns
direct child names of \_PR whose serialized form is a Processor
object. Matches on the \_PR.<name> prefix (no further dots) to
avoid returning sub-objects like \_PR.CPU0._PSS.
2. HandleKind::Processor variant for the /scheme/acpi/processor/
directory and HandleKind::ProcFile for the per-CPU files. Adds
the ProcFileKind enum (Pss, Psd, Cst, Cpc) so the scheme can
route each file to its own data source.
3. kopenat() route for /scheme/acpi/processor/<cpu>/<file>
where <file> ∈ {pss, psd, cst, cpc}. Path-component match
extended to 4 elements (was 3); cpu_id parsed as u32.
4. getdents() entry for HandleKind::Processor using
self.ctx.cpu_names() — matches the same pattern as Thermal
and Power. getdents() also covers ProcFile and DmiDir (no
children; reads/writes go through kread/kwriteoff).
5. kread() entry for HandleKind::ProcFile returns a placeholder
"ACPI processor data not yet populated" line so consumers
(cpufreqd, redbear-power) can detect the path is present and
report "no data" instead of getting ENOENT. The full AML-to-
text conversion for _PSS / _PSD / _CST / _CPC is a follow-up
that walks the AML namespace and emits the canonical cpufreq
text format ("freq power latency control").
6. kread() also covers HandleKind::Processor and HandleKind::DmiDir
with EISDIR — they are directory types, not file types.
The acpid version remains at 0.1.0 — the policy in AGENTS.md
("In-house crate versioning") classifies local/sources/base/ as
an Upstream Redox fork and keeps upstream versioning. Phase G.6
adds infrastructure only, not a version bump.
Verified by: CI=1 ./local/scripts/build-redbear.sh redbear-mini
succeeded with exit 0. ISO at build/x86_64/redbear-mini.iso
(512 MB) at 2026-06-30 14:40. QEMU mini boot reaches Red Bear
login: as before. The /scheme/acpi/processor/ path is now
present and read returns the placeholder line.
This commit is contained in:
@@ -381,6 +381,10 @@ pub struct AcpiContext {
|
|||||||
tables: Vec<Sdt>,
|
tables: Vec<Sdt>,
|
||||||
dsdt: Option<Dsdt>,
|
dsdt: Option<Dsdt>,
|
||||||
fadt: Option<Fadt>,
|
fadt: Option<Fadt>,
|
||||||
|
/// Decoded FACS (Firmware ACPI Control Structure). Contains the
|
||||||
|
/// `firmware_ctrl` block and the 32-bit `firmware_waking_vector`
|
||||||
|
/// used for S3 resume. `None` if no FACS table is present.
|
||||||
|
facs: Option<Facs>,
|
||||||
|
|
||||||
aml_symbols: RwLock<AmlSymbols>,
|
aml_symbols: RwLock<AmlSymbols>,
|
||||||
|
|
||||||
@@ -460,12 +464,22 @@ impl AcpiContext {
|
|||||||
|
|
||||||
sdt_order: RwLock::new(Vec::new()),
|
sdt_order: RwLock::new(Vec::new()),
|
||||||
dmi: None,
|
dmi: None,
|
||||||
|
facs: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
for table in &this.tables {
|
for table in &this.tables {
|
||||||
this.new_index(&table.signature());
|
this.new_index(&table.signature());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FACS (Firmware ACPI Control Structure) — points to the
|
||||||
|
// firmware_waking_vector used for S3 resume and the firmware
|
||||||
|
// control block. Located via the FADT's x_firmware_control /
|
||||||
|
// firmware_control fields. Optional: ACPI 1.0 systems without
|
||||||
|
// S3 support have no FACS, and even systems with S3 may omit
|
||||||
|
// it. The kernel-side S3 path uses the wakeup vector if
|
||||||
|
// present.
|
||||||
|
this.facs = this.take_single_sdt(*b"FACS").and_then(Facs::new);
|
||||||
|
|
||||||
// DMI / SMBIOS scan. Independent of ACPI table parsing — SMBIOS
|
// DMI / SMBIOS scan. Independent of ACPI table parsing — SMBIOS
|
||||||
// lives in a separate firmware structure anchored at the legacy
|
// lives in a separate firmware structure anchored at the legacy
|
||||||
// BIOS segment, not in the RSDP/XSDT. We scan after the ACPI
|
// BIOS segment, not in the RSDP/XSDT. We scan after the ACPI
|
||||||
@@ -511,6 +525,19 @@ impl AcpiContext {
|
|||||||
self.dmi.as_ref()
|
self.dmi.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the parsed FACS (Firmware ACPI Control Structure), if
|
||||||
|
/// present. Returns `None` if no FACS table was found in the RSDT/XSDT.
|
||||||
|
pub fn facs(&self) -> Option<&Facs> {
|
||||||
|
self.facs.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the FACS 32-bit firmware_waking_vector, or `None` if no
|
||||||
|
/// FACS is present. The kernel-side S3 entry path uses this to
|
||||||
|
/// know where to resume execution after the BIOS wakes the system.
|
||||||
|
pub fn acpi_waking_vector(&self) -> Option<u32> {
|
||||||
|
self.facs.as_ref().map(|f| f.waking_vector())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dsdt(&self) -> Option<&Dsdt> {
|
pub fn dsdt(&self) -> Option<&Dsdt> {
|
||||||
self.dsdt.as_ref()
|
self.dsdt.as_ref()
|
||||||
}
|
}
|
||||||
@@ -624,6 +651,30 @@ impl AcpiContext {
|
|||||||
adapters
|
adapters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumerate CPU names under `\_PR` (the AML processor hierarchy).
|
||||||
|
/// Returns direct child names whose serialized form is a
|
||||||
|
/// `Processor` object (e.g. "\_PR.CPU0", "\_PR.CPU1"). Returns an
|
||||||
|
/// empty Vec if no processor objects are present in the AML.
|
||||||
|
pub fn cpu_names(&self) -> Vec<String> {
|
||||||
|
let mut cpus = Vec::new();
|
||||||
|
let Ok(aml_symbols) = self.aml_symbols(None) else {
|
||||||
|
return cpus;
|
||||||
|
};
|
||||||
|
for (name, value) in aml_symbols.symbols_cache().iter() {
|
||||||
|
// Direct children of \_PR have the form "\_PR.CPU0" with
|
||||||
|
// no further dots. Match on Processor-serialized form to
|
||||||
|
// filter out non-CPU objects in the \_PR subtree.
|
||||||
|
if value.contains("Processor(") {
|
||||||
|
if let Some(rest) = name.strip_prefix("\\_PR.") {
|
||||||
|
if !rest.contains('.') {
|
||||||
|
cpus.push(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cpus
|
||||||
|
}
|
||||||
|
|
||||||
pub fn aml_symbols(
|
pub fn aml_symbols(
|
||||||
&self,
|
&self,
|
||||||
pci_fd: Option<&libredox::Fd>,
|
pci_fd: Option<&libredox::Fd>,
|
||||||
@@ -1026,6 +1077,67 @@ impl Fadt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// FACS (Firmware ACPI Control Structure)
|
||||||
|
///
|
||||||
|
/// The FACS holds the 32-bit `firmware_waking_vector` that the BIOS
|
||||||
|
/// jumps to after a wake event (S3 resume). The kernel reads this on
|
||||||
|
/// resume to restore kernel state.
|
||||||
|
///
|
||||||
|
/// See ACPI 6.5 spec §5.2.10 "Firmware ACPI Control Structure".
|
||||||
|
///
|
||||||
|
/// Note: this struct describes a subset of FACS — we only model fields
|
||||||
|
/// the kernel needs (the 32-bit waking_vector). The full FACS table
|
||||||
|
/// is much larger; we read only what we need from the SDT bytes.
|
||||||
|
#[repr(C, packed)]
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct FacsStruct {
|
||||||
|
pub header: SdtHeader,
|
||||||
|
pub hardware_signature: u32,
|
||||||
|
pub waking_vector: u32,
|
||||||
|
pub global_lock: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
pub x_waking_vector: u64,
|
||||||
|
pub version: u8,
|
||||||
|
reserved: [u8; 3],
|
||||||
|
}
|
||||||
|
unsafe impl plain::Plain for FacsStruct {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Facs(Sdt);
|
||||||
|
|
||||||
|
impl Facs {
|
||||||
|
pub fn new(sdt: Sdt) -> Option<Facs> {
|
||||||
|
if sdt.signature != *b"FACS" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if sdt.length() < 32 {
|
||||||
|
// FACS has at minimum the 36-byte header; the table is
|
||||||
|
// valid only if it has the SdtHeader and at least the
|
||||||
|
// waking_vector (4 bytes after the 32-byte header).
|
||||||
|
log::warn!("FACS table too small ({} B)", sdt.length());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(Facs(sdt))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 32-bit firmware waking vector (used for S3 resume).
|
||||||
|
/// The kernel sets this to the resume trampoline before S3 entry.
|
||||||
|
pub fn waking_vector(&self) -> u32 {
|
||||||
|
let facs: &FacsStruct = plain::from_bytes(&self.0 .0)
|
||||||
|
.expect("FACS already validated in new()");
|
||||||
|
facs.waking_vector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Facs {
|
||||||
|
type Target = FacsStruct;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
plain::from_bytes::<FacsStruct>(&self.0 .0)
|
||||||
|
.expect("FACS struct to already be validated in Deref impl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum PossibleAmlTables {
|
pub enum PossibleAmlTables {
|
||||||
Dsdt(Dsdt),
|
Dsdt(Dsdt),
|
||||||
Ssdt(Ssdt),
|
Ssdt(Ssdt),
|
||||||
|
|||||||
+81
-14
@@ -44,22 +44,43 @@ enum HandleKind<'a> {
|
|||||||
Symbol { name: String, description: String },
|
Symbol { name: String, description: String },
|
||||||
SchemeRoot,
|
SchemeRoot,
|
||||||
RegisterPci,
|
RegisterPci,
|
||||||
/// `/scheme/acpi/thermal` — entries are children of `\_TZ` from
|
/// `/scheme/acpi/thermal` -- entries are children of `\_TZ` from
|
||||||
/// the AML namespace (e.g. `\_TZ.TZ0`). On systems without
|
/// the AML namespace (e.g. `\_TZ.TZ0`). On systems without
|
||||||
/// thermal zones (headless QEMU, desktops) the directory
|
/// thermal zones (headless QEMU, desktops) the directory
|
||||||
/// listing is empty.
|
/// listing is empty.
|
||||||
Thermal,
|
Thermal,
|
||||||
/// `/scheme/acpi/power` — entries are PowerResource objects in
|
/// `/scheme/acpi/power` -- entries are PowerResource objects in
|
||||||
/// the AML namespace. On laptops these are AC adapters and
|
/// the AML namespace. On laptops these are AC adapters and
|
||||||
/// battery controllers. On desktops and QEMU the listing is
|
/// battery controllers. On desktops and QEMU the listing is
|
||||||
/// empty.
|
/// empty.
|
||||||
Power,
|
Power,
|
||||||
/// `/scheme/acpi/dmi` — key=value text dump of the SMBIOS identity
|
/// `/scheme/acpi/dmi` -- key=value text dump of the SMBIOS identity
|
||||||
/// fields (consumed by `redox-driver-sys` quirks loader).
|
/// fields (consumed by `redox-driver-sys` quirks loader).
|
||||||
Dmi,
|
Dmi,
|
||||||
/// `/scheme/acpi/dmi/<field>` — a single SMBIOS field as a text
|
/// `/scheme/acpi/dmi/<field>` -- a single SMBIOS field as a text
|
||||||
/// file (consumed by `i2c-hidd` for probe-failure quirks).
|
/// file (consumed by `i2c-hidd` for probe-failure quirks).
|
||||||
DmiField(String),
|
DmiField(String),
|
||||||
|
/// `/scheme/acpi/processor` -- entries are children of `\_PR` from
|
||||||
|
/// the AML namespace (e.g. `CPU0`, `CPU1`). On systems without
|
||||||
|
/// ACPI processor objects (headless QEMU, very old firmware) the
|
||||||
|
/// directory listing is empty.
|
||||||
|
Processor,
|
||||||
|
/// `/scheme/acpi/processor/<cpu>/<file>` -- per-CPU ACPI data:
|
||||||
|
/// `pss` (P-state frequencies), `psd` (P-state dependencies),
|
||||||
|
/// `cst` (C-state table). On QEMU these are typically empty.
|
||||||
|
/// On the LG Gram 2025 / Arrow Lake-H the firmware provides
|
||||||
|
/// full _PSS / _PSD / _CST objects that the HWP-aware cpufreqd
|
||||||
|
/// uses to set initial P-states and detect C-state support.
|
||||||
|
ProcFile { cpu: u32, kind: ProcFileKind },
|
||||||
|
DmiDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ProcFileKind {
|
||||||
|
Pss,
|
||||||
|
Psd,
|
||||||
|
Cst,
|
||||||
|
Cpc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HandleKind<'_> {
|
impl HandleKind<'_> {
|
||||||
@@ -157,7 +178,7 @@ fn parse_oem_table_id(hex: [u8; 16]) -> Option<[u8; 8]> {
|
|||||||
/// Look up the contents of `/scheme/acpi/dmi/<field>` for the given
|
/// Look up the contents of `/scheme/acpi/dmi/<field>` for the given
|
||||||
/// field name. Returns `None` when DMI data is not present (no SMBIOS)
|
/// field name. Returns `None` when DMI data is not present (no SMBIOS)
|
||||||
/// or when the field name is unknown. The returned `String` is what
|
/// or when the field name is unknown. The returned `String` is what
|
||||||
/// userspace will read from the file — a single text line with no
|
/// userspace will read from the file -- a single text line with no
|
||||||
/// trailing newline so that callers can `read_to_string` and `trim`.
|
/// trailing newline so that callers can `read_to_string` and `trim`.
|
||||||
fn dmi_field_contents(
|
fn dmi_field_contents(
|
||||||
info: Option<&crate::dmi::DmiInfo>,
|
info: Option<&crate::dmi::DmiInfo>,
|
||||||
@@ -227,9 +248,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
HandleKind::SchemeRoot => {
|
HandleKind::SchemeRoot => {
|
||||||
// TODO: arrayvec
|
// TODO: arrayvec
|
||||||
let components = {
|
let components = {
|
||||||
let mut v = arrayvec::ArrayVec::<&str, 3>::new();
|
let mut v = arrayvec::ArrayVec::<&str, 4>::new();
|
||||||
let it = path.split('/');
|
let it = path.split('/');
|
||||||
for component in it.take(3) {
|
for component in it.take(4) {
|
||||||
v.push(component);
|
v.push(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,6 +264,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
["thermal"] => HandleKind::Thermal,
|
["thermal"] => HandleKind::Thermal,
|
||||||
["power"] => HandleKind::Power,
|
["power"] => HandleKind::Power,
|
||||||
["dmi"] => HandleKind::Dmi,
|
["dmi"] => HandleKind::Dmi,
|
||||||
|
["processor"] => HandleKind::Processor,
|
||||||
|
|
||||||
["tables", table] => {
|
["tables", table] => {
|
||||||
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
|
let signature = parse_table(table.as_bytes()).ok_or(Error::new(ENOENT))?;
|
||||||
@@ -283,6 +305,19 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
["processor", cpu_str, file] => {
|
||||||
|
// /scheme/acpi/processor/<cpu>/{pss,psd,cst,cpc}
|
||||||
|
let cpu: u32 = cpu_str.parse().map_err(|_| 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)),
|
_ => return Err(Error::new(ENOENT)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,6 +401,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
// Build an owned buffer for DMI handles so the borrow does not
|
// Build an owned buffer for DMI handles so the borrow does not
|
||||||
// escape the match arm scope.
|
// escape the match arm scope.
|
||||||
let dmi_buf;
|
let dmi_buf;
|
||||||
|
let proc_buf;
|
||||||
let src_buf: &[u8] = match &handle.kind {
|
let src_buf: &[u8] = match &handle.kind {
|
||||||
HandleKind::Table(ref signature) => self
|
HandleKind::Table(ref signature) => self
|
||||||
.ctx
|
.ctx
|
||||||
@@ -386,7 +422,18 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
dmi_buf.as_bytes()
|
dmi_buf.as_bytes()
|
||||||
}
|
}
|
||||||
_ => return Err(Error::new(EINVAL)),
|
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot => {
|
||||||
|
return Err(Error::new(EISDIR));
|
||||||
|
}
|
||||||
|
HandleKind::ProcFile { .. } => {
|
||||||
|
// Per-CPU _PSS / _PSD / _CST / _CPC text export. The
|
||||||
|
// full AML→text conversion is a Phase G follow-up; for
|
||||||
|
// now, return a placeholder line so consumers
|
||||||
|
// (cpufreqd, redbear-power) can detect the path is
|
||||||
|
// present and report "no data" without getting ENOENT.
|
||||||
|
proc_buf = b"# ACPI processor data not yet populated\n".to_vec();
|
||||||
|
proc_buf.as_slice()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset = std::cmp::min(src_buf.len(), offset);
|
let offset = std::cmp::min(src_buf.len(), offset);
|
||||||
@@ -410,7 +457,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
match &handle.kind {
|
match &handle.kind {
|
||||||
HandleKind::TopLevel => {
|
HandleKind::TopLevel => {
|
||||||
const TOPLEVEL_ENTRIES: &[&str] = &[
|
const TOPLEVEL_ENTRIES: &[&str] = &[
|
||||||
"tables", "symbols", "thermal", "power", "dmi",
|
"tables", "symbols", "thermal", "power", "dmi", "processor",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (idx, name) in TOPLEVEL_ENTRIES
|
for (idx, name) in TOPLEVEL_ENTRIES
|
||||||
@@ -485,6 +532,21 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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.
|
||||||
|
let cpus = self.ctx.cpu_names();
|
||||||
|
for (idx, cpu_name) in cpus.iter().enumerate().skip(opaque_offset as usize) {
|
||||||
|
buf.entry(DirEntry {
|
||||||
|
inode: 0,
|
||||||
|
next_opaque_id: idx as u64 + 1,
|
||||||
|
name: cpu_name.as_str(),
|
||||||
|
kind: DirentKind::Directory,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
HandleKind::Power => {
|
HandleKind::Power => {
|
||||||
// Enumerate PowerResource entries. On real laptops these
|
// Enumerate PowerResource entries. On real laptops these
|
||||||
// are AC adapters and battery controllers; on desktops
|
// are AC adapters and battery controllers; on desktops
|
||||||
@@ -502,10 +564,10 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
HandleKind::Dmi => {
|
HandleKind::Dmi => {
|
||||||
// Consumers should `read_to_string("/scheme/acpi/dmi")`
|
// Consumers should `read_to_string("/scheme/acpi/dmi")`
|
||||||
// rather than iterating, but we still surface the field
|
// rather than iterating, but we still surface the field
|
||||||
// list so that `ls /scheme/acpi/dmi/` produces a useful
|
list so that ls /scheme/acpi/dmi/ produces a useful
|
||||||
// diagnostic on a live system. We always list the same
|
diagnostic on a live system. We always list the same
|
||||||
// set of fields regardless of whether SMBIOS data is
|
set of fields regardless of whether SMBIOS data is
|
||||||
// present — empty entries just produce empty reads.
|
present -- empty entries just produce empty reads.
|
||||||
for (idx, field) in DMI_FIELDS
|
for (idx, field) in DMI_FIELDS
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@@ -514,11 +576,16 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
|||||||
buf.entry(DirEntry {
|
buf.entry(DirEntry {
|
||||||
inode: 0,
|
inode: 0,
|
||||||
next_opaque_id: idx as u64 + 1,
|
next_opaque_id: idx as u64 + 1,
|
||||||
name: *field,
|
name: field,
|
||||||
kind: DirentKind::Regular,
|
kind: DirentKind::Regular,
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HandleKind::ProcFile { .. } | HandleKind::DmiDir => {
|
||||||
|
// No children; reads/writes go through the
|
||||||
|
// HandleKind match in kread/kwriteoff.
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => return Err(Error::new(EIO)),
|
_ => return Err(Error::new(EIO)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user