kernel/
cmdline.rs

1use parser::{CmdlineParse, ParseError, ParseErrorKind, Result};
2use tokenizer::Tokenizer;
3use tracing::{error, info};
4
5use crate::{multiboot2::MultiBoot2Info, sync::once::OnceLock};
6
7mod macros;
8mod parser;
9mod tokenizer;
10
11static CMDLINE: OnceLock<Cmd> = OnceLock::new();
12
13fn parse_cmdline(inp: &str) -> Result<'_, Cmd<'_>> {
14    let mut tokenizer = Tokenizer::new(inp);
15    Cmd::parse_cmdline(&mut tokenizer)
16}
17
18const fn default_cmdline() -> Cmd<'static> {
19    Cmd {
20        uart: true,
21        uart_baud: 115200,
22        max_log_level: LogLevel::Info,
23        log_file: "/kernel.log",
24        allow_hpet: true,
25        log_aml: LogAml::Off,
26    }
27}
28
29pub fn init(multiboot_info: &'static MultiBoot2Info) {
30    let cmdline = multiboot_info
31        .cmdline()
32        .and_then(|cmdline| {
33            // we will print the result later if there is an error
34            parse_cmdline(cmdline).ok()
35        })
36        .unwrap_or(default_cmdline());
37
38    CMDLINE.set(cmdline).expect("Should only be called once");
39}
40
41/// This is extra work, but it's done purely for debugging purposes
42pub fn print_cmdline_parse(multiboot_info: &MultiBoot2Info) {
43    if let Some(cmdline) = multiboot_info.cmdline() {
44        let parsed = parse_cmdline(cmdline);
45        info!("Command line: {cmdline:?}");
46        match parsed {
47            Ok(parsed) => info!("Parsed command line: {parsed:?}"),
48            Err(e) => error!("Failed to parse command line: {e:?}"),
49        }
50    };
51}
52
53pub fn cmdline() -> &'static Cmd<'static> {
54    // if we didn't initialize, we will use the default (applies for `test`)
55    CMDLINE.get_or_init(default_cmdline)
56}
57
58#[macro_rules_attribute::apply(macros::cmdline_struct!)]
59#[derive(Debug)]
60pub struct Cmd<'a> {
61    /// Enable the UART
62    #[default = true]
63    pub uart: bool,
64    /// UART baudrate
65    #[default = 115200]
66    pub uart_baud: u32,
67    /// Log level
68    #[default = LogLevel::Info]
69    pub max_log_level: LogLevel,
70    /// Log file
71    #[default = "/kernel.log"]
72    pub log_file: &'a str,
73    /// Allow `HPET` (if present), otherwise always use `PIT`
74    #[default = true]
75    pub allow_hpet: bool,
76    /// Log the AML content as ASL code on boot from ACPI tables
77    #[default = LogAml::Off]
78    pub log_aml: LogAml,
79}
80
81#[derive(Default, Debug, Clone, Copy)]
82pub enum LogLevel {
83    Trace,
84    Debug,
85    #[default]
86    Info,
87    Warn,
88    Error,
89}
90
91impl From<LogLevel> for tracing::Level {
92    fn from(val: LogLevel) -> Self {
93        match val {
94            LogLevel::Trace => tracing::Level::TRACE,
95            LogLevel::Debug => tracing::Level::DEBUG,
96            LogLevel::Info => tracing::Level::INFO,
97            LogLevel::Warn => tracing::Level::WARN,
98            LogLevel::Error => tracing::Level::ERROR,
99        }
100    }
101}
102
103impl<'a> CmdlineParse<'a> for LogLevel {
104    fn parse_cmdline(tokenizer: &mut Tokenizer<'a>) -> Result<'a, Self> {
105        let (loc, value) = tokenizer.next_value().ok_or_else(|| {
106            ParseError::new(
107                ParseErrorKind::Unexpected {
108                    need: "trace/debug/info/warn/error",
109                    got: None,
110                },
111                tokenizer.current_index(),
112            )
113        })?;
114
115        match value {
116            "trace" => Ok(Self::Trace),
117            "debug" => Ok(Self::Debug),
118            "info" => Ok(Self::Info),
119            "warn" => Ok(Self::Warn),
120            "error" => Ok(Self::Error),
121            _ => Err(ParseError::new(
122                ParseErrorKind::Unexpected {
123                    need: "trace/debug/info/warn/error",
124                    got: Some(value),
125                },
126                loc,
127            )),
128        }
129    }
130}
131
132#[derive(Default, Debug, Clone, Copy)]
133pub enum LogAml {
134    /// Do not print the ASL content
135    #[default]
136    Off,
137    /// Print the ASL content as parsed, without moving anything
138    Normal,
139    /// Reorganize the content of the ASL code to be in an easier structure
140    /// to work with and traverse
141    Structured,
142}
143
144impl<'a> CmdlineParse<'a> for LogAml {
145    fn parse_cmdline(tokenizer: &mut Tokenizer<'a>) -> Result<'a, Self> {
146        let (loc, value) = tokenizer.next_value().ok_or_else(|| {
147            ParseError::new(
148                ParseErrorKind::Unexpected {
149                    need: "off/normal/structured",
150                    got: None,
151                },
152                tokenizer.current_index(),
153            )
154        })?;
155
156        match value {
157            "off" => Ok(Self::Off),
158            "normal" => Ok(Self::Normal),
159            "structured" => Ok(Self::Structured),
160            _ => Err(ParseError::new(
161                ParseErrorKind::Unexpected {
162                    need: "off/normal/structured",
163                    got: Some(value),
164                },
165                loc,
166            )),
167        }
168    }
169}