hermit/
env.rs

1//! Central parsing of the command-line parameters.
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use core::{ptr, str};
6
7use ahash::RandomState;
8use fdt::Fdt;
9use hashbrown::HashMap;
10use hashbrown::hash_map::Iter;
11use hermit_entry::boot_info::{BootInfo, PlatformInfo, RawBootInfo};
12use hermit_sync::OnceCell;
13
14pub(crate) use crate::arch::kernel::{self, get_base_address, get_image_size, get_ram_address};
15
16static BOOT_INFO: OnceCell<BootInfo> = OnceCell::new();
17
18pub fn boot_info() -> &'static BootInfo {
19	BOOT_INFO.get().unwrap()
20}
21
22pub fn set_boot_info(raw_boot_info: RawBootInfo) {
23	let boot_info = BootInfo::from(raw_boot_info);
24	BOOT_INFO.set(boot_info).unwrap();
25}
26
27static CLI: OnceCell<Cli> = OnceCell::new();
28
29pub fn init() {
30	CLI.set(Cli::default()).unwrap();
31}
32
33#[derive(Debug)]
34struct Cli {
35	#[allow(dead_code)]
36	image_path: Option<String>,
37	#[cfg(not(target_arch = "riscv64"))]
38	freq: Option<u16>,
39	env_vars: HashMap<String, String, RandomState>,
40	args: Vec<String>,
41	#[allow(dead_code)]
42	mmio: Vec<String>,
43}
44
45/// Whether Hermit is running under the "uhyve" hypervisor.
46pub fn is_uhyve() -> bool {
47	matches!(boot_info().platform_info, PlatformInfo::Uhyve { .. })
48}
49
50pub fn is_uefi() -> bool {
51	fdt().is_some_and(|fdt| fdt.root().compatible().first() == "hermit,uefi")
52}
53
54pub fn fdt() -> Option<Fdt<'static>> {
55	boot_info().hardware_info.device_tree.map(|fdt| {
56		let ptr = ptr::with_exposed_provenance(fdt.get().try_into().unwrap());
57		unsafe { Fdt::from_ptr(ptr).unwrap() }
58	})
59}
60
61/// Returns the RSDP physical address if available.
62#[cfg(all(target_arch = "x86_64", feature = "acpi"))]
63pub fn rsdp() -> Option<core::num::NonZero<usize>> {
64	let rsdp = fdt()?
65		.find_node("/hermit,rsdp")?
66		.reg()?
67		.next()?
68		.starting_address
69		.addr();
70	core::num::NonZero::new(rsdp)
71}
72
73pub fn fdt_args() -> Option<&'static str> {
74	fdt().and_then(|fdt| fdt.chosen().bootargs())
75}
76
77impl Default for Cli {
78	fn default() -> Self {
79		let mut image_path = None;
80		#[cfg(not(target_arch = "riscv64"))]
81		let mut freq = None;
82		let mut env_vars = HashMap::<String, String, RandomState>::with_hasher(
83			RandomState::with_seeds(0, 0, 0, 0),
84		);
85
86		let args = kernel::args().or_else(fdt_args).unwrap_or_default();
87		info!("bootargs = {args}");
88		let words = shell_words::split(args).unwrap();
89
90		let mut words = words.into_iter();
91		let expect_arg = |arg: Option<String>, name: &str| {
92			arg.unwrap_or_else(|| {
93				panic!("The argument '{name}' requires a value but none was supplied")
94			})
95		};
96
97		let mut args = Vec::new();
98		let mut mmio = Vec::new();
99		while let Some(word) = words.next() {
100			if word.as_str().starts_with("virtio_mmio.device=") {
101				let v: Vec<&str> = word.as_str().split('=').collect();
102				mmio.push(v[1].to_string());
103				continue;
104			}
105
106			match word.as_str() {
107				#[cfg(not(target_arch = "riscv64"))]
108				"-freq" => {
109					let s = expect_arg(words.next(), word.as_str());
110					freq = Some(s.parse().unwrap());
111				}
112				"-ip" => {
113					let ip = expect_arg(words.next(), word.as_str());
114					env_vars.insert(String::from("HERMIT_IP"), ip);
115				}
116				"-mask" => {
117					let mask = expect_arg(words.next(), word.as_str());
118					env_vars.insert(String::from("HERMIT_MASK"), mask);
119				}
120				"-gateway" => {
121					let gateway = expect_arg(words.next(), word.as_str());
122					env_vars.insert(String::from("HERMIT_GATEWAY"), gateway);
123				}
124				"-mount" => {
125					let gateway = expect_arg(words.next(), word.as_str());
126					env_vars.insert(String::from("UHYVE_MOUNT"), gateway);
127				}
128				"--" => args.extend(&mut words),
129				word if word.contains('=') => {
130					let (arg, value) = word.split_once('=').unwrap();
131
132					match arg {
133						"env" => {
134							let Some((key, value)) = value.split_once('=') else {
135								error!("could not parse bootarg: {word}");
136								continue;
137							};
138							env_vars.insert(key.to_string(), value.to_string());
139						}
140						_ => error!("could not parse bootarg: {word}"),
141					}
142				}
143				_ if image_path.is_none() => image_path = Some(word),
144				word => error!("could not parse bootarg: {word}"),
145			};
146		}
147
148		Self {
149			image_path,
150			#[cfg(not(target_arch = "riscv64"))]
151			freq,
152			env_vars,
153			args,
154			#[allow(dead_code)]
155			mmio,
156		}
157	}
158}
159
160/// CPU Frequency in MHz if given through the -freq command-line parameter.
161#[cfg(not(target_arch = "riscv64"))]
162pub fn freq() -> Option<u16> {
163	CLI.get().unwrap().freq
164}
165
166#[allow(dead_code)]
167pub fn var(key: &str) -> Option<&String> {
168	CLI.get().unwrap().env_vars.get(key)
169}
170
171pub fn vars() -> Iter<'static, String, String> {
172	CLI.get().unwrap().env_vars.iter()
173}
174
175/// Returns the cmdline argument passed in after "--"
176pub fn args() -> &'static [String] {
177	CLI.get().unwrap().args.as_slice()
178}
179
180/// Returns the configuration of all mmio devices
181#[allow(dead_code)]
182pub fn mmio() -> &'static [String] {
183	CLI.get().unwrap().mmio.as_slice()
184}