Skip to content

Commit 7e82782

Browse files
committed
Full multiport console (missing blocking shutdown)
console: Introduce port abstraction and use VolatileRead/Write Curently only 1 (console) port is supported. Signed-off-by: Matej Hrica <[email protected]> console: Re-implement console resize for multiport console Signed-off-by: Matej Hrica <[email protected]> All the cleanups squashme: process_tx no log virtio-console: prepare device to handle more ports Signed-off-by: Matej Hrica <[email protected]> virtio-console: Make redirected stdin go through a pipe (not console) Signed-off-by: Matej Hrica <[email protected]> virtio-net: Handle input EOF and pending input before guest is ready Signed-off-by: Matej Hrica <[email protected]> virtio-console: Make output non-blocking Signed-off-by: Matej Hrica <[email protected]> virtio-console: Make stdout a pipe if !isatty Signed-off-by: Matej Hrica <[email protected]> Multithreaded virtio-console with eof handling suboptimal but working suboptimal but working poll inside the rx/tx thread instead of global event_manager try to write the descriptor chains, but doesn't really help :( tweaks and logging stuff, importantly we crash when input is EOF, so handle that ASAP move process_tx to the main thread a have a queue in front of it propage ConsoleControl into PortRX/portTx wip hardcoded pipe input works refactor cleanup Port abstract PortOutput/PortInput as traits close eof on the correct port number Signed-off-by: Matej Hrica <[email protected]>
1 parent 9896513 commit 7e82782

17 files changed

+1052
-372
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

init/init.c

+66
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/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ crossbeam-channel = "0.5"
1515
env_logger = "0.9.0"
1616
libc = ">=0.2.39"
1717
log = "0.4.0"
18-
nix = "0.24.1"
18+
nix = { version = "0.24.1", features = ["poll"] }
1919
rand = "0.8.5"
2020
vm-memory = { version = ">=0.13", features = ["backend-mmap"] }
2121

Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
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+
19
use crate::virtio::console::defs::control_event::{
2-
VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_PORT_ADD,
10+
VIRTIO_CONSOLE_CONSOLE_PORT, VIRTIO_CONSOLE_PORT_ADD, VIRTIO_CONSOLE_PORT_NAME,
11+
VIRTIO_CONSOLE_PORT_OPEN, VIRTIO_CONSOLE_RESIZE,
312
};
4-
use crate::virtio::Queue as VirtQueue;
5-
use vm_memory::{ByteValued, Bytes, GuestMemoryMmap};
613

714
#[derive(Copy, Clone, Debug, Default)]
815
#[repr(C, packed(4))]
@@ -19,53 +26,127 @@ pub struct VirtioConsoleControl {
1926
// But NOTE that this relies on CPU being little endian, to have correct semantics
2027
unsafe impl ByteValued for VirtioConsoleControl {}
2128

29+
#[derive(Copy, Clone, Debug, Default)]
30+
#[repr(C, packed)]
31+
pub struct VirtioConsoleResize {
32+
// NOTE: the order of these fields in the actual kernel implementation and in the spec are swapped,
33+
// we follow the order in the kernel to get it working correctly
34+
pub rows: u16,
35+
pub cols: u16,
36+
}
37+
38+
// Safe because it only has data and has no implicit padding.
39+
// but NOTE, that we rely on CPU being little endian, for the values to be correct
40+
unsafe impl ByteValued for VirtioConsoleResize {}
41+
42+
pub enum Payload {
43+
ConsoleControl(VirtioConsoleControl),
44+
Bytes(Vec<u8>),
45+
}
46+
47+
impl Deref for Payload {
48+
type Target = [u8];
49+
50+
fn deref(&self) -> &Self::Target {
51+
match self {
52+
Payload::ConsoleControl(b) => b.as_slice(),
53+
Payload::Bytes(b) => b.as_slice(),
54+
}
55+
}
56+
}
57+
2258
// Utility for sending commands into control rx queue
23-
pub struct ConsoleControlSender<'a> {
24-
queue: &'a mut VirtQueue,
59+
pub struct ConsoleControl {
60+
queue: Mutex<VecDeque<Payload>>,
61+
queue_evt: EventFd,
2562
}
2663

27-
impl<'a> ConsoleControlSender<'a> {
28-
pub fn new(control_rx_queue: &'a mut VirtQueue) -> Self {
29-
return ConsoleControlSender {
30-
queue: control_rx_queue,
31-
};
64+
impl ConsoleControl {
65+
pub fn new() -> Arc<Self> {
66+
Arc::new(Self {
67+
queue: Default::default(),
68+
queue_evt: EventFd::new(EFD_NONBLOCK).unwrap(),
69+
})
3270
}
3371

34-
pub fn send_mark_console_port(&mut self, mem: &GuestMemoryMmap, port_id: u32) {
35-
self.send_cmd(
36-
mem,
37-
&VirtioConsoleControl {
38-
id: port_id,
39-
event: VIRTIO_CONSOLE_CONSOLE_PORT,
40-
value: 1,
41-
},
42-
)
72+
pub fn mark_console_port(&self, _mem: &GuestMemoryMmap, port_id: u32) {
73+
self.push_msg(VirtioConsoleControl {
74+
id: port_id,
75+
event: VIRTIO_CONSOLE_CONSOLE_PORT,
76+
value: 1,
77+
})
4378
}
4479

45-
/// Adds another port with the specified port_id
46-
pub fn send_port_add(&mut self, mem: &GuestMemoryMmap, port_id: u32) {
47-
self.send_cmd(
48-
mem,
49-
&VirtioConsoleControl {
80+
pub fn console_resize(&self, port_id: u32, new_size: VirtioConsoleResize) {
81+
let mut buf = Vec::new();
82+
buf.extend(
83+
VirtioConsoleControl {
5084
id: port_id,
51-
event: VIRTIO_CONSOLE_PORT_ADD,
85+
event: VIRTIO_CONSOLE_RESIZE,
5286
value: 0,
53-
},
54-
)
87+
}
88+
.as_slice(),
89+
);
90+
buf.extend(new_size.as_slice());
91+
self.push_vec(buf)
92+
}
93+
94+
/// Adds another port with the specified port_id
95+
pub fn port_add(&self, port_id: u32) {
96+
self.push_msg(VirtioConsoleControl {
97+
id: port_id,
98+
event: VIRTIO_CONSOLE_PORT_ADD,
99+
value: 0,
100+
})
55101
}
56102

57-
fn send_bytes(&mut self, mem: &GuestMemoryMmap, data: &[u8]) {
58-
if let Some(head) = self.queue.pop(mem) {
59-
if let Err(e) = mem.write_slice(data, head.addr) {
60-
log::error!("Failed to write to tx_control_queue: {e:?}");
103+
pub fn port_open(&self, port_id: u32, open: bool) {
104+
self.push_msg(VirtioConsoleControl {
105+
id: port_id,
106+
event: VIRTIO_CONSOLE_PORT_OPEN,
107+
value: open as u16,
108+
})
109+
}
110+
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.
61119
}
62-
self.queue.add_used(mem, head.index, data.len() as u32);
63-
} else {
64-
log::error!("Failed to write to tx_control_queue: no space in queue");
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+
128+
pub fn queue_pop(&self) -> Option<Payload> {
129+
let mut queue = self.queue.lock().expect("Poisoned lock");
130+
queue.pop_front()
131+
}
132+
133+
pub fn queue_evt(&self) -> &EventFd {
134+
&self.queue_evt
135+
}
136+
137+
fn push_msg(&self, msg: VirtioConsoleControl) {
138+
let mut queue = self.queue.lock().expect("Poisoned lock");
139+
queue.push_back(Payload::ConsoleControl(msg));
140+
if let Err(e) = self.queue_evt.write(1) {
141+
log::trace!("ConsoleControl failed to write to notify {e}")
65142
}
66143
}
67144

68-
fn send_cmd(&mut self, mem: &GuestMemoryMmap, cmd: &VirtioConsoleControl) {
69-
self.send_bytes(mem, cmd.as_slice())
145+
fn push_vec(&self, buf: Vec<u8>) {
146+
let mut queue = self.queue.lock().expect("Poisoned lock");
147+
queue.push_back(Payload::Bytes(buf));
148+
if let Err(e) = self.queue_evt.write(1) {
149+
log::trace!("ConsoleControl failed to write to notify {e}")
150+
}
70151
}
71152
}

0 commit comments

Comments
 (0)