1use std::collections::BTreeMap;
6
7#[cfg(feature = "seccomp_trace")]
8use base::debug;
9use base::Event;
10use devices::serial_device::SerialHardware;
11use devices::serial_device::SerialParameters;
12use devices::serial_device::SerialType;
13use devices::Bus;
14use devices::Serial;
15use hypervisor::ProtectionType;
16#[cfg(feature = "seccomp_trace")]
17use jail::read_jail_addr;
18#[cfg(windows)]
19use jail::FakeMinijailStub as Minijail;
20#[cfg(any(target_os = "android", target_os = "linux"))]
21use minijail::Minijail;
22use remain::sorted;
23use thiserror::Error as ThisError;
24
25use crate::DeviceRegistrationError;
26
27mod sys;
28
29pub fn set_default_serial_parameters(
38 serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
39 is_vhost_user_console_enabled: bool,
40) {
41 let default_console = (SerialHardware::Serial, 1);
44 if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
45 serial_parameters
46 .entry(default_console)
47 .or_insert(SerialParameters {
48 type_: SerialType::Stdout,
49 hardware: SerialHardware::Serial,
50 name: None,
51 path: None,
52 input: None,
53 num: 1,
54 console: true,
55 earlycon: false,
56 stdin: true,
57 out_timestamp: false,
58 ..Default::default()
59 });
60 }
61
62 for num in 1..=4 {
66 let key = (SerialHardware::Serial, num);
67 serial_parameters.entry(key).or_insert(SerialParameters {
68 type_: SerialType::Sink,
69 hardware: SerialHardware::Serial,
70 name: None,
71 path: None,
72 input: None,
73 num,
74 console: false,
75 earlycon: false,
76 stdin: false,
77 out_timestamp: false,
78 ..Default::default()
79 });
80 }
81}
82
83pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
85
86pub struct SerialDeviceInfo {
88 pub address: u64,
91
92 pub size: u64,
94
95 pub irq: u32,
97}
98
99pub fn add_serial_devices(
113 protection_type: ProtectionType,
114 io_bus: &Bus,
115 com_evt_1_3: (u32, &Event),
116 com_evt_2_4: (u32, &Event),
117 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
118 #[cfg_attr(windows, allow(unused_variables))] serial_jail: Option<Minijail>,
119 #[cfg(feature = "swap")] swap_controller: &mut Option<swap::SwapController>,
120) -> std::result::Result<Vec<SerialDeviceInfo>, DeviceRegistrationError> {
121 let mut devices = Vec::new();
122 for com_num in 0..=3 {
123 let com_evt = match com_num {
124 0 => &com_evt_1_3,
125 1 => &com_evt_2_4,
126 2 => &com_evt_1_3,
127 3 => &com_evt_2_4,
128 _ => &com_evt_1_3,
129 };
130
131 let (irq, com_evt) = (com_evt.0, com_evt.1);
132
133 let param = serial_parameters
134 .get(&(SerialHardware::Serial, com_num + 1))
135 .ok_or(DeviceRegistrationError::MissingRequiredSerialDevice(
136 com_num + 1,
137 ))?;
138
139 let mut preserved_descriptors = Vec::new();
140 let com = param
141 .create_serial_device::<Serial>(protection_type, com_evt, &mut preserved_descriptors)
142 .map_err(DeviceRegistrationError::CreateSerialDevice)?;
143
144 #[cfg(any(target_os = "android", target_os = "linux"))]
145 let serial_jail = if let Some(serial_jail) = serial_jail.as_ref() {
146 let jail_clone = serial_jail
147 .try_clone()
148 .map_err(DeviceRegistrationError::CloneJail)?;
149 #[cfg(feature = "seccomp_trace")]
150 debug!(
151 "seccomp_trace {{\"event\": \"minijail_clone\", \"src_jail_addr\": \"0x{:x}\", \"dst_jail_addr\": \"0x{:x}\"}}",
152 read_jail_addr(serial_jail),
153 read_jail_addr(&jail_clone)
154 );
155 Some(jail_clone)
156 } else {
157 None
158 };
159 #[cfg(windows)]
160 let serial_jail = None;
161
162 let com = sys::add_serial_device(
163 com,
164 param,
165 serial_jail,
166 preserved_descriptors,
167 #[cfg(feature = "swap")]
168 swap_controller,
169 )?;
170
171 let address = SERIAL_ADDR[usize::from(com_num)];
172 let size = 0x8; io_bus.insert(com, address, size).unwrap();
174 devices.push(SerialDeviceInfo { address, size, irq })
175 }
176
177 Ok(devices)
178}
179
180#[sorted]
181#[derive(ThisError, Debug)]
182pub enum GetSerialCmdlineError {
183 #[error("Error appending to cmdline: {0}")]
184 KernelCmdline(kernel_cmdline::Error),
185 #[error("Hardware {0} not supported as earlycon")]
186 UnsupportedEarlyconHardware(SerialHardware),
187}
188
189pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
190
191pub fn get_serial_cmdline(
196 cmdline: &mut kernel_cmdline::Cmdline,
197 serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
198 serial_io_type: &str,
199 serial_devices: &[SerialDeviceInfo],
200) -> GetSerialCmdlineResult<()> {
201 for serial_parameter in serial_parameters
202 .iter()
203 .filter(|(_, p)| p.console)
204 .map(|(k, _)| k)
205 {
206 match serial_parameter {
207 (SerialHardware::Serial, num) => {
208 cmdline
209 .insert("console", &format!("ttyS{}", num - 1))
210 .map_err(GetSerialCmdlineError::KernelCmdline)?;
211 }
212 (SerialHardware::VirtioConsole, num) => {
213 cmdline
214 .insert("console", &format!("hvc{}", num - 1))
215 .map_err(GetSerialCmdlineError::KernelCmdline)?;
216 }
217 (SerialHardware::Debugcon, _) => {}
218 }
219 }
220
221 match serial_parameters
222 .iter()
223 .filter(|(_, p)| p.earlycon)
224 .map(|(k, _)| k)
225 .next()
226 {
227 Some((SerialHardware::Serial, num)) => {
228 if let Some(serial_device) = serial_devices.get(*num as usize - 1) {
229 cmdline
230 .insert(
231 "earlycon",
232 &format!("uart8250,{},0x{:x}", serial_io_type, serial_device.address),
233 )
234 .map_err(GetSerialCmdlineError::KernelCmdline)?;
235 }
236 }
237 Some((hw, _num)) => {
238 return Err(GetSerialCmdlineError::UnsupportedEarlyconHardware(*hw));
239 }
240 None => {}
241 }
242
243 Ok(())
244}
245
246#[cfg(test)]
247mod tests {
248 use devices::BusType;
249 use kernel_cmdline::Cmdline;
250
251 use super::*;
252
253 #[test]
254 fn get_serial_cmdline_default() {
255 let mut cmdline = Cmdline::new();
256 let mut serial_parameters = BTreeMap::new();
257 let io_bus = Bus::new(BusType::Io);
258 let evt1_3 = Event::new().unwrap();
259 let evt2_4 = Event::new().unwrap();
260
261 set_default_serial_parameters(&mut serial_parameters, false);
262 let serial_devices = add_serial_devices(
263 ProtectionType::Unprotected,
264 &io_bus,
265 (4, &evt1_3),
266 (3, &evt2_4),
267 &serial_parameters,
268 None,
269 #[cfg(feature = "swap")]
270 &mut None,
271 )
272 .unwrap();
273 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
274 .expect("get_serial_cmdline failed");
275
276 let cmdline_str = cmdline.as_str();
277 assert!(cmdline_str.contains("console=ttyS0"));
278 }
279
280 #[test]
281 fn get_serial_cmdline_virtio_console() {
282 let mut cmdline = Cmdline::new();
283 let mut serial_parameters = BTreeMap::new();
284 let io_bus = Bus::new(BusType::Io);
285 let evt1_3 = Event::new().unwrap();
286 let evt2_4 = Event::new().unwrap();
287
288 serial_parameters.insert(
290 (SerialHardware::VirtioConsole, 1),
291 SerialParameters {
292 type_: SerialType::Stdout,
293 hardware: SerialHardware::VirtioConsole,
294 num: 1,
295 console: true,
296 stdin: true,
297 ..Default::default()
298 },
299 );
300
301 set_default_serial_parameters(&mut serial_parameters, false);
302 let serial_devices = add_serial_devices(
303 ProtectionType::Unprotected,
304 &io_bus,
305 (4, &evt1_3),
306 (3, &evt2_4),
307 &serial_parameters,
308 None,
309 #[cfg(feature = "swap")]
310 &mut None,
311 )
312 .unwrap();
313 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
314 .expect("get_serial_cmdline failed");
315
316 let cmdline_str = cmdline.as_str();
317 assert!(cmdline_str.contains("console=hvc0"));
318 }
319
320 #[test]
321 fn get_serial_cmdline_virtio_console_serial_earlycon() {
322 let mut cmdline = Cmdline::new();
323 let mut serial_parameters = BTreeMap::new();
324 let io_bus = Bus::new(BusType::Io);
325 let evt1_3 = Event::new().unwrap();
326 let evt2_4 = Event::new().unwrap();
327
328 serial_parameters.insert(
330 (SerialHardware::VirtioConsole, 1),
331 SerialParameters {
332 type_: SerialType::Stdout,
333 hardware: SerialHardware::VirtioConsole,
334 num: 1,
335 console: true,
336 stdin: true,
337 ..Default::default()
338 },
339 );
340
341 serial_parameters.insert(
343 (SerialHardware::Serial, 1),
344 SerialParameters {
345 type_: SerialType::Stdout,
346 hardware: SerialHardware::Serial,
347 num: 1,
348 earlycon: true,
349 ..Default::default()
350 },
351 );
352
353 set_default_serial_parameters(&mut serial_parameters, false);
354 let serial_devices = add_serial_devices(
355 ProtectionType::Unprotected,
356 &io_bus,
357 (4, &evt1_3),
358 (3, &evt2_4),
359 &serial_parameters,
360 None,
361 #[cfg(feature = "swap")]
362 &mut None,
363 )
364 .unwrap();
365 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
366 .expect("get_serial_cmdline failed");
367
368 let cmdline_str = cmdline.as_str();
369 assert!(cmdline_str.contains("console=hvc0"));
370 assert!(cmdline_str.contains("earlycon=uart8250,io,0x3f8"));
371 }
372
373 #[test]
374 fn get_serial_cmdline_virtio_console_invalid_earlycon() {
375 let mut cmdline = Cmdline::new();
376 let mut serial_parameters = BTreeMap::new();
377 let io_bus = Bus::new(BusType::Io);
378 let evt1_3 = Event::new().unwrap();
379 let evt2_4 = Event::new().unwrap();
380
381 serial_parameters.insert(
383 (SerialHardware::VirtioConsole, 1),
384 SerialParameters {
385 type_: SerialType::Stdout,
386 hardware: SerialHardware::VirtioConsole,
387 num: 1,
388 earlycon: true,
389 stdin: true,
390 ..Default::default()
391 },
392 );
393
394 set_default_serial_parameters(&mut serial_parameters, false);
395 let serial_devices = add_serial_devices(
396 ProtectionType::Unprotected,
397 &io_bus,
398 (4, &evt1_3),
399 (3, &evt2_4),
400 &serial_parameters,
401 None,
402 #[cfg(feature = "swap")]
403 &mut None,
404 )
405 .unwrap();
406 get_serial_cmdline(&mut cmdline, &serial_parameters, "io", &serial_devices)
407 .expect_err("get_serial_cmdline succeeded");
408 }
409}