serial_debugcon/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Implementation of the bochs / QEMU debugcon device.
5//!
6//! This is a zero-configuration, output-only serial device, which should only
7//! be used for debugging (hence the name). It offers no flow control
8//! mechanisms, or any method of reading data into the Guest.
9
10#![forbid(unsafe_code)]
11
12pub mod resolver;
13
14use chipset_device::ChipsetDevice;
15use chipset_device::io::IoError;
16use chipset_device::io::IoResult;
17use chipset_device::pio::PortIoIntercept;
18use chipset_device::poll_device::PollDevice;
19use futures::AsyncWrite;
20use inspect::InspectMut;
21use serial_core::SerialIo;
22use std::collections::VecDeque;
23use std::io::ErrorKind;
24use std::ops::RangeInclusive;
25use std::pin::Pin;
26use std::task::Context;
27use std::task::Poll;
28use std::task::Waker;
29use std::task::ready;
30use vmcore::device_state::ChangeDeviceState;
31
32// the bound here is entirely arbitrary. we pick a relatively large number, just
33// in case some guest decides to try and dump a _lot_ of data over the debugcon
34// all at once.
35const TX_BUFFER_MAX: usize = 1024 * 1024; // 1MB
36
37/// A debugcon serial port emulator.
38#[derive(InspectMut)]
39pub struct SerialDebugcon {
40    // Fixed configuration
41    io_port: u16,
42    #[inspect(skip)]
43    io_region: (&'static str, RangeInclusive<u16>),
44
45    // Runtime glue
46    #[inspect(mut)]
47    io: Box<dyn SerialIo>,
48
49    // Volatile state
50    #[inspect(with = "VecDeque::len")]
51    tx_buffer: VecDeque<u8>,
52    #[inspect(skip)]
53    tx_waker: Option<Waker>,
54}
55
56impl SerialDebugcon {
57    /// Returns a new emulator instance.
58    pub fn new(port: u16, io: Box<dyn SerialIo>) -> Self {
59        Self {
60            io_port: port,
61            io_region: ("debugcon", port..=port),
62            io,
63            tx_buffer: VecDeque::new(),
64            tx_waker: None,
65        }
66    }
67
68    /// Synchronize interrupt and waker state with device state.
69    fn sync(&mut self) {
70        // Wake to poll if there are any bytes to write.
71        if !self.tx_buffer.is_empty() {
72            if let Some(waker) = self.tx_waker.take() {
73                waker.wake();
74            }
75        }
76    }
77
78    fn poll_tx(&mut self, cx: &mut Context<'_>) -> Poll<()> {
79        while !self.tx_buffer.is_empty() {
80            let (buf, _) = self.tx_buffer.as_slices();
81            match ready!(Pin::new(&mut self.io).poll_write(cx, buf)) {
82                Ok(n) => {
83                    assert_ne!(n, 0);
84                    self.tx_buffer.drain(..n);
85                }
86                Err(err) if err.kind() == ErrorKind::BrokenPipe => {
87                    tracing::info!("debugcon output broken pipe");
88                }
89                Err(err) => {
90                    tracelimit::error_ratelimited!(
91                        len = buf.len(),
92                        error = &err as &dyn std::error::Error,
93                        "debugcon write failed, dropping data"
94                    );
95                    self.tx_buffer.drain(..buf.len());
96                }
97            }
98        }
99        // Wait for more bytes to write.
100        self.tx_waker = Some(cx.waker().clone());
101        Poll::Pending
102    }
103}
104
105impl ChangeDeviceState for SerialDebugcon {
106    fn start(&mut self) {}
107
108    async fn stop(&mut self) {}
109
110    async fn reset(&mut self) {
111        self.tx_buffer.clear();
112    }
113}
114
115impl ChipsetDevice for SerialDebugcon {
116    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
117        Some(self)
118    }
119
120    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
121        Some(self)
122    }
123}
124
125impl PollDevice for SerialDebugcon {
126    fn poll_device(&mut self, cx: &mut Context<'_>) {
127        let _ = self.poll_tx(cx);
128        self.sync();
129    }
130}
131
132impl PortIoIntercept for SerialDebugcon {
133    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
134        if io_port != self.io_port {
135            return IoResult::Err(IoError::InvalidRegister);
136        }
137
138        if data.len() != 1 {
139            return IoResult::Err(IoError::InvalidAccessSize);
140        }
141
142        // this is a magic constant which the guest can use to detect the
143        // presence of the debugcon device. Its value is fixed, and matches the
144        // value set by the original debugcon implementation in bochs.
145        data[0] = 0xe9;
146
147        IoResult::Ok
148    }
149
150    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
151        if io_port != self.io_port {
152            return IoResult::Err(IoError::InvalidRegister);
153        }
154
155        if data.len() != 1 {
156            return IoResult::Err(IoError::InvalidAccessSize);
157        }
158
159        if self.tx_buffer.len() >= TX_BUFFER_MAX {
160            tracing::debug!("debugcon buffer overrun, dropping output data");
161            return IoResult::Ok;
162        }
163
164        self.tx_buffer.push_back(data[0]);
165
166        // HACK: work around the fact that in openvmm, the console is in raw mode.
167        //
168        // FUTURE: this should be configurable, in case folks need 1:1 faithful
169        // debugcon output.
170        if data[0] == b'\n' {
171            self.tx_buffer.push_back(b'\r')
172        }
173
174        self.sync();
175
176        IoResult::Ok
177    }
178
179    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
180        std::slice::from_ref(&self.io_region)
181    }
182}
183
184mod save_restore {
185    use crate::SerialDebugcon;
186    use vmcore::save_restore::NoSavedState;
187    use vmcore::save_restore::RestoreError;
188    use vmcore::save_restore::SaveError;
189    use vmcore::save_restore::SaveRestore;
190
191    impl SaveRestore for SerialDebugcon {
192        type SavedState = NoSavedState;
193
194        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
195            Ok(NoSavedState)
196        }
197
198        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
199            let NoSavedState = state;
200            Ok(())
201        }
202    }
203}