Skip to main content

strat9_kernel/hardware/virtio/
console.rs

1// VirtIO Console Driver
2// Reference: VirtIO spec v1.2, Section 5.3 (Console Device)
3
4use 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    /// Creates a new instance.
94    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    /// Performs the write operation.
139    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    /// Performs the read operation.
159    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    /// Performs the reset operation.
168    fn reset(&mut self) {
169        unsafe {
170            (self.mmio as *mut u32).write_volatile(0);
171        }
172        core::hint::spin_loop();
173    }
174
175    /// Performs the add status operation.
176    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    /// Reads status.
184    fn read_status(&self) -> u8 {
185        unsafe { ((self.mmio + 0x14) as *const u8).read_volatile() }
186    }
187
188    /// Reads features.
189    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    /// Writes features.
198    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    /// Performs the notify queue operation.
206    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    /// Creates a new instance.
217    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    /// Performs the write operation.
275    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    /// Performs the read operation.
298    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    /// Performs the poll used operation.
316    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
324/// Performs the init operation.
325pub 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
363/// Performs the write operation.
364pub fn write(data: &[u8]) -> Result<usize, &'static str> {
365    CONSOLE_INSTANCE
366        .get()
367        .ok_or("Console not initialized")?
368        .write(data)
369}
370
371/// Performs the read operation.
372pub 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
379/// Returns whether available.
380pub fn is_available() -> bool {
381    CONSOLE_INITIALIZED.load(Ordering::Relaxed)
382}