Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions drivers/std/src/devices/cpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
use file_system::{
DirectBaseOperations, DirectCharacterDevice, Error, MountOperations, Result, Size,
};
use std::collections::BTreeMap;

const EMBEDDED_FIELDS: [&str; 6] = [
"processor",
"vendor_id",
"model name",
"cpu MHz",
"cpu cores",
"architecture",
];

pub struct CpuInformationsDevice;

fn parse_proc_cpuinfo(content: &str) -> Vec<BTreeMap<String, String>> {
let mut entries = Vec::new();
let mut current = BTreeMap::new();

for line in content.lines() {
let line = line.trim();

if line.is_empty() {
if !current.is_empty() {
entries.push(current);
current = BTreeMap::new();
}
continue;
}

if let Some((key, value)) = line.split_once(':') {
current.insert(key.trim().to_string(), value.trim().to_string());
}
}

if !current.is_empty() {
entries.push(current);
}

entries
}

fn format_entry(
entry: &BTreeMap<String, String>,
index: usize,
architecture: &str,
fallback_cores: &str,
) -> String {
let mut block = String::new();

for key in EMBEDDED_FIELDS {
let value = match key {
"processor" => entry.get(key).cloned().unwrap_or_else(|| index.to_string()),
"architecture" => architecture.to_string(),
"cpu cores" => entry
.get(key)
.cloned()
.unwrap_or_else(|| fallback_cores.to_string()),
_ => entry
.get(key)
.cloned()
.unwrap_or_else(|| "unknown".to_string()),
};

block.push_str(key);
block.push_str("\t: ");
block.push_str(&value);
block.push('\n');
}

block.push('\n');
block
}

fn build_cpuinfo_text_from_proc(
content: &str,
architecture: &str,
available_cores: Option<usize>,
) -> String {
let parsed = parse_proc_cpuinfo(content);
let fallback_cores = available_cores
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());

if parsed.is_empty() {
return format_entry(&BTreeMap::new(), 0, architecture, &fallback_cores);
}

let mut output = String::new();
for (index, entry) in parsed.iter().enumerate() {
output.push_str(&format_entry(entry, index, architecture, &fallback_cores));
}

output
}

fn render_cpuinfo_text() -> String {
let architecture = std::env::consts::ARCH;
let cores = std::thread::available_parallelism().ok().map(usize::from);

if cfg!(target_os = "linux") {
if let Ok(content) = std::fs::read_to_string("/proc/cpuinfo") {
return build_cpuinfo_text_from_proc(&content, architecture, cores);
}
}

build_cpuinfo_text_from_proc("", architecture, cores)
}

impl DirectBaseOperations for CpuInformationsDevice {
fn read(&self, buffer: &mut [u8], absolute_position: Size) -> Result<usize> {
let content = render_cpuinfo_text();
let bytes = content.as_bytes();

let start = usize::try_from(absolute_position).unwrap_or(usize::MAX);
if start >= bytes.len() {
return Ok(0);
}

let length = buffer.len().min(bytes.len() - start);
buffer[..length].copy_from_slice(&bytes[start..start + length]);

Ok(length)
}

fn write(&self, _: &[u8], _: Size) -> Result<usize> {
Err(Error::UnsupportedOperation)
}
}

impl MountOperations for CpuInformationsDevice {}

impl DirectCharacterDevice for CpuInformationsDevice {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parser_extracts_embedded_subset_fields() {
let sample = "processor\t: 0\nvendor_id\t: ARM\nmodel name\t: Cortex-A53\ncpu MHz\t\t: 1200.0\ncpu cores\t: 4\n\n";
let output = build_cpuinfo_text_from_proc(sample, "aarch64", Some(4));
assert!(output.contains("processor\t: 0"));
assert!(output.contains("vendor_id\t: ARM"));
assert!(output.contains("model name\t: Cortex-A53"));
assert!(output.contains("cpu MHz\t: 1200.0"));
assert!(output.contains("cpu cores\t: 4"));
assert!(output.contains("architecture\t: aarch64"));
}

#[test]
fn parser_falls_back_to_unknown_for_missing_values() {
let output = build_cpuinfo_text_from_proc("", "aarch64", None);
assert!(output.contains("vendor_id\t: unknown"));
assert!(output.contains("cpu MHz\t: unknown"));
assert!(output.contains("cpu cores\t: unknown"));
}

#[test]
fn read_uses_absolute_offset_and_handles_eof() {
let device = CpuInformationsDevice;
let mut buffer = [0u8; 32];
let first = device.read(&mut buffer, 0).unwrap();
assert!(first > 0);

let eof = device.read(&mut buffer, Size::MAX).unwrap();
assert_eq!(eof, 0);
}

#[test]
fn write_is_not_supported() {
let device = CpuInformationsDevice;
let result = device.write(b"ignored", 0);
assert!(matches!(result, Err(Error::UnsupportedOperation)));
}
}
2 changes: 2 additions & 0 deletions drivers/std/src/devices/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod cpu;
mod time;

pub use cpu::*;
pub use time::*;
110 changes: 110 additions & 0 deletions drivers/wasm/src/devices/cpu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use alloc::string::{String, ToString};
use file_system::{
DirectBaseOperations, DirectCharacterDevice, Error, MountOperations, Result, Size,
};

pub struct CpuInformationsDevice;

fn get_hardware_concurrency() -> Option<u32> {
let window = web_sys::window()?;
let navigator = window.navigator();
let value = js_sys::Reflect::get(
&navigator,
&wasm_bindgen::JsValue::from_str("hardwareConcurrency"),
)
.ok()?;
let value = value.as_f64()?;

if !value.is_finite() || value <= 0.0 {
return None;
}

Some(value as u32)
}

fn build_cpuinfo_text(architecture: &str, hardware_concurrency: Option<u32>) -> String {
let cores = hardware_concurrency
.map(|value| value.to_string())
.unwrap_or_else(|| "unknown".to_string());

let mut content = String::new();
content.push_str("processor\t: 0\n");
content.push_str("vendor_id\t: unknown\n");
content.push_str("model name\t: unknown\n");
content.push_str("cpu MHz\t: unknown\n");
content.push_str("cpu cores\t: ");
content.push_str(&cores);
content.push('\n');
content.push_str("architecture\t: ");
content.push_str(architecture);
content.push_str("\n\n");
content
}

fn render_cpuinfo_text() -> String {
build_cpuinfo_text("wasm32", get_hardware_concurrency())
}

impl DirectBaseOperations for CpuInformationsDevice {
fn read(&self, buffer: &mut [u8], absolute_position: Size) -> Result<usize> {
let content = render_cpuinfo_text();
let bytes = content.as_bytes();

let start = usize::try_from(absolute_position).unwrap_or(usize::MAX);
if start >= bytes.len() {
return Ok(0);
}

let length = buffer.len().min(bytes.len() - start);
buffer[..length].copy_from_slice(&bytes[start..start + length]);

Ok(length)
}

fn write(&self, _: &[u8], _: Size) -> Result<usize> {
Err(Error::UnsupportedOperation)
}
}

impl MountOperations for CpuInformationsDevice {}

impl DirectCharacterDevice for CpuInformationsDevice {}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn formatter_uses_unknown_for_missing_values() {
let output = build_cpuinfo_text("wasm32", None);
assert!(output.contains("vendor_id\t: unknown"));
assert!(output.contains("model name\t: unknown"));
assert!(output.contains("cpu MHz\t: unknown"));
assert!(output.contains("cpu cores\t: unknown"));
assert!(output.contains("architecture\t: wasm32"));
}

#[test]
fn formatter_includes_detected_cores() {
let output = build_cpuinfo_text("wasm32", Some(8));
assert!(output.contains("cpu cores\t: 8"));
}

#[test]
fn read_uses_absolute_offset_and_handles_eof() {
let device = CpuInformationsDevice;
let mut buffer = [0u8; 24];
let first = device.read(&mut buffer, 0).unwrap();
assert!(first > 0);

let eof = device.read(&mut buffer, Size::MAX).unwrap();
assert_eq!(eof, 0);
}

#[test]
fn write_is_not_supported() {
let device = CpuInformationsDevice;
let result = device.write(b"ignored", 0);
assert!(matches!(result, Err(Error::UnsupportedOperation)));
}
}
2 changes: 2 additions & 0 deletions drivers/wasm/src/devices/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
mod cpu;
mod drive;
pub mod graphics;
mod http_client;
mod time;

pub use cpu::*;
pub use drive::*;
pub use http_client::*;
pub use time::*;
8 changes: 8 additions & 0 deletions examples/native/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ async fn main() {

// - - Create the default system hierarchy
let _ = virtual_file_system::create_default_hierarchy(virtual_file_system, task).await;
let _ = virtual_file_system
.create_directory(task, &"/devices/cpu")
.await;

log::information!("Default hierarchy created.");

Expand Down Expand Up @@ -145,6 +148,11 @@ async fn main() {
CharacterDevice,
drivers_std::devices::TimeDevice
),
(
&"/devices/cpu/informations",
CharacterDevice,
drivers_std::devices::CpuInformationsDevice
),
(
&"/devices/random",
CharacterDevice,
Expand Down
8 changes: 8 additions & 0 deletions examples/wasm/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ async fn main() {

// - - Create the default system hierarchy
let _ = virtual_file_system::create_default_hierarchy(virtual_file_system, task).await;
let _ = virtual_file_system
.create_directory(task, &"/devices/cpu")
.await;

// - - Mount the devices
virtual_file_system::clean_devices(virtual_file_system, task)
Expand Down Expand Up @@ -131,6 +134,11 @@ async fn main() {
CharacterDevice,
drivers_wasm::devices::TimeDevice
),
(
&"/devices/cpu/informations",
CharacterDevice,
drivers_wasm::devices::CpuInformationsDevice
),
(
&"/devices/random",
CharacterDevice,
Expand Down
1 change: 1 addition & 0 deletions executables/settings/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"Address": "Address",
"Apply": "Apply",
"Authentication failed: {}": "Authentication failed: {}",
"CPU:": "CPU:",
"Cancel": "Cancel",
"Change Password": "Change Password",
"Configuration Mode": "Configuration Mode",
Expand Down
1 change: 1 addition & 0 deletions executables/settings/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"Address": "Adresse",
"Apply": "Appliquer",
"Authentication failed: {}": "Échec de l'authentification: {}",
"CPU:": "Processeur :",
"Cancel": "Annuler",
"Change Password": "Changer le mot de passe",
"Configuration Mode": "Mode de configuration",
Expand Down
Loading
Loading