Skip to content

Commit 12c8689

Browse files
committed
virtio-console: Implement redirection for stdin/stdout/stderr
TODO: implement stderr here too! This is done by creating a port for each of stdin/stdout/stderr, Each of these ports is multidirectional but we only use one direction. Port 0 must always be a console - this is implied by the specification. Signed-off-by: Matej Hrica <[email protected]>
1 parent 4373a0c commit 12c8689

File tree

8 files changed

+304
-42
lines changed

8 files changed

+304
-42
lines changed

init/init.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,68 @@ void clock_worker()
756756
}
757757
#endif
758758

759+
int reopen_fd(int fd, char *path, int flags)
760+
{
761+
int newfd = open(path,flags);
762+
if (newfd < 0) {
763+
printf("Failed to open '%s': %s\n", path,strerror(errno));
764+
return -1;
765+
}
766+
767+
close(fd);
768+
if (dup2(newfd, fd) < 0) {
769+
perror("dup2");
770+
close(newfd);
771+
return -1;
772+
}
773+
close(newfd);
774+
return 0;
775+
}
776+
777+
int setup_redirects()
778+
{
779+
DIR *ports_dir = opendir("/sys/class/virtio-ports");
780+
if (ports_dir == NULL) {
781+
printf("Unable to open ports directory!\n");
782+
return -4;
783+
}
784+
785+
char path[2048];
786+
char name_buf[1024];
787+
788+
struct dirent *entry = NULL;
789+
while ((entry=readdir(ports_dir))) {
790+
char* port_identifier = entry->d_name;
791+
int result_len = snprintf(path, sizeof(path), "/sys/class/virtio-ports/%s/name", port_identifier);
792+
793+
// result was truncated
794+
if (result_len > sizeof(name_buf) - 1) {
795+
printf("Path buffer too small");
796+
return -1;
797+
}
798+
799+
FILE *port_name_file = fopen(path, "r");
800+
if (port_name_file == NULL) {
801+
continue;
802+
}
803+
804+
char *port_name = fgets(name_buf, sizeof(name_buf), port_name_file);
805+
fclose(port_name_file);
806+
807+
if (port_name != NULL && strcmp(port_name, "krun-stdin\n") == 0) {
808+
// if previous snprintf didn't fail, this one cannot fail either
809+
snprintf(path, sizeof(path), "/dev/%s", port_identifier);
810+
reopen_fd(STDIN_FILENO, path, O_RDONLY);
811+
}else if (port_name != NULL && strcmp(port_name, "krun-stdout\n") == 0) {
812+
snprintf(path, sizeof(path), "/dev/%s", port_identifier);
813+
reopen_fd(STDOUT_FILENO, path, O_WRONLY);
814+
}
815+
}
816+
817+
closedir(ports_dir);
818+
return 0;
819+
}
820+
759821
int main(int argc, char **argv)
760822
{
761823
struct ifreq ifr;
@@ -848,6 +910,10 @@ int main(int argc, char **argv)
848910
}
849911
#endif
850912

913+
if (setup_redirects() < 0) {
914+
exit(-4);
915+
}
916+
851917
if (execvp(exec_argv[0], exec_argv) < 0) {
852918
printf("Couldn't execute '%s' inside the vm: %s\n", exec_argv[0], strerror(errno));
853919
exit(-3);

src/devices/src/virtio/console/console_control.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use utils::eventfd::EventFd;
77
use vm_memory::{ByteValued, GuestMemoryMmap};
88

99
use crate::virtio::console::defs::control_event::{
10-
VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_PORT_OPEN,
11-
VIRTIO_CONSOLE_RESIZE,
10+
VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_PORT_NAME,
11+
VIRTIO_CONSOLE_PORT_OPEN, VIRTIO_CONSOLE_RESIZE,
1212
};
1313

1414
#[derive(Copy, Clone, Debug, Default)]
@@ -108,6 +108,23 @@ impl ConsoleControl {
108108
})
109109
}
110110

111+
pub fn port_name(&self, port_id: u32, name: &str) {
112+
let mut buf: Vec<u8> = Vec::new();
113+
114+
buf.extend_from_slice(
115+
VirtioConsoleControl {
116+
id: port_id,
117+
event: VIRTIO_CONSOLE_PORT_NAME,
118+
value: 1, // Unspecified/unused in the spec, lets use the same value as QEMU.
119+
}
120+
.as_slice(),
121+
);
122+
123+
// The spec says the name shouldn't be NUL terminated.
124+
buf.extend(name.as_bytes());
125+
self.push_vec(buf)
126+
}
127+
111128
pub fn queue_pop(&self) -> Option<Payload> {
112129
let mut queue = self.queue.lock().expect("Poisoned lock");
113130
queue.pop_front()

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

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::cmp;
22
use std::io::Write;
3+
use std::iter::zip;
34
use std::mem::{size_of, size_of_val};
45
use std::os::unix::io::{AsRawFd, RawFd};
56
use std::sync::atomic::AtomicUsize;
@@ -17,13 +18,14 @@ use crate::legacy::Gic;
1718
use crate::virtio::console::console_control::{
1819
ConsoleControl, VirtioConsoleControl, VirtioConsoleResize,
1920
};
20-
use crate::virtio::console::defs::NUM_PORTS;
21+
use crate::virtio::console::defs::QUEUE_SIZE;
2122
use crate::virtio::console::irq_signaler::IRQSignaler;
2223
use crate::virtio::console::port::Port;
24+
use crate::virtio::console::port_queue_mapping::{
25+
num_queues, port_id_to_queue_idx, QueueDirection,
26+
};
2327
use crate::virtio::PortDescription;
2428

25-
pub(crate) const RXQ_INDEX: usize = 0;
26-
pub(crate) const TXQ_INDEX: usize = 1;
2729
pub(crate) const CONTROL_RXQ_INDEX: usize = 2;
2830
pub(crate) const CONTROL_TXQ_INDEX: usize = 3;
2931

@@ -76,7 +78,7 @@ pub struct Console {
7678
pub(crate) device_state: DeviceState,
7779
pub(crate) irq: IRQSignaler,
7880
pub(crate) control: Arc<ConsoleControl>,
79-
pub(crate) port: Port,
81+
pub(crate) ports: Vec<Port>,
8082

8183
pub(crate) queues: Vec<VirtQueue>,
8284
pub(crate) queue_events: Vec<EventFd>,
@@ -91,11 +93,15 @@ pub struct Console {
9193
}
9294

9395
impl Console {
94-
pub fn new(port: PortDescription) -> super::Result<Console> {
95-
let queues: Vec<VirtQueue> = defs::QUEUE_SIZES
96-
.iter()
97-
.map(|&max_size| VirtQueue::new(max_size))
98-
.collect();
96+
pub fn new(ports: Vec<PortDescription>) -> super::Result<Console> {
97+
assert!(!ports.is_empty(), "Expected at least 1 port");
98+
assert!(
99+
matches!(ports[0], PortDescription::Console { .. }),
100+
"First port must be a console"
101+
);
102+
103+
let num_queues = num_queues(ports.len());
104+
let queues = vec![VirtQueue::new(QUEUE_SIZE); num_queues];
99105

100106
let mut queue_events = Vec::new();
101107
for _ in 0..queues.len() {
@@ -104,12 +110,15 @@ impl Console {
104110
}
105111

106112
let (cols, rows) = get_win_size();
107-
let config = VirtioConsoleConfig::new(cols, rows, NUM_PORTS as u32);
113+
let config = VirtioConsoleConfig::new(cols, rows, ports.len() as u32);
114+
let ports = zip(0u32.., ports)
115+
.map(|(port_id, description)| Port::new(port_id, description))
116+
.collect();
108117

109118
Ok(Console {
110119
irq: IRQSignaler::new(),
111120
control: ConsoleControl::new(),
112-
port: Port::new(0, port),
121+
ports,
113122
queues,
114123
queue_events,
115124
avail_features: AVAIL_FEATURES,
@@ -181,7 +190,7 @@ impl Console {
181190
let tx_queue = &mut self.queues[CONTROL_TXQ_INDEX];
182191
let mut raise_irq = false;
183192

184-
let mut start_port = false;
193+
let mut ports_to_start = Vec::new();
185194

186195
while let Some(head) = tx_queue.pop(mem) {
187196
raise_irq = true;
@@ -206,14 +215,28 @@ impl Console {
206215
"Device is ready: initialization {}",
207216
if cmd.value == 1 { "ok" } else { "failed" }
208217
);
209-
self.control.port_add(0);
218+
for port_id in 0..self.ports.len() {
219+
self.control.port_add(port_id as u32);
220+
}
210221
}
211222
control_event::VIRTIO_CONSOLE_PORT_READY => {
212223
if cmd.value != 1 {
213224
log::error!("Port initialization failed: {:?}", cmd);
214225
continue;
215226
}
216-
self.control.mark_console_port(mem, cmd.id);
227+
228+
if self.ports[cmd.id as usize].is_console() {
229+
self.control.mark_console_port(mem, cmd.id);
230+
} else {
231+
// We start with all ports open, this makes sense for now,
232+
// because underlying file descriptors STDIN, STDOUT, STDERR are always open too
233+
self.control.port_open(cmd.id, true)
234+
}
235+
236+
let name = self.ports[cmd.id as usize].name();
237+
if !name.is_empty() {
238+
self.control.port_name(cmd.id, name)
239+
}
217240
}
218241
control_event::VIRTIO_CONSOLE_PORT_OPEN => {
219242
let opened = match cmd.value {
@@ -234,18 +257,18 @@ impl Console {
234257
continue;
235258
}
236259

237-
start_port = true;
260+
ports_to_start.push(cmd.id as usize);
238261
}
239262
_ => log::warn!("Unknown console control event {:x}", cmd.event),
240263
}
241264
}
242265

243-
if start_port {
244-
log::trace!("Starting port");
245-
self.port.start(
266+
for port_id in ports_to_start {
267+
log::trace!("Starting port io for port {}", port_id);
268+
self.ports[port_id].start(
246269
mem.clone(),
247-
self.queues[RXQ_INDEX].clone(),
248-
self.queues[TXQ_INDEX].clone(),
270+
self.queues[port_id_to_queue_idx(QueueDirection::Rx, port_id)].clone(),
271+
self.queues[port_id_to_queue_idx(QueueDirection::Tx, port_id)].clone(),
249272
self.irq.clone(),
250273
self.control.clone(),
251274
);

src/devices/src/virtio/console/event_handler.rs

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ use std::os::unix::io::AsRawFd;
33
use polly::event_manager::{EventManager, Subscriber};
44
use utils::epoll::{EpollEvent, EventSet};
55

6-
use super::device::{get_win_size, Console, RXQ_INDEX, TXQ_INDEX};
6+
use super::device::{get_win_size, Console};
77
use crate::virtio::console::device::{CONTROL_RXQ_INDEX, CONTROL_TXQ_INDEX};
8+
use crate::virtio::console::port_queue_mapping::{queue_idx_to_port_id, QueueDirection};
89
use crate::virtio::device::VirtioDevice;
910

1011
impl Console {
@@ -25,6 +26,20 @@ impl Console {
2526
true
2627
}
2728

29+
fn notify_port_queue_event(&mut self, queue_index: usize) {
30+
let (direction, port_id) = queue_idx_to_port_id(queue_index);
31+
match direction {
32+
QueueDirection::Rx => {
33+
log::trace!("Notify rx (queue event)");
34+
self.ports[port_id].notify_rx()
35+
}
36+
QueueDirection::Tx => {
37+
log::trace!("Notify tx (queue event)");
38+
self.ports[port_id].notify_tx()
39+
}
40+
}
41+
}
42+
2843
fn handle_activate_event(&self, event_manager: &mut EventManager) {
2944
debug!("console: activate event");
3045
if let Err(e) = self.activate_evt.read() {
@@ -92,8 +107,6 @@ impl Console {
92107
impl Subscriber for Console {
93108
fn process(&mut self, event: &EpollEvent, event_manager: &mut EventManager) {
94109
let source = event.fd();
95-
let rxq = self.queue_events[RXQ_INDEX].as_raw_fd();
96-
let txq = self.queue_events[TXQ_INDEX].as_raw_fd();
97110

98111
let control_rxq = self.queue_events[CONTROL_RXQ_INDEX].as_raw_fd();
99112
let control_txq = self.queue_events[CONTROL_TXQ_INDEX].as_raw_fd();
@@ -114,12 +127,14 @@ impl Subscriber for Console {
114127
} else if source == control_rxq {
115128
raise_irq |= self.read_queue_event(CONTROL_RXQ_INDEX, event)
116129
}
117-
else if source == rxq {
118-
self.read_queue_event(RXQ_INDEX, event);
119-
self.port.notify_rx();
120-
} else if source == txq {
121-
self.read_queue_event(TXQ_INDEX, event);
122-
self.port.notify_tx()
130+
/* Guest signaled input/output on port */
131+
else if let Some(queue_index) = self
132+
.queue_events
133+
.iter()
134+
.position(|fd| fd.as_raw_fd() == source)
135+
{
136+
raise_irq |= self.read_queue_event(queue_index, event);
137+
self.notify_port_queue_event(queue_index);
123138
} else if source == activate_evt {
124139
self.handle_activate_event(event_manager);
125140
} else if source == sigwinch_evt {

src/devices/src/virtio/console/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod event_handler;
44
mod irq_signaler;
55
mod port;
66
pub mod port_io;
7+
mod port_queue_mapping;
78
mod process_rx;
89
mod process_tx;
910

@@ -13,10 +14,7 @@ pub use self::port::PortDescription;
1314

1415
mod defs {
1516
pub const CONSOLE_DEV_ID: &str = "virtio_console";
16-
pub const NUM_PORTS: usize = 1;
17-
// 2 control queues and then an rx and tx queue for each port
18-
pub const NUM_QUEUES: usize = 2 + NUM_PORTS * 2;
19-
pub const QUEUE_SIZES: &[u16] = &[256; NUM_QUEUES];
17+
pub const QUEUE_SIZE: u16 = 32;
2018

2119
pub mod uapi {
2220
/// The device conforms to the virtio spec version 1.0.

0 commit comments

Comments
 (0)