Skip to content

Commit a9f1731

Browse files
committed
virtio-console: Initial multiport protocol support
This makes the device act as a multiport console. This doesn't introduce new functionality, the number of ports remains fixed at 1 port (for the terminal with stdin/stdout). Signed-off-by: Matej Hrica <[email protected]>
1 parent e42dc79 commit a9f1731

File tree

4 files changed

+327
-74
lines changed

4 files changed

+327
-74
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use std::collections::VecDeque;
2+
use std::ops::Deref;
3+
use std::sync::{Arc, Mutex};
4+
5+
use libc::EFD_NONBLOCK;
6+
use utils::eventfd::EventFd;
7+
use vm_memory::{ByteValued, GuestMemoryMmap};
8+
9+
use crate::virtio::console::defs::control_event::{
10+
VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_RESIZE,
11+
};
12+
13+
#[derive(Copy, Clone, Debug, Default)]
14+
#[repr(C, packed(4))]
15+
pub struct VirtioConsoleControl {
16+
/// Port number
17+
pub id: u32,
18+
/// The kind of control event
19+
pub event: u16,
20+
/// Extra information for the event
21+
pub value: u16,
22+
}
23+
24+
// Safe because it only has data and has no implicit padding.
25+
// But NOTE that this relies on CPU being little endian, to have correct semantics
26+
unsafe impl ByteValued for VirtioConsoleControl {}
27+
28+
#[derive(Copy, Clone, Debug, Default)]
29+
#[repr(C, packed)]
30+
pub struct VirtioConsoleResize {
31+
// NOTE: the order of these fields in the actual kernel implementation and in the spec are swapped,
32+
// we follow the order in the kernel to get it working correctly
33+
pub rows: u16,
34+
pub cols: u16,
35+
}
36+
37+
// Safe because it only has data and has no implicit padding.
38+
// but NOTE, that we rely on CPU being little endian, for the values to be correct
39+
unsafe impl ByteValued for VirtioConsoleResize {}
40+
41+
pub enum Payload {
42+
ConsoleControl(VirtioConsoleControl),
43+
Bytes(Vec<u8>),
44+
}
45+
46+
impl Deref for Payload {
47+
type Target = [u8];
48+
49+
fn deref(&self) -> &Self::Target {
50+
match self {
51+
Payload::ConsoleControl(b) => b.as_slice(),
52+
Payload::Bytes(b) => b.as_slice(),
53+
}
54+
}
55+
}
56+
57+
// Utility for sending commands into control rx queue
58+
pub struct ConsoleControl {
59+
queue: Mutex<VecDeque<Payload>>,
60+
queue_evt: EventFd,
61+
}
62+
63+
impl ConsoleControl {
64+
pub fn new() -> Arc<Self> {
65+
Arc::new(Self {
66+
queue: Default::default(),
67+
queue_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
68+
})
69+
}
70+
71+
pub fn mark_console_port(&self, _mem: &GuestMemoryMmap, port_id: u32) {
72+
self.push_msg(VirtioConsoleControl {
73+
id: port_id,
74+
event: VIRTIO_CONSOLE_CONSOLE_PORT,
75+
value: 1,
76+
})
77+
}
78+
79+
pub fn console_resize(&self, port_id: u32, new_size: VirtioConsoleResize) {
80+
let mut buf = Vec::new();
81+
buf.extend(
82+
VirtioConsoleControl {
83+
id: port_id,
84+
event: VIRTIO_CONSOLE_RESIZE,
85+
value: 0,
86+
}
87+
.as_slice(),
88+
);
89+
buf.extend(new_size.as_slice());
90+
self.push_vec(buf)
91+
}
92+
93+
/// Adds another port with the specified port_id
94+
pub fn port_add(&self, port_id: u32) {
95+
self.push_msg(VirtioConsoleControl {
96+
id: port_id,
97+
event: VIRTIO_CONSOLE_PORT_ADD,
98+
value: 0,
99+
})
100+
}
101+
102+
pub fn queue_pop(&self) -> Option<Payload> {
103+
let mut queue = self.queue.lock().expect("Poisoned lock");
104+
queue.pop_front()
105+
}
106+
107+
pub fn queue_evt(&self) -> &EventFd {
108+
&self.queue_evt
109+
}
110+
111+
fn push_msg(&self, msg: VirtioConsoleControl) {
112+
let mut queue = self.queue.lock().expect("Poisoned lock");
113+
queue.push_back(Payload::ConsoleControl(msg));
114+
if let Err(e) = self.queue_evt.write(1) {
115+
log::trace!("ConsoleControl failed to write to notify {e}")
116+
}
117+
}
118+
119+
fn push_vec(&self, buf: Vec<u8>) {
120+
let mut queue = self.queue.lock().expect("Poisoned lock");
121+
queue.push_back(Payload::Bytes(buf));
122+
if let Err(e) = self.queue_evt.write(1) {
123+
log::trace!("ConsoleControl failed to write to notify {e}")
124+
}
125+
}
126+
}

src/devices/src/virtio/console/device.rs

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::cmp;
22
use std::collections::VecDeque;
33
use std::io;
44
use std::io::Write;
5+
use std::mem::{size_of, size_of_val};
56
use std::os::unix::io::{AsRawFd, RawFd};
67
use std::result;
78
use std::sync::atomic::{AtomicUsize, Ordering};
@@ -16,14 +17,23 @@ use super::super::{
1617
ActivateError, ActivateResult, ConsoleError, DeviceState, Queue as VirtQueue, VirtioDevice,
1718
VIRTIO_MMIO_INT_CONFIG, VIRTIO_MMIO_INT_VRING,
1819
};
19-
use super::{defs, defs::uapi};
20+
use super::{defs, defs::control_event, defs::uapi};
2021
use crate::legacy::Gic;
22+
use crate::virtio::console::console_control::{
23+
ConsoleControl, VirtioConsoleControl, VirtioConsoleResize,
24+
};
25+
use crate::virtio::console::defs::NUM_PORTS;
2126
use crate::Error as DeviceError;
2227

2328
pub(crate) const RXQ_INDEX: usize = 0;
2429
pub(crate) const TXQ_INDEX: usize = 1;
25-
pub(crate) const AVAIL_FEATURES: u64 =
26-
1 << uapi::VIRTIO_CONSOLE_F_SIZE as u64 | 1 << uapi::VIRTIO_F_VERSION_1 as u64;
30+
31+
pub(crate) const CONTROL_RXQ_INDEX: usize = 2;
32+
pub(crate) const CONTROL_TXQ_INDEX: usize = 3;
33+
34+
pub(crate) const AVAIL_FEATURES: u64 = 1 << uapi::VIRTIO_CONSOLE_F_SIZE as u64
35+
| 1 << uapi::VIRTIO_CONSOLE_F_MULTIPORT as u64
36+
| 1 << uapi::VIRTIO_F_VERSION_1 as u64;
2737

2838
pub(crate) fn get_win_size() -> (u16, u16) {
2939
#[repr(C)]
@@ -60,20 +70,16 @@ impl VirtioConsoleConfig {
6070
VirtioConsoleConfig {
6171
cols,
6272
rows,
63-
max_nr_ports: 1u32,
73+
max_nr_ports: NUM_PORTS as u32,
6474
emerg_wr: 0u32,
6575
}
6676
}
67-
68-
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
69-
self.cols = cols;
70-
self.rows = rows;
71-
}
7277
}
7378

7479
pub struct Console {
7580
pub(crate) queues: Vec<VirtQueue>,
7681
pub(crate) queue_events: Vec<EventFd>,
82+
pub(crate) control: Arc<ConsoleControl>,
7783
pub(crate) avail_features: u64,
7884
pub(crate) acked_features: u64,
7985
pub(crate) interrupt_status: Arc<AtomicUsize>,
@@ -107,6 +113,7 @@ impl Console {
107113
let config = VirtioConsoleConfig::new(cols, rows);
108114

109115
Ok(Console {
116+
control: ConsoleControl::new(),
110117
queues,
111118
queue_events,
112119
avail_features: AVAIL_FEATURES,
@@ -185,9 +192,110 @@ impl Console {
185192
}
186193

187194
pub fn update_console_size(&mut self, cols: u16, rows: u16) {
188-
debug!("update_console_size: {} {}", cols, rows);
189-
self.config.update_console_size(cols, rows);
190-
self.signal_config_update().unwrap();
195+
log::debug!("update_console_size: {} {}", cols, rows);
196+
// Note that we currently only support resizing on the first/main console
197+
self.control
198+
.console_resize(0, VirtioConsoleResize { rows, cols });
199+
}
200+
201+
pub(crate) fn process_control_rx(&mut self) -> bool {
202+
log::trace!("process_control_rx");
203+
let DeviceState::Activated(ref mem) = self.device_state else {
204+
unreachable!()
205+
};
206+
let mut raise_irq = false;
207+
208+
while let Some(head) = self.queues[CONTROL_RXQ_INDEX].pop(mem) {
209+
if let Some(buf) = self.control.queue_pop() {
210+
match mem.write(&buf, head.addr) {
211+
Ok(n) => {
212+
if n != buf.len() {
213+
log::error!("process_control_rx: partial write");
214+
}
215+
raise_irq = true;
216+
log::trace!("process_control_rx wrote {n}");
217+
self.queues[CONTROL_RXQ_INDEX].add_used(mem, head.index, n as u32);
218+
}
219+
Err(e) => {
220+
log::error!("process_control_rx failed to write: {e}");
221+
}
222+
}
223+
} else {
224+
self.queues[CONTROL_RXQ_INDEX].undo_pop();
225+
break;
226+
}
227+
}
228+
raise_irq
229+
}
230+
231+
pub(crate) fn process_control_tx(&mut self) -> bool {
232+
let mem = match self.device_state {
233+
DeviceState::Activated(ref mem) => mem,
234+
// This should never happen, it's been already validated in the event handler.
235+
DeviceState::Inactive => unreachable!(),
236+
};
237+
238+
let tx_queue = &mut self.queues[CONTROL_TXQ_INDEX];
239+
let mut raise_irq = false;
240+
241+
while let Some(head) = tx_queue.pop(mem) {
242+
raise_irq = true;
243+
244+
let cmd: VirtioConsoleControl = match mem.read_obj(head.addr) {
245+
Ok(cmd) => cmd,
246+
Err(e) => {
247+
log::error!(
248+
"Failed to read VirtioConsoleControl struct: {e:?}, struct len = {len}, head.len = {head_len}",
249+
len = size_of::<VirtioConsoleControl>(),
250+
head_len = head.len,
251+
);
252+
continue;
253+
}
254+
};
255+
tx_queue.add_used(mem, head.index, size_of_val(&cmd) as u32);
256+
257+
log::trace!("VirtioConsoleControl cmd: {cmd:?}");
258+
match cmd.event {
259+
control_event::VIRTIO_CONSOLE_DEVICE_READY => {
260+
log::debug!(
261+
"Device is ready: initialization {}",
262+
if cmd.value == 1 { "ok" } else { "failed" }
263+
);
264+
self.control.port_add(0);
265+
}
266+
control_event::VIRTIO_CONSOLE_PORT_READY => {
267+
if cmd.value != 1 {
268+
log::error!("Port initialization failed: {:?}", cmd);
269+
continue;
270+
}
271+
if cmd.id == 0 {
272+
self.control.mark_console_port(mem, 0);
273+
}
274+
}
275+
control_event::VIRTIO_CONSOLE_PORT_OPEN => {
276+
let opened = match cmd.value {
277+
0 => false,
278+
1 => true,
279+
_ => {
280+
log::error!(
281+
"Invalid value ({}) for VIRTIO_CONSOLE_PORT_OPEN on port {}",
282+
cmd.value,
283+
cmd.id
284+
);
285+
continue;
286+
}
287+
};
288+
289+
if !opened {
290+
log::debug!("Guest closed port {}", cmd.id);
291+
continue;
292+
}
293+
}
294+
_ => log::warn!("Unknown console control event {:x}", cmd.event),
295+
}
296+
}
297+
298+
raise_irq
191299
}
192300

193301
pub(crate) fn process_rx(&mut self) -> bool {

0 commit comments

Comments
 (0)