strat9_kernel/hardware/virtio/
console.rs1use crate::{
5 arch::x86_64::pci::{self, Bar, ProbeCriteria},
6 memory::{allocate_zeroed_frame, phys_to_virt},
7};
8use alloc::{sync::Arc, vec::Vec};
9use core::sync::atomic::{AtomicBool, Ordering};
10use endian_num::Le;
11use spin::{Mutex, Once};
12
13const VIRTIO_RING_SIZE: usize = 8;
14const VIRTIO_CONSOLE_PORT_SIZE: usize = 256;
15
16pub struct VirtioConsole {
17 device: VirtioDevice,
18 ports: Mutex<Vec<VirtioConsolePort>>,
19}
20
21struct VirtioDevice {
22 mmio: usize,
23}
24
25struct VirtioConsolePort {
26 #[allow(dead_code)]
27 id: u16,
28 rx_queue: Virtqueue,
29 tx_queue: Virtqueue,
30 #[allow(dead_code)]
31 open: bool,
32}
33
34struct Virtqueue {
35 desc: *mut VirtqDesc,
36 avail: *mut VirtqAvail,
37 used: *mut VirtqUsed,
38 #[allow(dead_code)]
39 desc_phys: u64,
40 #[allow(dead_code)]
41 avail_phys: u64,
42 #[allow(dead_code)]
43 used_phys: u64,
44 buffer_phys: u64,
45 buffer_virt: *mut u8,
46 free: Vec<u16>,
47 last_used_idx: u16,
48}
49
50unsafe impl Send for Virtqueue {}
51
52#[repr(C)]
53#[derive(Clone, Copy)]
54struct VirtqDesc {
55 addr: Le<u64>,
56 len: Le<u32>,
57 flags: Le<u16>,
58 next: Le<u16>,
59}
60
61#[repr(C)]
62struct VirtqAvail {
63 flags: Le<u16>,
64 idx: Le<u16>,
65 ring: [Le<u16>; VIRTIO_RING_SIZE],
66}
67
68#[repr(C)]
69struct VirtqUsed {
70 flags: Le<u16>,
71 idx: Le<u16>,
72 ring: [VirtqUsedElem; VIRTIO_RING_SIZE],
73}
74
75#[repr(C)]
76#[derive(Clone, Copy)]
77struct VirtqUsedElem {
78 id: Le<u32>,
79 len: Le<u32>,
80}
81
82const VIRTIO_F_VERSION_1: u64 = 1 << 32;
83const VIRTIO_CONSOLE_F_MULTIPORT: u32 = 1;
84
85#[allow(dead_code)]
86const VIRTIO_STATUS_RESET: u8 = 0;
87const VIRTIO_STATUS_ACKNOWLEDGE: u8 = 1;
88const VIRTIO_STATUS_DRIVER: u8 = 2;
89const VIRTIO_STATUS_DRIVER_OK: u8 = 4;
90const VIRTIO_STATUS_FEATURES_OK: u8 = 8;
91
92impl VirtioConsole {
93 pub unsafe fn new(pci_dev: pci::PciDevice) -> Result<Self, &'static str> {
95 let bar = match pci_dev.read_bar(0) {
96 Some(Bar::Memory64 { addr, .. }) => addr,
97 _ => return Err("Invalid BAR"),
98 };
99
100 let mmio = phys_to_virt(bar) as usize;
101 let mut device = VirtioDevice { mmio };
102
103 device.reset();
104 device.add_status(VIRTIO_STATUS_ACKNOWLEDGE);
105 device.add_status(VIRTIO_STATUS_DRIVER);
106
107 let features = device.read_features();
108 let mut guest_features = VIRTIO_F_VERSION_1;
109 if (features & (1 << VIRTIO_CONSOLE_F_MULTIPORT)) != 0 {
110 guest_features |= 1 << VIRTIO_CONSOLE_F_MULTIPORT;
111 }
112 device.write_features(guest_features);
113 device.add_status(VIRTIO_STATUS_FEATURES_OK);
114
115 if (device.read_status() & VIRTIO_STATUS_FEATURES_OK) == 0 {
116 return Err("Features negotiation failed");
117 }
118
119 let mut ports = Vec::new();
120 let rx_queue = Virtqueue::new(&mut device, 0)?;
121 let tx_queue = Virtqueue::new(&mut device, 1)?;
122
123 ports.push(VirtioConsolePort {
124 id: 0,
125 rx_queue,
126 tx_queue,
127 open: true,
128 });
129
130 device.add_status(VIRTIO_STATUS_DRIVER_OK);
131
132 Ok(Self {
133 device,
134 ports: Mutex::new(ports),
135 })
136 }
137
138 pub fn write(&self, data: &[u8]) -> Result<usize, &'static str> {
140 let mut ports = self.ports.lock();
141 let port = ports.first_mut().ok_or("No console port")?;
142
143 for chunk in data.chunks(VIRTIO_CONSOLE_PORT_SIZE) {
144 port.tx_queue.write(chunk)?;
145 self.device.notify_queue(1);
146
147 loop {
148 if port.tx_queue.poll_used() {
149 break;
150 }
151 core::hint::spin_loop();
152 }
153 }
154
155 Ok(data.len())
156 }
157
158 pub fn read(&self, buf: &mut [u8]) -> Result<usize, &'static str> {
160 let mut ports = self.ports.lock();
161 let port = ports.first_mut().ok_or("No console port")?;
162 port.rx_queue.read(buf)
163 }
164}
165
166impl VirtioDevice {
167 fn reset(&mut self) {
169 unsafe {
170 (self.mmio as *mut u32).write_volatile(0);
171 }
172 core::hint::spin_loop();
173 }
174
175 fn add_status(&mut self, status: u8) {
177 unsafe {
178 let current = ((self.mmio + 0x14) as *const u8).read_volatile();
179 ((self.mmio + 0x14) as *mut u8).write_volatile(current | status);
180 }
181 }
182
183 fn read_status(&self) -> u8 {
185 unsafe { ((self.mmio + 0x14) as *const u8).read_volatile() }
186 }
187
188 fn read_features(&self) -> u64 {
190 unsafe {
191 let lo = (self.mmio as *const u32).read_volatile() as u64;
192 let hi = ((self.mmio + 4) as *const u32).read_volatile() as u64;
193 (hi << 32) | lo
194 }
195 }
196
197 fn write_features(&mut self, features: u64) {
199 unsafe {
200 (self.mmio as *mut u32).write_volatile((features & 0xFFFFFFFF) as u32);
201 ((self.mmio + 4) as *mut u32).write_volatile(((features >> 32) & 0xFFFFFFFF) as u32);
202 }
203 }
204
205 fn notify_queue(&self, queue: u16) {
207 unsafe {
208 let offset = ((self.mmio + 0x20) as *const u16).read_volatile() as usize;
209 let queue_notify = self.mmio + 0x50 + offset * 4;
210 (queue_notify as *mut u32).write_volatile(queue as u32);
211 }
212 }
213}
214
215impl Virtqueue {
216 fn new(device: &mut VirtioDevice, queue_idx: u16) -> Result<Self, &'static str> {
218 unsafe {
219 ((device.mmio + 0x16) as *mut u16).write_volatile(queue_idx);
220 let max_size = ((device.mmio + 0x18) as *const u16).read_volatile();
221 if max_size < VIRTIO_RING_SIZE as u16 {
222 return Err("Queue size too small");
223 }
224 ((device.mmio + 0x16) as *mut u16).write_volatile(VIRTIO_RING_SIZE as u16);
225
226 let desc_frame = allocate_zeroed_frame().ok_or("Failed to allocate desc")?;
227 let avail_frame = allocate_zeroed_frame().ok_or("Failed to allocate avail")?;
228 let used_frame = allocate_zeroed_frame().ok_or("Failed to allocate used")?;
229
230 let desc_phys = desc_frame.start_address.as_u64();
231 let avail_phys = avail_frame.start_address.as_u64();
232 let used_phys = used_frame.start_address.as_u64();
233
234 let desc_virt = phys_to_virt(desc_phys) as *mut VirtqDesc;
235 let avail_virt = phys_to_virt(avail_phys) as *mut VirtqAvail;
236 let used_virt = phys_to_virt(used_phys) as *mut VirtqUsed;
237
238 core::ptr::write_bytes(
239 desc_virt,
240 0,
241 VIRTIO_RING_SIZE * core::mem::size_of::<VirtqDesc>(),
242 );
243 core::ptr::write_bytes(avail_virt, 0, core::mem::size_of::<VirtqAvail>());
244 core::ptr::write_bytes(used_virt, 0, core::mem::size_of::<VirtqUsed>());
245
246 ((device.mmio + 0x10) as *mut u32).write_volatile((desc_phys & 0xFFFFFFFF) as u32);
247 ((device.mmio + 0x1A) as *mut u16).write_volatile(0xFFFF);
248
249 let buffer_frame = allocate_zeroed_frame().ok_or("Failed to allocate buffer")?;
250 let buffer_phys = buffer_frame.start_address.as_u64();
251 let buffer_virt = phys_to_virt(buffer_phys) as *mut u8;
252 core::ptr::write_bytes(buffer_virt, 0, 4096);
253
254 let mut free = Vec::with_capacity(VIRTIO_RING_SIZE);
255 for i in 0..VIRTIO_RING_SIZE {
256 free.push(i as u16);
257 }
258
259 Ok(Self {
260 desc: desc_virt,
261 avail: avail_virt,
262 used: used_virt,
263 desc_phys,
264 avail_phys,
265 used_phys,
266 buffer_phys,
267 buffer_virt,
268 free,
269 last_used_idx: 0,
270 })
271 }
272 }
273
274 fn write(&mut self, data: &[u8]) -> Result<(), &'static str> {
276 unsafe {
277 if self.free.is_empty() {
278 return Err("No free descriptors");
279 }
280 let desc_idx = self.free.pop().unwrap();
281
282 let desc = &mut *self.desc.add(desc_idx as usize);
283 core::ptr::copy_nonoverlapping(data.as_ptr(), self.buffer_virt, data.len());
284 desc.addr = Le::<u64>::from_ne(self.buffer_phys);
285 desc.len = Le::<u32>::from_ne(data.len() as u32);
286 desc.flags = Le::<u16>::from_ne(2u16);
287 desc.next = Le::<u16>::from_ne(0u16);
288
289 let avail = &mut *self.avail;
290 let idx = avail.idx.to_ne() as usize % VIRTIO_RING_SIZE;
291 avail.ring[idx] = Le::<u16>::from_ne(desc_idx);
292 avail.idx = Le::<u16>::from_ne(avail.idx.to_ne().wrapping_add(1));
293 }
294 Ok(())
295 }
296
297 fn read(&mut self, buf: &mut [u8]) -> Result<usize, &'static str> {
299 unsafe {
300 if self.last_used_idx == (*self.used).idx.to_ne() {
301 return Ok(0);
302 }
303
304 let idx = self.last_used_idx as usize % VIRTIO_RING_SIZE;
305 let elem = (*self.used).ring[idx];
306
307 let len = core::cmp::min(elem.len.to_ne() as usize, buf.len());
308 core::ptr::copy_nonoverlapping(self.buffer_virt, buf.as_mut_ptr(), len);
309
310 self.last_used_idx = self.last_used_idx.wrapping_add(1);
311 Ok(len)
312 }
313 }
314
315 fn poll_used(&mut self) -> bool {
317 unsafe { self.last_used_idx != (*self.used).idx.to_ne() }
318 }
319}
320
321static CONSOLE_INSTANCE: Once<Arc<VirtioConsole>> = Once::new();
322static CONSOLE_INITIALIZED: AtomicBool = AtomicBool::new(false);
323
324pub fn init() {
326 log::info!("[VirtIO-Console] Scanning for VirtIO Console devices...");
327
328 let candidates = pci::probe_all(ProbeCriteria {
329 vendor_id: Some(pci::vendor::VIRTIO),
330 device_id: Some(pci::device::VIRTIO_CONSOLE),
331 class_code: None,
332 subclass: None,
333 prog_if: None,
334 });
335
336 for pci_dev in candidates.into_iter() {
337 log::info!(
338 "VirtIO-Console: Found device at {:?} (VEN:{:04x} DEV:{:04x})",
339 pci_dev.address,
340 pci_dev.vendor_id,
341 pci_dev.device_id
342 );
343
344 pci_dev.enable_bus_master();
345
346 match unsafe { VirtioConsole::new(pci_dev) } {
347 Ok(console) => {
348 let arc = Arc::new(console);
349 CONSOLE_INSTANCE.call_once(|| arc.clone());
350 CONSOLE_INITIALIZED.store(true, Ordering::SeqCst);
351 log::info!("[VirtIO-Console] Initialized");
352 return;
353 }
354 Err(e) => {
355 log::warn!("VirtIO-Console: Failed to initialize device: {}", e);
356 }
357 }
358 }
359
360 log::info!("[VirtIO-Console] No device found");
361}
362
363pub fn write(data: &[u8]) -> Result<usize, &'static str> {
365 CONSOLE_INSTANCE
366 .get()
367 .ok_or("Console not initialized")?
368 .write(data)
369}
370
371pub fn read(buf: &mut [u8]) -> Result<usize, &'static str> {
373 CONSOLE_INSTANCE
374 .get()
375 .ok_or("Console not initialized")?
376 .read(buf)
377}
378
379pub fn is_available() -> bool {
381 CONSOLE_INITIALIZED.load(Ordering::Relaxed)
382}