Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/arch/src/serial.rs
5394 views
1
// Copyright 2020 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
use std::collections::BTreeMap;
6
7
#[cfg(feature = "seccomp_trace")]
8
use base::debug;
9
use base::Event;
10
use devices::serial_device::SerialHardware;
11
use devices::serial_device::SerialParameters;
12
use devices::serial_device::SerialType;
13
use devices::Bus;
14
use devices::Serial;
15
use hypervisor::ProtectionType;
16
#[cfg(feature = "seccomp_trace")]
17
use jail::read_jail_addr;
18
#[cfg(windows)]
19
use jail::FakeMinijailStub as Minijail;
20
#[cfg(any(target_os = "android", target_os = "linux"))]
21
use minijail::Minijail;
22
use remain::sorted;
23
use thiserror::Error as ThisError;
24
25
use crate::DeviceRegistrationError;
26
27
mod sys;
28
29
/// Add the default serial parameters for serial ports that have not already been specified.
30
///
31
/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
32
/// serial ports (COM1-COM4).
33
///
34
/// It also sets the first `SerialHardware::Serial` to be the default console device if no other
35
/// serial parameters exist with console=true and the first serial device has not already been
36
/// configured explicitly.
37
pub fn set_default_serial_parameters(
38
serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
39
is_vhost_user_console_enabled: bool,
40
) {
41
// If no console device exists and the first serial port has not been specified,
42
// set the first serial port as a stdout+stdin console.
43
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
// Ensure all four of the COM ports exist.
63
// If one of these four SerialHardware::Serial port was not configured by the user,
64
// set it up as a sink.
65
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
83
/// Address for Serial ports in x86
84
pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
85
86
/// Information about a serial device (16550-style UART) created by `add_serial_devices()`.
87
pub struct SerialDeviceInfo {
88
/// Address of the device on the bus.
89
/// This is the I/O bus on x86 machines and MMIO otherwise.
90
pub address: u64,
91
92
/// Size of the device's address space on the bus.
93
pub size: u64,
94
95
/// IRQ number of the device.
96
pub irq: u32,
97
}
98
99
/// Adds serial devices to the provided bus based on the serial parameters given.
100
///
101
/// Only devices with hardware type `SerialHardware::Serial` are added by this function.
102
///
103
/// # Arguments
104
///
105
/// * `protection_type` - VM protection mode.
106
/// * `io_bus` - Bus to add the devices to
107
/// * `com_evt_1_3` - irq and event for com1 and com3
108
/// * `com_evt_1_4` - irq and event for com2 and com4
109
/// * `serial_parameters` - definitions of serial parameter configurations.
110
/// * `serial_jail` - minijail object cloned for use with each serial device. All four of the
111
/// traditional PC-style serial ports (COM1-COM4) must be specified.
112
pub 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; // 16550 UART uses 8 bytes of address space.
173
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)]
182
pub 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
189
pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
190
191
/// Add serial options to the provided `cmdline` based on `serial_parameters`.
192
/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
193
/// or "mmio" if the serial ports are memory mapped.
194
// TODO(b/227407433): Support cases where vhost-user console is specified.
195
pub 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)]
247
mod 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
// Add a virtio-console device with console=true.
289
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
// Add a virtio-console device with console=true.
329
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
// Override the default COM1 with an earlycon device.
342
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
// Try to add a virtio-console device with earlycon=true (unsupported).
382
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
}
410
411