1#![allow(clippy::useless_format)]
8
9use alloc::{format, string::String, sync::Arc, vec::Vec};
10
11use super::{DirEntry, Filesystem, Metadata, NodeType, Permissions, VfsNode};
12use crate::error::{FsError, KernelError};
13
14enum ProcNodeType {
16 Root,
17 ProcessDir(u64),
18 ProcessFile(u64, String),
19 SystemFile(String),
20}
21
22struct ProcNode {
24 node_type: ProcNodeType,
25}
26
27impl ProcNode {
28 fn new_root() -> Self {
29 Self {
30 node_type: ProcNodeType::Root,
31 }
32 }
33
34 fn new_process_dir(pid: u64) -> Self {
35 Self {
36 node_type: ProcNodeType::ProcessDir(pid),
37 }
38 }
39
40 fn new_process_file(pid: u64, name: String) -> Self {
41 Self {
42 node_type: ProcNodeType::ProcessFile(pid, name),
43 }
44 }
45
46 fn new_system_file(name: String) -> Self {
47 Self {
48 node_type: ProcNodeType::SystemFile(name),
49 }
50 }
51}
52
53impl VfsNode for ProcNode {
54 fn node_type(&self) -> NodeType {
55 match &self.node_type {
56 ProcNodeType::Root | ProcNodeType::ProcessDir(_) => NodeType::Directory,
57 ProcNodeType::ProcessFile(_, _) | ProcNodeType::SystemFile(_) => NodeType::File,
58 }
59 }
60
61 fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<usize, KernelError> {
62 let content = match &self.node_type {
63 ProcNodeType::SystemFile(name) => {
64 match name.as_str() {
65 "version" => {
66 format!(
67 "VeridianOS version 0.5.0 (gcc 14.2.0) #1 SMP {}\n",
68 env!("CARGO_PKG_VERSION")
69 )
70 }
71 "uptime" => {
72 let secs = crate::arch::timer::get_timestamp_secs();
73 let frac_ms = crate::arch::timer::get_timestamp_ms() % 1000;
74 format!("{}.{:02} 0.00\n", secs, frac_ms / 10)
76 }
77 "meminfo" => {
78 let stats = crate::mm::get_memory_stats();
79 let total_kb = stats.total_frames * 4; let free_kb = stats.free_frames * 4;
81 let used_kb = total_kb.saturating_sub(free_kb);
82 let cached_kb = stats.cached_frames * 4;
83 let available_kb = free_kb + cached_kb;
84 let buffers_kb = 0usize;
85 let slab_kb = 0usize;
86
87 format!(
88 "MemTotal: {:>8} kB\n\
89 MemFree: {:>8} kB\n\
90 MemAvailable: {:>8} kB\n\
91 Buffers: {:>8} kB\n\
92 Cached: {:>8} kB\n\
93 Slab: {:>8} kB\n\
94 MemUsed: {:>8} kB\n",
95 total_kb,
96 free_kb,
97 available_kb,
98 buffers_kb,
99 cached_kb,
100 slab_kb,
101 used_kb,
102 )
103 }
104 "cpuinfo" => generate_cpuinfo(),
105 "loadavg" => generate_loadavg(),
106 _ => String::new(),
107 }
108 }
109 ProcNodeType::ProcessFile(pid, name) => {
110 match name.as_str() {
111 "status" => {
112 if let Some(process) =
114 crate::process::get_process(crate::process::ProcessId(*pid))
115 {
116 let state = match process.get_state() {
117 crate::process::ProcessState::Creating => "N (new)",
118 crate::process::ProcessState::Ready => "R (running)",
119 crate::process::ProcessState::Running => "R (running)",
120 crate::process::ProcessState::Blocked => "S (sleeping)",
121 crate::process::ProcessState::Sleeping => "S (sleeping)",
122 crate::process::ProcessState::Zombie => "Z (zombie)",
123 crate::process::ProcessState::Dead => "X (dead)",
124 };
125
126 #[cfg(feature = "alloc")]
127 let name = &process.name;
128 #[cfg(not(feature = "alloc"))]
129 let name = "process";
130
131 let parent = process.parent.unwrap_or(crate::process::ProcessId(0));
132
133 format!(
134 "Name:\t{}\nPid:\t{}\nPPid:\t{}\nState:\t{}\n",
135 name, pid, parent.0, state
136 )
137 } else {
138 format!("Name:\tProcess\nPid:\t{}\nState:\tR (running)\n", pid)
139 }
140 }
141 "cmdline" => {
142 if let Some(process) =
144 crate::process::get_process(crate::process::ProcessId(*pid))
145 {
146 #[cfg(feature = "alloc")]
147 let name = &process.name;
148 #[cfg(not(feature = "alloc"))]
149 let name = "process";
150 format!("{}\0", name)
151 } else {
152 format!("init\0")
153 }
154 }
155 _ => String::new(),
156 }
157 }
158 _ => return Err(KernelError::FsError(FsError::NotAFile)),
159 };
160
161 let bytes = content.as_bytes();
162 if offset >= bytes.len() {
163 return Ok(0);
164 }
165
166 let bytes_to_read = core::cmp::min(buffer.len(), bytes.len() - offset);
167 buffer[..bytes_to_read].copy_from_slice(&bytes[offset..offset + bytes_to_read]);
168 Ok(bytes_to_read)
169 }
170
171 fn write(&self, _offset: usize, _data: &[u8]) -> Result<usize, KernelError> {
172 Err(KernelError::FsError(FsError::ReadOnly))
173 }
174
175 fn metadata(&self) -> Result<Metadata, KernelError> {
176 let node_type = match &self.node_type {
177 ProcNodeType::Root | ProcNodeType::ProcessDir(_) => NodeType::Directory,
178 _ => NodeType::File,
179 };
180
181 Ok(Metadata {
182 node_type,
183 size: 0,
184 permissions: Permissions::read_only(),
185 uid: 0,
186 gid: 0,
187 created: 0,
188 modified: 0,
189 accessed: 0,
190 inode: 0,
191 })
192 }
193
194 fn readdir(&self) -> Result<Vec<DirEntry>, KernelError> {
195 let mut entries = Vec::new();
196
197 entries.push(DirEntry {
198 name: String::from("."),
199 node_type: NodeType::Directory,
200 inode: 0,
201 });
202
203 entries.push(DirEntry {
204 name: String::from(".."),
205 node_type: NodeType::Directory,
206 inode: 0,
207 });
208
209 match &self.node_type {
210 ProcNodeType::Root => {
211 entries.push(DirEntry {
213 name: String::from("version"),
214 node_type: NodeType::File,
215 inode: 0,
216 });
217
218 entries.push(DirEntry {
219 name: String::from("uptime"),
220 node_type: NodeType::File,
221 inode: 0,
222 });
223
224 entries.push(DirEntry {
225 name: String::from("meminfo"),
226 node_type: NodeType::File,
227 inode: 0,
228 });
229
230 entries.push(DirEntry {
231 name: String::from("cpuinfo"),
232 node_type: NodeType::File,
233 inode: 0,
234 });
235
236 entries.push(DirEntry {
237 name: String::from("loadavg"),
238 node_type: NodeType::File,
239 inode: 0,
240 });
241
242 if let Some(process_list) = crate::process::get_process_list() {
244 for pid in process_list {
245 entries.push(DirEntry {
246 name: format!("{}", pid),
247 node_type: NodeType::Directory,
248 inode: pid,
249 });
250 }
251 } else {
252 entries.push(DirEntry {
254 name: String::from("1"),
255 node_type: NodeType::Directory,
256 inode: 1,
257 });
258 }
259 }
260 ProcNodeType::ProcessDir(_pid) => {
261 entries.push(DirEntry {
262 name: String::from("status"),
263 node_type: NodeType::File,
264 inode: 0,
265 });
266
267 entries.push(DirEntry {
268 name: String::from("cmdline"),
269 node_type: NodeType::File,
270 inode: 0,
271 });
272 }
273 _ => return Err(KernelError::FsError(FsError::NotADirectory)),
274 }
275
276 Ok(entries)
277 }
278
279 fn lookup(&self, name: &str) -> Result<Arc<dyn VfsNode>, KernelError> {
280 match &self.node_type {
281 ProcNodeType::Root => {
282 match name {
284 "version" | "uptime" | "meminfo" | "cpuinfo" | "loadavg" => {
285 Ok(Arc::new(ProcNode::new_system_file(String::from(name)))
286 as Arc<dyn VfsNode>)
287 }
288 _ => {
289 if let Ok(pid) = name.parse::<u64>() {
291 if crate::process::get_process(crate::process::ProcessId(pid)).is_some()
293 {
294 Ok(Arc::new(ProcNode::new_process_dir(pid)) as Arc<dyn VfsNode>)
295 } else {
296 Err(KernelError::FsError(FsError::NotFound))
297 }
298 } else {
299 Err(KernelError::FsError(FsError::NotFound))
300 }
301 }
302 }
303 }
304 ProcNodeType::ProcessDir(pid) => match name {
305 "status" | "cmdline" => Ok(Arc::new(ProcNode::new_process_file(
306 *pid,
307 String::from(name),
308 )) as Arc<dyn VfsNode>),
309 _ => Err(KernelError::FsError(FsError::NotFound)),
310 },
311 _ => Err(KernelError::FsError(FsError::NotADirectory)),
312 }
313 }
314
315 fn create(
316 &self,
317 _name: &str,
318 _permissions: Permissions,
319 ) -> Result<Arc<dyn VfsNode>, KernelError> {
320 Err(KernelError::FsError(FsError::ReadOnly))
321 }
322
323 fn mkdir(
324 &self,
325 _name: &str,
326 _permissions: Permissions,
327 ) -> Result<Arc<dyn VfsNode>, KernelError> {
328 Err(KernelError::FsError(FsError::ReadOnly))
329 }
330
331 fn unlink(&self, _name: &str) -> Result<(), KernelError> {
332 Err(KernelError::FsError(FsError::ReadOnly))
333 }
334
335 fn truncate(&self, _size: usize) -> Result<(), KernelError> {
336 Err(KernelError::FsError(FsError::ReadOnly))
337 }
338}
339
340fn generate_cpuinfo() -> String {
342 #[cfg(target_arch = "x86_64")]
343 {
344 let brand = cpuid_brand_string();
345 let freq_mhz = crate::arch::timer::hw_ticks_per_second() / 1_000_000;
346
347 format!(
348 "processor\t: 0\nvendor_id\t: {}\nmodel name\t: {}\ncpu MHz\t\t: {}\ncache size\t: 0 \
349 KB\nbogomips\t: {}\n",
350 cpuid_vendor_id(),
351 brand,
352 freq_mhz,
353 freq_mhz * 2,
354 )
355 }
356
357 #[cfg(target_arch = "aarch64")]
358 {
359 let freq_mhz = crate::arch::timer::hw_ticks_per_second() / 1_000_000;
360 format!(
361 "processor\t: 0\narchitecture\t: aarch64\nmodel name\t: ARMv8 Processor\nBogoMIPS\t: \
362 {}\nFeatures\t: fp asimd\n",
363 freq_mhz * 2,
364 )
365 }
366
367 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
368 {
369 format!("processor\t: 0\narchitecture\t: riscv64\nmodel name\t: RISC-V Processor\n")
370 }
371}
372
373fn generate_loadavg() -> String {
375 let (running, total) = if let Some(pids) = crate::process::get_process_list() {
377 let total = pids.len();
378 let mut running = 0usize;
379 for pid in &pids {
380 if let Some(p) = crate::process::get_process(crate::process::ProcessId(*pid)) {
381 match p.get_state() {
382 crate::process::ProcessState::Running | crate::process::ProcessState::Ready => {
383 running += 1;
384 }
385 _ => {}
386 }
387 }
388 }
389 (running, total)
390 } else {
391 (1, 1)
392 };
393
394 let last_pid = crate::process::get_process_list()
396 .and_then(|pids| pids.iter().copied().max())
397 .unwrap_or(1);
398
399 format!("0.00 0.00 0.00 {}/{} {}\n", running, total, last_pid)
401}
402
403#[cfg(target_arch = "x86_64")]
405fn cpuid_vendor_id() -> String {
406 let ebx: u32;
407 let ecx: u32;
408 let edx: u32;
409 unsafe {
412 core::arch::asm!(
413 "push rbx",
414 "xor eax, eax",
415 "cpuid",
416 "mov {0:e}, ebx",
417 "mov {1:e}, ecx",
418 "mov {2:e}, edx",
419 "pop rbx",
420 out(reg) ebx,
421 out(reg) ecx,
422 out(reg) edx,
423 out("eax") _,
424 );
425 }
426 let mut buf = [0u8; 12];
427 buf[0..4].copy_from_slice(&ebx.to_le_bytes());
428 buf[4..8].copy_from_slice(&edx.to_le_bytes());
429 buf[8..12].copy_from_slice(&ecx.to_le_bytes());
430 String::from_utf8_lossy(&buf).trim_end_matches('\0').into()
431}
432
433#[cfg(target_arch = "x86_64")]
435fn cpuid_brand_string() -> String {
436 let max_ext: u32;
438 unsafe {
440 core::arch::asm!(
441 "push rbx",
442 "mov eax, 0x80000000",
443 "cpuid",
444 "mov {0:e}, eax",
445 "pop rbx",
446 out(reg) max_ext,
447 out("eax") _,
448 );
449 }
450
451 if max_ext < 0x80000004 {
452 return String::from("Unknown CPU");
453 }
454
455 let mut buf = [0u8; 48];
456
457 for (i, leaf) in [0x80000002u32, 0x80000003, 0x80000004].iter().enumerate() {
458 let eax: u32;
459 let ebx: u32;
460 let ecx: u32;
461 let edx: u32;
462 unsafe {
464 core::arch::asm!(
465 "push rbx",
466 "mov eax, {leaf:e}",
467 "cpuid",
468 "mov {0:e}, eax",
469 "mov {1:e}, ebx",
470 "mov {2:e}, ecx",
471 "mov {3:e}, edx",
472 "pop rbx",
473 out(reg) eax,
474 out(reg) ebx,
475 out(reg) ecx,
476 out(reg) edx,
477 leaf = in(reg) *leaf,
478 out("eax") _,
479 );
480 }
481 let off = i * 16;
482 buf[off..off + 4].copy_from_slice(&eax.to_le_bytes());
483 buf[off + 4..off + 8].copy_from_slice(&ebx.to_le_bytes());
484 buf[off + 8..off + 12].copy_from_slice(&ecx.to_le_bytes());
485 buf[off + 12..off + 16].copy_from_slice(&edx.to_le_bytes());
486 }
487
488 String::from_utf8_lossy(&buf)
489 .trim_end_matches('\0')
490 .trim()
491 .into()
492}
493
494pub struct ProcFs {
496 root: Arc<ProcNode>,
497}
498
499impl ProcFs {
500 pub fn new() -> Self {
501 Self {
502 root: Arc::new(ProcNode::new_root()),
503 }
504 }
505}
506
507impl Default for ProcFs {
508 fn default() -> Self {
509 Self::new()
510 }
511}
512
513impl Filesystem for ProcFs {
514 fn root(&self) -> Arc<dyn VfsNode> {
515 self.root.clone() as Arc<dyn VfsNode>
516 }
517
518 fn name(&self) -> &str {
519 "procfs"
520 }
521
522 fn is_readonly(&self) -> bool {
523 true
524 }
525
526 fn sync(&self) -> Result<(), KernelError> {
527 Ok(())
528 }
529}