1use std::collections::VecDeque;
4use std::sync::{Arc, Mutex};
5
6use tokio::sync::mpsc;
7
8use arcbox_virtio_core::error::{Result, VirtioError};
9use arcbox_virtio_core::queue::VirtQueue;
10use arcbox_virtio_core::{QueueConfig, VirtioDevice, VirtioDeviceId, virtio_bindings};
11
12use crate::{ConsoleIo, StdioConsole};
13
14#[derive(Debug, Clone)]
16pub struct ConsoleConfig {
17 pub cols: u16,
19 pub rows: u16,
21 pub max_ports: u32,
23 pub multiport: bool,
25}
26
27impl Default for ConsoleConfig {
28 fn default() -> Self {
29 Self {
30 cols: 80,
31 rows: 25,
32 max_ports: 1,
33 multiport: false,
34 }
35 }
36}
37
38#[derive(Debug)]
40#[allow(dead_code)]
41struct ConsolePort {
42 id: u32,
44 open: bool,
46 input_buffer: VecDeque<u8>,
48 output_buffer: VecDeque<u8>,
50}
51
52impl ConsolePort {
53 fn new(id: u32) -> Self {
54 Self {
55 id,
56 open: false,
57 input_buffer: VecDeque::with_capacity(4096),
58 output_buffer: VecDeque::with_capacity(4096),
59 }
60 }
61
62 fn take_complete_lines(&mut self) -> Vec<String> {
65 let mut lines = Vec::new();
66 while let Some(pos) = self.output_buffer.iter().position(|&b| b == b'\n') {
67 let line: Vec<u8> = self.output_buffer.drain(..=pos).collect();
68 if let Ok(s) = std::str::from_utf8(&line) {
69 lines.push(s.trim_end().to_string());
70 }
71 }
72 lines
73 }
74
75 fn take_all_lines(&mut self) -> Vec<String> {
79 let mut lines = self.take_complete_lines();
80 if !self.output_buffer.is_empty() {
81 let rest: Vec<u8> = self.output_buffer.drain(..).collect();
82 if let Ok(s) = std::str::from_utf8(&rest) {
83 let trimmed = s.trim_end();
84 if !trimmed.is_empty() {
85 lines.push(trimmed.to_string());
86 }
87 }
88 }
89 lines
90 }
91}
92
93fn emit_console_lines(lines: Vec<String>) {
95 for line in lines {
96 tracing::info!(target: "guest_console", "{line}");
97 }
98}
99
100#[allow(dead_code)]
102pub struct VirtioConsole {
103 config: ConsoleConfig,
104 features: u64,
105 acked_features: u64,
106 ports: Vec<ConsolePort>,
108 rx_queue: Option<VirtQueue>,
110 tx_queue: Option<VirtQueue>,
112 io: Option<Arc<Mutex<dyn ConsoleIo>>>,
114 input_tx: Option<mpsc::UnboundedSender<Vec<u8>>>,
116 rx_last_avail: u16,
120}
121
122impl VirtioConsole {
123 pub const FEATURE_SIZE: u64 = 1 << 0;
125 pub const FEATURE_MULTIPORT: u64 = 1 << 1;
127 pub const FEATURE_EMERG_WRITE: u64 = 1 << 2;
129 pub const FEATURE_VERSION_1: u64 = 1 << virtio_bindings::virtio_config::VIRTIO_F_VERSION_1;
131
132 #[must_use]
134 pub fn new(config: ConsoleConfig) -> Self {
135 let mut features = Self::FEATURE_SIZE | Self::FEATURE_EMERG_WRITE | Self::FEATURE_VERSION_1;
139
140 if config.multiport {
141 features |= Self::FEATURE_MULTIPORT;
142 }
143
144 let mut ports = Vec::with_capacity(config.max_ports as usize);
145 ports.push(ConsolePort::new(0)); Self {
148 config,
149 features,
150 acked_features: 0,
151 ports,
152 rx_queue: None,
153 tx_queue: None,
154 io: None,
155 input_tx: None,
156 rx_last_avail: 0,
157 }
158 }
159
160 #[must_use]
162 pub fn with_stdio() -> Self {
163 let mut console = Self::new(ConsoleConfig::default());
164 console.io = Some(Arc::new(Mutex::new(StdioConsole)));
165 console
166 }
167
168 pub fn set_io(&mut self, io: Arc<Mutex<dyn ConsoleIo>>) {
170 self.io = Some(io);
171 }
172
173 pub fn queue_input(&mut self, data: &[u8]) -> Result<()> {
179 if let Some(port) = self.ports.first_mut() {
180 port.input_buffer.extend(data);
181 Ok(())
182 } else {
183 Err(VirtioError::NotReady("No console port".into()))
184 }
185 }
186
187 #[must_use]
189 pub fn read_output(&mut self) -> Vec<u8> {
190 if let Some(port) = self.ports.first_mut() {
191 port.output_buffer.drain(..).collect()
192 } else {
193 Vec::new()
194 }
195 }
196
197 fn handle_tx(&mut self, data: &[u8]) -> Result<()> {
199 if let Some(port) = self.ports.first_mut() {
200 port.output_buffer.extend(data);
201 }
202
203 if let Some(io) = &self.io {
204 let mut io = io
205 .lock()
206 .map_err(|e| VirtioError::Io(format!("Failed to lock I/O: {e}")))?;
207 io.write(data)
208 .map_err(|e| VirtioError::Io(format!("Write failed: {e}")))?;
209 io.flush()
210 .map_err(|e| VirtioError::Io(format!("Flush failed: {e}")))?;
211 }
212
213 tracing::trace!("Console TX: {} bytes", data.len());
214 Ok(())
215 }
216
217 #[allow(dead_code)]
219 fn handle_rx(&mut self, buf: &mut [u8]) -> Result<usize> {
220 if let Some(port) = self.ports.first_mut() {
221 if !port.input_buffer.is_empty() {
222 let len = buf.len().min(port.input_buffer.len());
223 for (i, byte) in port.input_buffer.drain(..len).enumerate() {
224 buf[i] = byte;
225 }
226 return Ok(len);
227 }
228 }
229
230 if let Some(io) = &self.io {
231 let mut io = io
232 .lock()
233 .map_err(|e| VirtioError::Io(format!("Failed to lock I/O: {e}")))?;
234 let n = io
235 .read(buf)
236 .map_err(|e| VirtioError::Io(format!("Read failed: {e}")))?;
237 tracing::trace!("Console RX: {} bytes", n);
238 return Ok(n);
239 }
240
241 Ok(0)
242 }
243
244 pub fn process_tx_queue(&mut self, memory: &[u8]) -> Result<Vec<(u16, u32)>> {
250 let mut tx_data: Vec<(u16, Vec<u8>)> = Vec::new();
251
252 {
253 let queue = self
254 .tx_queue
255 .as_mut()
256 .ok_or_else(|| VirtioError::NotReady("TX queue not ready".into()))?;
257
258 while let Some((head_idx, chain)) = queue.pop_avail() {
259 let mut data = Vec::new();
260
261 for desc in chain {
262 if !desc.is_write_only() {
263 let start = desc.addr as usize;
264 let end = start + desc.len as usize;
265 if end <= memory.len() {
266 data.extend_from_slice(&memory[start..end]);
267 }
268 }
269 }
270
271 tx_data.push((head_idx, data));
272 }
273 }
274
275 let mut completed = Vec::new();
276 for (head_idx, data) in tx_data {
277 let len = data.len() as u32;
278 self.handle_tx(&data)?;
279 completed.push((head_idx, len));
280 }
281
282 Ok(completed)
283 }
284
285 #[must_use]
287 pub fn rx_available(&self) -> usize {
288 self.ports
289 .first()
290 .map(|p| p.input_buffer.len())
291 .unwrap_or(0)
292 }
293
294 pub fn process_rx_queue(
306 &mut self,
307 memory: &mut [u8],
308 queue_config: &QueueConfig,
309 ) -> Result<bool> {
310 if !queue_config.ready || queue_config.size == 0 {
311 return Ok(false);
312 }
313 if self.rx_available() == 0 {
315 return Ok(false);
316 }
317
318 let mem = std::sync::Arc::new(unsafe {
322 arcbox_virtio_core::GuestMemWriter::new(
323 memory.as_mut_ptr(),
324 memory.len(),
325 queue_config.gpa_base as usize,
326 )
327 });
328 let mut queue = arcbox_virtio_core::SplitQueue::new(mem, 0, queue_config, false);
329 queue.set_last_avail_idx(self.rx_last_avail);
330
331 let mut completions: Vec<(u16, u32)> = Vec::new();
332 while self.rx_available() > 0 {
336 let Some(chain) = queue.pop_avail() else {
337 break;
338 };
339 let mut written = 0u32;
340 let mut input_done = false;
341 for desc in &chain.descriptors {
342 if desc.is_write() && desc.len > 0 {
344 if let Some(port) = self.ports.first_mut() {
345 let n = (desc.len as usize).min(port.input_buffer.len());
346 if let Some(buf) = unsafe { queue.mem().slice_mut(desc.addr as usize, n) } {
348 for slot in buf.iter_mut() {
349 if let Some(b) = port.input_buffer.pop_front() {
350 *slot = b;
351 }
352 }
353 written += n as u32;
354 input_done = port.input_buffer.is_empty();
355 }
356 }
357 }
358 if input_done {
359 break; }
361 }
362 completions.push((chain.head_idx, written));
363 }
364
365 let injected = !completions.is_empty();
366 queue.push_used_batch(&completions);
367 self.rx_last_avail = queue.last_avail_idx();
368 Ok(injected)
369 }
370}
371
372impl VirtioDevice for VirtioConsole {
373 fn device_id(&self) -> VirtioDeviceId {
374 VirtioDeviceId::Console
375 }
376
377 fn features(&self) -> u64 {
378 self.features
379 }
380
381 fn ack_features(&mut self, features: u64) {
382 self.acked_features = self.features & features;
383 }
384
385 fn read_config(&self, offset: u64, data: &mut [u8]) {
386 let config_data = [
392 self.config.cols.to_le_bytes().as_slice(),
393 &self.config.rows.to_le_bytes(),
394 &self.config.max_ports.to_le_bytes(),
395 &0u32.to_le_bytes(), ]
397 .concat();
398
399 let offset = offset as usize;
400 let len = data.len().min(config_data.len().saturating_sub(offset));
401 if len > 0 {
402 data[..len].copy_from_slice(&config_data[offset..offset + len]);
403 }
404 }
405
406 fn write_config(&mut self, offset: u64, data: &[u8]) {
407 if offset == 8 && data.len() >= 4 {
409 let ch = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
410 if ch != 0 {
411 if let Some(c) = char::from_u32(ch) {
412 eprint!("{c}");
413 }
414 }
415 }
416 }
417
418 fn activate(&mut self) -> Result<()> {
419 self.rx_queue = Some(VirtQueue::new(256)?);
420 self.tx_queue = Some(VirtQueue::new(256)?);
421
422 if let Some(port) = self.ports.first_mut() {
423 port.open = true;
424 }
425
426 tracing::info!(
427 "VirtIO console activated: {}x{}, {} ports",
428 self.config.cols,
429 self.config.rows,
430 self.config.max_ports
431 );
432
433 Ok(())
434 }
435
436 fn reset(&mut self) {
437 self.acked_features = 0;
438 self.rx_queue = None;
439 self.tx_queue = None;
440 self.rx_last_avail = 0;
441
442 for port in &mut self.ports {
443 port.open = false;
444 port.input_buffer.clear();
445 emit_console_lines(port.take_all_lines());
448 }
449 }
450
451 fn process_queue(
452 &mut self,
453 queue_idx: u16,
454 memory: &mut [u8],
455 queue_config: &QueueConfig,
456 ) -> Result<Vec<(u16, u32)>> {
457 if queue_idx != 1 {
460 return Ok(Vec::new());
461 }
462
463 if !queue_config.ready || queue_config.size == 0 {
464 return Ok(Vec::new());
465 }
466
467 let mem = std::sync::Arc::new(unsafe {
471 arcbox_virtio_core::GuestMemWriter::new(
472 memory.as_mut_ptr(),
473 memory.len(),
474 queue_config.gpa_base as usize,
475 )
476 });
477 let mut queue = arcbox_virtio_core::SplitQueue::new(mem, queue_idx, queue_config, false);
478 let start = queue.mem().read_u16(queue_config.used_addr as usize + 2);
481 queue.set_last_avail_idx(start);
482
483 let io = self.io.clone();
488
489 let mut completions = Vec::new();
490 while let Some(chain) = queue.pop_avail() {
491 let mut total_len = 0u32;
492 for desc in &chain.descriptors {
493 if desc.is_write() {
495 continue;
496 }
497 let Some(data) = queue.mem().slice(desc.addr as usize, desc.len as usize) else {
498 continue;
499 };
500 if let Some(io) = &io {
501 if let Ok(mut g) = io.lock() {
502 let _ = g.write(data);
503 let _ = g.flush();
504 }
505 }
506 if let Some(port) = self.ports.first_mut() {
507 port.output_buffer.extend(data.iter().copied());
508 emit_console_lines(port.take_complete_lines());
512 }
513 total_len += desc.len;
514 }
515 completions.push((chain.head_idx, total_len));
516 }
517
518 queue.push_used_batch(&completions);
519 Ok(completions)
520 }
521}
522
523impl Drop for VirtioConsole {
524 fn drop(&mut self) {
525 for port in &mut self.ports {
528 emit_console_lines(port.take_all_lines());
529 }
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use crate::BufferConsole;
537
538 #[test]
539 fn test_console_creation() {
540 let console = VirtioConsole::new(ConsoleConfig::default());
541 assert_eq!(console.device_id(), VirtioDeviceId::Console);
542 assert!(console.features() & VirtioConsole::FEATURE_SIZE != 0);
543 }
544
545 #[test]
546 fn test_console_config_read() {
547 let config = ConsoleConfig {
548 cols: 120,
549 rows: 40,
550 max_ports: 4,
551 multiport: false,
552 };
553 let console = VirtioConsole::new(config);
554
555 let mut data = [0u8; 8];
556 console.read_config(0, &mut data);
557
558 assert_eq!(u16::from_le_bytes([data[0], data[1]]), 120); assert_eq!(u16::from_le_bytes([data[2], data[3]]), 40); assert_eq!(u32::from_le_bytes([data[4], data[5], data[6], data[7]]), 4);
561 }
563
564 #[test]
565 fn test_console_input_queue() {
566 let mut console = VirtioConsole::new(ConsoleConfig::default());
567 console.activate().unwrap();
568
569 console.queue_input(b"test input").unwrap();
570 assert_eq!(console.rx_available(), 10);
571 }
572
573 #[test]
574 fn test_console_output() {
575 let buffer = Arc::new(Mutex::new(BufferConsole::new()));
576 let mut console = VirtioConsole::new(ConsoleConfig::default());
577 console.set_io(buffer.clone());
578 console.activate().unwrap();
579
580 console.handle_tx(b"Hello, World!").unwrap();
581
582 let output = buffer.lock().unwrap().take_output();
583 assert_eq!(&output, b"Hello, World!");
584 }
585
586 #[test]
587 fn test_console_multiport_feature() {
588 let config = ConsoleConfig {
589 multiport: true,
590 ..Default::default()
591 };
592 let console = VirtioConsole::new(config);
593 assert!(console.features() & VirtioConsole::FEATURE_MULTIPORT != 0);
594 }
595
596 #[test]
597 fn test_console_activate_and_reset() {
598 let mut console = VirtioConsole::new(ConsoleConfig::default());
599
600 console.activate().unwrap();
601 assert!(console.rx_queue.is_some());
602 assert!(console.tx_queue.is_some());
603
604 console.reset();
605 assert!(console.rx_queue.is_none());
606 assert!(console.tx_queue.is_none());
607 assert_eq!(console.acked_features, 0);
608 }
609
610 #[test]
611 fn test_console_read_output() {
612 let mut console = VirtioConsole::new(ConsoleConfig::default());
613 console.activate().unwrap();
614
615 let output = console.read_output();
616 assert!(output.is_empty());
617
618 console.handle_tx(b"test output").unwrap();
619 let output = console.read_output();
620 assert_eq!(&output, b"test output");
621
622 let output2 = console.read_output();
623 assert!(output2.is_empty());
624 }
625
626 #[test]
627 fn test_console_queue_input_not_ready() {
628 let mut console = VirtioConsole::new(ConsoleConfig::default());
629
630 console.ports.clear();
631
632 let result = console.queue_input(b"test");
633 assert!(result.is_err());
634 }
635
636 #[test]
637 fn test_console_config_write() {
638 let mut console = VirtioConsole::new(ConsoleConfig::default());
639
640 let emergency_char = 'X' as u32;
641 console.write_config(8, &emergency_char.to_le_bytes());
642
643 }
645
646 #[test]
647 fn test_console_feature_negotiation() {
648 let mut console = VirtioConsole::new(ConsoleConfig::default());
649
650 let offered = console.features();
651 assert!(offered & VirtioConsole::FEATURE_VERSION_1 != 0);
652
653 console.ack_features(VirtioConsole::FEATURE_SIZE | VirtioConsole::FEATURE_VERSION_1);
654 assert!(console.acked_features & VirtioConsole::FEATURE_SIZE != 0);
655 }
656
657 #[test]
658 fn test_console_with_stdio() {
659 let console = VirtioConsole::with_stdio();
660 assert!(console.io.is_some());
661 }
662
663 #[test]
664 fn test_console_config_partial_read() {
665 let console = VirtioConsole::new(ConsoleConfig {
666 cols: 80,
667 rows: 25,
668 max_ports: 1,
669 multiport: false,
670 });
671
672 let mut data = [0u8; 2];
673 console.read_config(0, &mut data);
674 assert_eq!(u16::from_le_bytes(data), 80);
675
676 let mut data2 = [0u8; 2];
677 console.read_config(2, &mut data2);
678 assert_eq!(u16::from_le_bytes(data2), 25);
679 }
680
681 #[test]
682 fn test_console_rx_available_empty() {
683 let console = VirtioConsole::new(ConsoleConfig::default());
684 assert_eq!(console.rx_available(), 0);
685 }
686
687 #[test]
688 fn test_console_process_rx_queue_injects_input() {
689 const SIZE: u16 = 4;
691 const DESC: usize = 0x100;
692 const AVAIL: usize = 0x200;
693 const USED: usize = 0x300;
694 const BUF: usize = 0x400;
695 let mut mem = vec![0u8; 0x600];
696
697 mem[DESC..DESC + 8].copy_from_slice(&(BUF as u64).to_le_bytes());
699 mem[DESC + 8..DESC + 12].copy_from_slice(&16u32.to_le_bytes());
700 mem[DESC + 12..DESC + 14].copy_from_slice(&2u16.to_le_bytes()); mem[DESC + 14..DESC + 16].copy_from_slice(&0u16.to_le_bytes());
702
703 mem[AVAIL + 4..AVAIL + 6].copy_from_slice(&0u16.to_le_bytes());
705 mem[AVAIL + 2..AVAIL + 4].copy_from_slice(&1u16.to_le_bytes());
706
707 let qc = QueueConfig {
708 desc_addr: DESC as u64,
709 avail_addr: AVAIL as u64,
710 used_addr: USED as u64,
711 size: SIZE,
712 ready: true,
713 gpa_base: 0,
714 };
715
716 let mut console = VirtioConsole::new(ConsoleConfig::default());
717 assert!(!console.process_rx_queue(&mut mem, &qc).unwrap());
719
720 console.queue_input(b"hello").unwrap();
721 let injected = console.process_rx_queue(&mut mem, &qc).unwrap();
722 assert!(injected);
723 assert_eq!(&mem[BUF..BUF + 5], b"hello");
724
725 assert_eq!(u16::from_le_bytes([mem[USED + 2], mem[USED + 3]]), 1);
727 assert_eq!(
728 u32::from_le_bytes([mem[USED + 4], mem[USED + 5], mem[USED + 6], mem[USED + 7]]),
729 0
730 );
731 assert_eq!(
732 u32::from_le_bytes([mem[USED + 8], mem[USED + 9], mem[USED + 10], mem[USED + 11]]),
733 5
734 );
735
736 assert_eq!(console.rx_available(), 0);
738 assert!(!console.process_rx_queue(&mut mem, &qc).unwrap());
739 }
740
741 #[test]
742 fn take_complete_lines_leaves_trailing_partial_buffered() {
743 let mut port = ConsolePort::new(0);
744 port.output_buffer
745 .extend(b"first\nsecond\npartial no newline");
746
747 assert_eq!(port.take_complete_lines(), vec!["first", "second"]);
749 assert!(!port.output_buffer.is_empty());
750 }
751
752 #[test]
753 fn take_all_lines_flushes_the_dying_partial_line() {
754 let mut port = ConsolePort::new(0);
755 port.output_buffer
757 .extend(b"Kernel panic - not syncing: Attempted to kill init");
758
759 let lines = port.take_all_lines();
761 assert_eq!(
762 lines,
763 vec!["Kernel panic - not syncing: Attempted to kill init"]
764 );
765 assert!(port.output_buffer.is_empty());
766 }
767}