squib_virtio/devices/
console.rs1use std::{io::Write, sync::Arc};
13
14use parking_lot::Mutex;
15use squib_core::GuestMemory;
16
17use crate::{
18 device::{ActivateError, VirtioDevice},
19 device_id::VirtioDeviceType,
20 interrupt::IrqLine,
21 queue::Queue,
22};
23
24pub const F_SIZE: u64 = 1 << 0;
27pub const F_MULTIPORT: u64 = 1 << 1;
29
30pub const RX_QUEUE: usize = 0;
32pub const TX_QUEUE: usize = 1;
34
35const QUEUE_MAX_SIZE: u16 = 64;
36
37pub enum ConsoleSink {
39 Discard,
41 Writer(Box<dyn Write + Send>),
43}
44
45impl std::fmt::Debug for ConsoleSink {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 match self {
48 Self::Discard => f.write_str("ConsoleSink::Discard"),
49 Self::Writer(_) => f.write_str("ConsoleSink::Writer(<dyn Write + Send>)"),
50 }
51 }
52}
53
54#[derive(Debug)]
56pub struct ConsoleDevice {
57 avail: u64,
58 acked: u64,
59 queues: Vec<Queue>,
60 sink: Arc<Mutex<ConsoleSink>>,
61 state: Arc<Mutex<ActiveState>>,
62}
63
64#[derive(Debug, Default)]
65struct ActiveState {
66 mem: Option<Arc<dyn GuestMemory>>,
67 irq: Option<IrqLine>,
68 activated: bool,
69}
70
71impl ConsoleDevice {
72 #[must_use]
74 pub fn new(sink: ConsoleSink) -> Self {
75 Self {
76 avail: 0,
77 acked: 0,
78 queues: vec![Queue::new(QUEUE_MAX_SIZE), Queue::new(QUEUE_MAX_SIZE)],
79 sink: Arc::new(Mutex::new(sink)),
80 state: Arc::new(Mutex::new(ActiveState::default())),
81 }
82 }
83
84 #[must_use]
87 pub fn discard() -> Self {
88 Self::new(ConsoleSink::Discard)
89 }
90
91 fn drain_tx(&mut self) {
92 let (mem, irq) = {
93 let state = self.state.lock();
94 match (state.mem.clone(), state.irq.clone()) {
95 (Some(m), Some(i)) => (m, i),
96 _ => return,
97 }
98 };
99 let queue = &mut self.queues[TX_QUEUE];
100 let mut completed = false;
101 loop {
102 let chain = match queue.pop_avail(mem.as_ref()) {
103 Ok(Some(c)) => c,
104 Ok(None) => break,
105 Err(err) => {
106 tracing::warn!(error = %err, "console: tx walk failed");
107 break;
108 }
109 };
110 let head = chain.head_index();
111 let descs = match chain.collect(mem.as_ref()) {
112 Ok(d) => d,
113 Err(err) => {
114 tracing::warn!(error = %err, "console: tx collect failed");
115 break;
116 }
117 };
118 let mut written: u32 = 0;
119 for desc in descs {
120 if desc.is_write_only() {
121 continue;
122 }
123 let len = desc.len as usize;
124 if len == 0 {
125 continue;
126 }
127 let mut buf = vec![0u8; len];
128 if let Err(err) = mem.read(desc.addr, &mut buf) {
129 tracing::warn!(error = %err, "console: tx read from guest failed");
130 continue;
131 }
132 let mut sink = self.sink.lock();
133 match &mut *sink {
134 ConsoleSink::Discard => {}
135 ConsoleSink::Writer(w) => {
136 if let Err(err) = w.write_all(&buf) {
137 tracing::warn!(error = %err, "console: tx host write failed");
138 }
139 }
140 }
141 written = written.saturating_add(desc.len);
142 }
143 if let Err(err) = queue.push_used(mem.as_ref(), head, written) {
144 tracing::warn!(error = %err, "console: push_used failed");
145 break;
146 }
147 completed = true;
148 }
149 if completed {
150 let _ = irq.trigger_queue();
151 }
152 }
153}
154
155impl VirtioDevice for ConsoleDevice {
156 fn device_type(&self) -> VirtioDeviceType {
157 VirtioDeviceType::Console
158 }
159 fn avail_features(&self) -> u64 {
160 self.avail
161 }
162 fn acked_features(&self) -> u64 {
163 self.acked
164 }
165 fn set_acked_features(&mut self, value: u64) {
166 self.acked = value;
167 }
168 fn queue_max_sizes(&self) -> &[u16] {
169 const SIZES: &[u16] = &[QUEUE_MAX_SIZE, QUEUE_MAX_SIZE];
170 SIZES
171 }
172 fn queues(&self) -> &[Queue] {
173 &self.queues
174 }
175 fn queues_mut(&mut self) -> &mut [Queue] {
176 &mut self.queues
177 }
178 fn read_config(&self, _offset: u64, data: &mut [u8]) {
179 for b in data.iter_mut() {
180 *b = 0;
181 }
182 }
183 fn write_config(&mut self, _offset: u64, _data: &[u8]) {}
184 fn activate(&mut self, mem: Arc<dyn GuestMemory>, irq: IrqLine) -> Result<(), ActivateError> {
185 let mut state = self.state.lock();
186 state.mem = Some(mem);
187 state.irq = Some(irq);
188 state.activated = true;
189 Ok(())
190 }
191 fn is_activated(&self) -> bool {
192 self.state.lock().activated
193 }
194 fn process_queue(&mut self, queue_index: u16) {
195 if queue_index as usize == TX_QUEUE {
196 self.drain_tx();
197 }
198 }
202}
203
204#[cfg(test)]
205mod tests {
206 use std::io::Cursor;
207
208 use squib_arch::IntId;
209 use squib_core::{GuestAddress, SliceGuestMemory};
210 use squib_gic::Gic;
211
212 use super::*;
213
214 #[derive(Debug, Default)]
216 struct CapturedSink(Arc<Mutex<Vec<u8>>>);
217 impl Write for CapturedSink {
218 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
219 self.0.lock().extend_from_slice(buf);
220 Ok(buf.len())
221 }
222 fn flush(&mut self) -> std::io::Result<()> {
223 Ok(())
224 }
225 }
226
227 #[derive(Debug, Default)]
228 struct StubGic;
229 impl Gic for StubGic {
230 fn pulse_spi(&self, _: IntId) -> Result<(), squib_gic::GicError> {
231 Ok(())
232 }
233 fn set_spi_level(&self, _: IntId, _: bool) -> Result<(), squib_gic::GicError> {
234 Ok(())
235 }
236 fn save_state(&self) -> Result<Vec<u8>, squib_gic::GicError> {
237 Ok(Vec::new())
238 }
239 fn restore_state(&self, _data: &[u8]) -> Result<(), squib_gic::GicError> {
240 Ok(())
241 }
242 }
243
244 fn line() -> IrqLine {
245 let gic: Arc<dyn Gic + Send + Sync> = Arc::new(StubGic);
246 IrqLine::new(gic, IntId::from_spi_cell(16).unwrap())
247 }
248
249 #[test]
250 fn test_should_offer_two_queues_one_each_direction() {
251 let dev = ConsoleDevice::discard();
252 assert_eq!(dev.queue_max_sizes().len(), 2);
253 }
254
255 #[test]
256 fn test_should_forward_tx_descriptors_to_host_sink() {
257 let buf = Arc::new(Mutex::new(Vec::new()));
258 let sink = CapturedSink(buf.clone());
259 let mut dev = ConsoleDevice::new(ConsoleSink::Writer(Box::new(sink)));
260 let mem = Arc::new(SliceGuestMemory::new(GuestAddress(0x4000_0000), 0x4000));
261 let q = &mut dev.queues_mut()[TX_QUEUE];
262 q.size = 8;
263 q.desc_table_addr = GuestAddress(0x4000_0000);
264 q.avail_ring_addr = GuestAddress(0x4000_0800);
265 q.used_ring_addr = GuestAddress(0x4000_1000);
266 q.ready = true;
267 mem.write(GuestAddress(0x4000_2000), b"hi\n").unwrap();
269 let base = 0x4000_0000u64;
270 mem.write_u32_le(GuestAddress(base), 0x4000_2000).unwrap();
271 mem.write_u32_le(GuestAddress(base + 4), 0).unwrap();
272 mem.write_u32_le(GuestAddress(base + 8), 3).unwrap();
273 mem.write_u16_le(GuestAddress(base + 12), 0).unwrap();
274 mem.write_u16_le(GuestAddress(base + 14), 0).unwrap();
275 mem.write_u16_le(GuestAddress(0x4000_0804), 0).unwrap();
276 mem.write_u16_le(GuestAddress(0x4000_0802), 1).unwrap();
277 dev.activate(mem.clone(), line()).unwrap();
278 dev.process_queue(TX_QUEUE as u16);
279 assert_eq!(buf.lock().as_slice(), b"hi\n");
280 }
281
282 #[test]
283 fn test_discard_sink_is_a_noop() {
284 let mut dev = ConsoleDevice::discard();
285 let mem = Arc::new(SliceGuestMemory::new(GuestAddress(0x4000_0000), 0x4000));
286 dev.activate(mem.clone(), line()).unwrap();
287 dev.process_queue(TX_QUEUE as u16); }
289
290 #[test]
292 fn test_constants_referenced() {
293 let _ = F_SIZE;
294 let _ = F_MULTIPORT;
295 let _ = Cursor::new(Vec::<u8>::new());
296 }
297}