hermit/
env.rs

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