Skip to main content

strat9_kernel/hardware/virtio/
gpu.rs

1// VirtIO GPU driver
2// Reference: VirtIO spec v1.2, Section 5.4 (GPU Device)
3
4use crate::{
5    arch::x86_64::pci::{self, Bar, ProbeCriteria},
6    memory::{self, allocate_zeroed_frame, phys_to_virt, PhysFrame},
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 = 64;
14const PAGE_SIZE: usize = 4096;
15const VIRTQ_PAYLOAD_ORDER: u8 = 6;
16const FLUSH_OPS_THRESHOLD: u32 = 64;
17
18pub struct VirtioGpu {
19    ctrl_queue: Mutex<Virtqueue>,
20    _cursor_queue: Mutex<Option<Virtqueue>>,
21    info: GpuInfo,
22    _framebuffer_pages: Vec<PhysFrame>,
23    framebuffer_segments: Vec<FramebufferSegment>,
24    framebuffer_size: usize,
25    dirty: Mutex<DirtyRect>,
26}
27
28struct VirtioDevice {
29    mmio: usize,
30    queue_notify_addr: usize,
31}
32
33struct Virtqueue {
34    desc: *mut VirtqDesc,
35    avail: *mut VirtqAvail,
36    used: *mut VirtqUsed,
37    queue_idx: u16,
38    queue_size: u16,
39    notify_addr: usize,
40    free_stack: [u16; VIRTIO_RING_SIZE],
41    free_len: usize,
42    last_used_idx: u16,
43    cmd_phys: u64,
44    cmd_virt: *mut u8,
45    payload_phys: u64,
46    payload_virt: *mut u8,
47    payload_capacity: usize,
48    resp_phys: u64,
49    resp_virt: *mut u8,
50}
51
52unsafe impl Send for Virtqueue {}
53
54#[derive(Clone, Copy)]
55struct FramebufferSegment {
56    virt: *mut u8,
57    len: usize,
58}
59
60unsafe impl Send for FramebufferSegment {}
61unsafe impl Sync for FramebufferSegment {}
62
63#[derive(Clone, Copy)]
64struct DirtyRect {
65    valid: bool,
66    x0: u32,
67    y0: u32,
68    x1: u32,
69    y1: u32,
70    pending_ops: u32,
71}
72
73impl DirtyRect {
74    /// Performs the empty operation.
75    const fn empty() -> Self {
76        Self {
77            valid: false,
78            x0: 0,
79            y0: 0,
80            x1: 0,
81            y1: 0,
82            pending_ops: 0,
83        }
84    }
85
86    /// Performs the include operation.
87    fn include(&mut self, x: u32, y: u32, width: u32, height: u32) {
88        if width == 0 || height == 0 {
89            return;
90        }
91        let x1 = x.saturating_add(width);
92        let y1 = y.saturating_add(height);
93        if !self.valid {
94            self.valid = true;
95            self.x0 = x;
96            self.y0 = y;
97            self.x1 = x1;
98            self.y1 = y1;
99        } else {
100            self.x0 = self.x0.min(x);
101            self.y0 = self.y0.min(y);
102            self.x1 = self.x1.max(x1);
103            self.y1 = self.y1.max(y1);
104        }
105        self.pending_ops = self.pending_ops.saturating_add(1);
106    }
107}
108
109#[repr(C)]
110#[derive(Clone, Copy)]
111struct VirtqDesc {
112    addr: Le<u64>,
113    len: Le<u32>,
114    flags: Le<u16>,
115    next: Le<u16>,
116}
117
118#[repr(C)]
119struct VirtqAvail {
120    flags: Le<u16>,
121    idx: Le<u16>,
122    ring: [Le<u16>; VIRTIO_RING_SIZE],
123}
124
125#[repr(C)]
126struct VirtqUsed {
127    flags: Le<u16>,
128    idx: Le<u16>,
129    ring: [VirtqUsedElem; VIRTIO_RING_SIZE],
130}
131
132#[repr(C)]
133#[derive(Clone, Copy)]
134struct VirtqUsedElem {
135    id: Le<u32>,
136    len: Le<u32>,
137}
138
139#[derive(Clone, Copy)]
140pub struct GpuInfo {
141    pub width: u32,
142    pub height: u32,
143    pub stride: u32,
144    pub framebuffer_phys: u64,
145    pub framebuffer_virt: *mut u8,
146}
147
148unsafe impl Send for GpuInfo {}
149unsafe impl Sync for GpuInfo {}
150
151const VIRTIO_F_VERSION_1: u64 = 1 << 32;
152const VIRTIO_GPU_F_EDID: u32 = 1;
153
154const VIRTIO_STATUS_ACKNOWLEDGE: u8 = 1;
155const VIRTIO_STATUS_DRIVER: u8 = 2;
156const VIRTIO_STATUS_DRIVER_OK: u8 = 4;
157const VIRTIO_STATUS_FEATURES_OK: u8 = 8;
158
159const VIRTIO_GPU_CMD_GET_DISPLAY_INFO: u32 = 0x0100;
160const VIRTIO_GPU_CMD_RESOURCE_CREATE_2D: u32 = 0x0101;
161const VIRTIO_GPU_CMD_SET_SCANOUT: u32 = 0x0103;
162const VIRTIO_GPU_CMD_RESOURCE_FLUSH: u32 = 0x0104;
163const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D: u32 = 0x0105;
164const VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING: u32 = 0x0106;
165
166const VIRTIO_GPU_RESP_OK_NODATA: u32 = 0x1100;
167const VIRTIO_GPU_RESP_OK_DISPLAY_INFO: u32 = 0x1101;
168
169const VIRTQ_DESC_F_NEXT: u16 = 1;
170const VIRTQ_DESC_F_WRITE: u16 = 2;
171
172const VIRTIO_GPU_FORMAT_X8R8G8B8: u32 = 1;
173
174#[repr(C)]
175#[derive(Clone, Copy, Default)]
176struct GpuRect {
177    x: u32,
178    y: u32,
179    width: u32,
180    height: u32,
181}
182
183#[repr(C)]
184#[derive(Clone, Copy)]
185struct CtrlHeader {
186    cmd_and_flags: u32,
187    fence_id: u64,
188    ctx_id: u32,
189    _padding: u32,
190}
191
192#[repr(C)]
193#[derive(Clone, Copy)]
194struct CmdGetDisplayInfo {
195    hdr: CtrlHeader,
196    scanout_id: u32,
197    _padding: [u32; 3],
198}
199
200#[repr(C)]
201#[derive(Clone, Copy)]
202struct RespDisplayInfo {
203    hdr: CtrlHeader,
204    rect: GpuRect,
205    enabled: u32,
206    _padding: [u32; 3],
207}
208
209#[repr(C)]
210#[derive(Clone, Copy)]
211struct CmdResourceCreate2d {
212    hdr: CtrlHeader,
213    resource_id: u32,
214    format: u32,
215    width: u32,
216    height: u32,
217}
218
219#[repr(C)]
220#[derive(Clone, Copy)]
221struct CmdResourceAttachBacking {
222    hdr: CtrlHeader,
223    resource_id: u32,
224    nr_entries: u32,
225}
226
227#[repr(C)]
228#[derive(Clone, Copy)]
229struct MemEntry {
230    addr: Le<u64>,
231    length: Le<u32>,
232    _padding: u32,
233}
234
235#[repr(C)]
236#[derive(Clone, Copy)]
237struct CmdSetScanout {
238    hdr: CtrlHeader,
239    rect: GpuRect,
240    scanout_id: u32,
241    resource_id: u32,
242}
243
244#[repr(C)]
245#[derive(Clone, Copy)]
246struct CmdResourceFlush {
247    hdr: CtrlHeader,
248    rect: GpuRect,
249    resource_id: u32,
250    _padding: u32,
251}
252
253#[repr(C)]
254#[derive(Clone, Copy)]
255struct CmdTransferToHost2d {
256    hdr: CtrlHeader,
257    rect: GpuRect,
258    offset: u64,
259    resource_id: u32,
260    _padding: u32,
261}
262
263impl VirtioGpu {
264    /// Creates a new instance.
265    pub unsafe fn new(pci_dev: pci::PciDevice) -> Result<Self, &'static str> {
266        let bar = match pci_dev.read_bar(0) {
267            Some(Bar::Memory64 { addr, .. }) => addr,
268            _ => return Err("Invalid BAR"),
269        };
270
271        let mmio = phys_to_virt(bar) as usize;
272        let notify_mult = unsafe { ((mmio + 0x20) as *const u16).read_volatile() as usize };
273        let queue_notify_addr = mmio + 0x50 + notify_mult * 4;
274        let mut device = VirtioDevice {
275            mmio,
276            queue_notify_addr,
277        };
278
279        device.reset();
280        device.add_status(VIRTIO_STATUS_ACKNOWLEDGE);
281        device.add_status(VIRTIO_STATUS_DRIVER);
282
283        let features = device.read_features();
284        let mut guest_features = VIRTIO_F_VERSION_1;
285        if (features & (1 << VIRTIO_GPU_F_EDID)) != 0 {
286            guest_features |= 1 << VIRTIO_GPU_F_EDID;
287        }
288        device.write_features(guest_features);
289        device.add_status(VIRTIO_STATUS_FEATURES_OK);
290
291        if (device.read_status() & VIRTIO_STATUS_FEATURES_OK) == 0 {
292            return Err("Features negotiation failed");
293        }
294
295        let ctrl_queue = Virtqueue::new(&mut device, 0)?;
296
297        device.add_status(VIRTIO_STATUS_DRIVER_OK);
298
299        let mut gpu = Self {
300            ctrl_queue: Mutex::new(ctrl_queue),
301            _cursor_queue: Mutex::new(None),
302            info: GpuInfo {
303                width: 1024,
304                height: 768,
305                stride: 1024 * 4,
306                framebuffer_phys: 0,
307                framebuffer_virt: core::ptr::null_mut(),
308            },
309            _framebuffer_pages: Vec::new(),
310            framebuffer_segments: Vec::new(),
311            framebuffer_size: 0,
312            dirty: Mutex::new(DirtyRect::empty()),
313        };
314
315        gpu.init_display()?;
316        Ok(gpu)
317    }
318
319    /// Initializes display.
320    fn init_display(&mut self) -> Result<(), &'static str> {
321        self.get_display_info()?;
322
323        let framebuffer_size = self.info.stride as usize * self.info.height as usize;
324        if framebuffer_size == 0 {
325            return Err("Display reports zero-sized framebuffer");
326        }
327        let page_count = (framebuffer_size + PAGE_SIZE - 1) / PAGE_SIZE;
328        let mut pages = Vec::with_capacity(page_count);
329        let mut entries = Vec::with_capacity(page_count);
330        let mut segments = Vec::with_capacity(page_count);
331
332        for _ in 0..page_count {
333            let frame = allocate_zeroed_frame().ok_or("Failed to allocate framebuffer page")?;
334            let phys = frame.start_address.as_u64();
335            pages.push(frame);
336            entries.push(MemEntry {
337                addr: Le::<u64>::from_ne(phys),
338                length: Le::<u32>::from_ne(PAGE_SIZE as u32),
339                _padding: 0,
340            });
341            segments.push(FramebufferSegment {
342                virt: phys_to_virt(phys) as *mut u8,
343                len: PAGE_SIZE,
344            });
345        }
346
347        if let Some(last) = entries.last_mut() {
348            let rem = framebuffer_size % PAGE_SIZE;
349            if rem != 0 {
350                last.length = Le::<u32>::from_ne(rem as u32);
351            }
352        }
353        if let Some(last) = segments.last_mut() {
354            let rem = framebuffer_size % PAGE_SIZE;
355            if rem != 0 {
356                last.len = rem;
357            }
358        }
359
360        self.info.framebuffer_phys = entries.first().map(|e| e.addr.to_ne()).unwrap_or(0);
361        self.info.framebuffer_virt = segments
362            .first()
363            .map(|s| s.virt)
364            .unwrap_or(core::ptr::null_mut());
365        self.framebuffer_size = framebuffer_size;
366        self._framebuffer_pages = pages;
367        self.framebuffer_segments = segments;
368
369        let resource_id = 1;
370        self.resource_create_2d(resource_id, self.info.width, self.info.height)?;
371        self.resource_attach_backing(resource_id, &entries)?;
372        self.set_scanout(0, resource_id)?;
373        self.transfer_to_host_2d(resource_id, 0, 0, self.info.width, self.info.height)?;
374        self.resource_flush(resource_id, 0, 0, self.info.width, self.info.height)?;
375
376        log::info!(
377            "VirtIO GPU: {}x{} @ {} bpp, framebuffer {} pages",
378            self.info.width,
379            self.info.height,
380            32,
381            page_count
382        );
383
384        Ok(())
385    }
386
387    /// Returns display info.
388    fn get_display_info(&mut self) -> Result<(), &'static str> {
389        let cmd = CmdGetDisplayInfo {
390            hdr: CtrlHeader {
391                cmd_and_flags: VIRTIO_GPU_CMD_GET_DISPLAY_INFO,
392                fence_id: 0,
393                ctx_id: 0,
394                _padding: 0,
395            },
396            scanout_id: 0,
397            _padding: [0; 3],
398        };
399
400        let resp: RespDisplayInfo = self.send_command(&cmd)?;
401        if resp.hdr.cmd_and_flags != VIRTIO_GPU_RESP_OK_DISPLAY_INFO {
402            return Err("GET_DISPLAY_INFO failed");
403        }
404
405        if resp.enabled != 0 {
406            // Clamp to sane limits to prevent buddy exhaustion if the GPU
407            // returns garbage dimensions.
408            const MAX_WIDTH: u32 = 3840;
409            const MAX_HEIGHT: u32 = 2160;
410            let w = resp.rect.width.min(MAX_WIDTH);
411            let h = resp.rect.height.min(MAX_HEIGHT);
412            if w == 0 || h == 0 {
413                return Err("GET_DISPLAY_INFO: zero dimensions");
414            }
415            self.info.width = w;
416            self.info.height = h;
417            self.info.stride = w * 4;
418        }
419
420        Ok(())
421    }
422
423    /// Performs the resource create 2d operation.
424    fn resource_create_2d(
425        &self,
426        resource_id: u32,
427        width: u32,
428        height: u32,
429    ) -> Result<(), &'static str> {
430        let cmd = CmdResourceCreate2d {
431            hdr: CtrlHeader {
432                cmd_and_flags: VIRTIO_GPU_CMD_RESOURCE_CREATE_2D,
433                fence_id: 0,
434                ctx_id: 0,
435                _padding: 0,
436            },
437            resource_id,
438            format: VIRTIO_GPU_FORMAT_X8R8G8B8,
439            width,
440            height,
441        };
442
443        let resp: CtrlHeader = self.send_command(&cmd)?;
444        if resp.cmd_and_flags != VIRTIO_GPU_RESP_OK_NODATA {
445            return Err("RESOURCE_CREATE_2D failed");
446        }
447        Ok(())
448    }
449
450    /// Performs the resource attach backing operation.
451    fn resource_attach_backing(
452        &self,
453        resource_id: u32,
454        entries: &[MemEntry],
455    ) -> Result<(), &'static str> {
456        if entries.is_empty() {
457            return Err("No backing entries");
458        }
459        let cmd = CmdResourceAttachBacking {
460            hdr: CtrlHeader {
461                cmd_and_flags: VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING,
462                fence_id: 0,
463                ctx_id: 0,
464                _padding: 0,
465            },
466            resource_id,
467            nr_entries: entries.len() as u32,
468        };
469
470        let payload = unsafe {
471            core::slice::from_raw_parts(
472                entries.as_ptr() as *const u8,
473                entries.len() * core::mem::size_of::<MemEntry>(),
474            )
475        };
476        let resp: CtrlHeader = self.send_command_with_payload(&cmd, Some(payload))?;
477        if resp.cmd_and_flags != VIRTIO_GPU_RESP_OK_NODATA {
478            return Err("RESOURCE_ATTACH_BACKING failed");
479        }
480        Ok(())
481    }
482
483    /// Sets scanout.
484    fn set_scanout(&self, scanout_id: u32, resource_id: u32) -> Result<(), &'static str> {
485        let cmd = CmdSetScanout {
486            hdr: CtrlHeader {
487                cmd_and_flags: VIRTIO_GPU_CMD_SET_SCANOUT,
488                fence_id: 0,
489                ctx_id: 0,
490                _padding: 0,
491            },
492            rect: GpuRect {
493                x: 0,
494                y: 0,
495                width: self.info.width,
496                height: self.info.height,
497            },
498            scanout_id,
499            resource_id,
500        };
501
502        let resp: CtrlHeader = self.send_command(&cmd)?;
503        if resp.cmd_and_flags != VIRTIO_GPU_RESP_OK_NODATA {
504            return Err("SET_SCANOUT failed");
505        }
506        Ok(())
507    }
508
509    /// Performs the transfer to host 2d operation.
510    fn transfer_to_host_2d(
511        &self,
512        resource_id: u32,
513        x: u32,
514        y: u32,
515        width: u32,
516        height: u32,
517    ) -> Result<(), &'static str> {
518        let cmd = CmdTransferToHost2d {
519            hdr: CtrlHeader {
520                cmd_and_flags: VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D,
521                fence_id: 0,
522                ctx_id: 0,
523                _padding: 0,
524            },
525            rect: GpuRect {
526                x,
527                y,
528                width,
529                height,
530            },
531            offset: 0,
532            resource_id,
533            _padding: 0,
534        };
535
536        let resp: CtrlHeader = self.send_command(&cmd)?;
537        if resp.cmd_and_flags != VIRTIO_GPU_RESP_OK_NODATA {
538            return Err("TRANSFER_TO_HOST_2D failed");
539        }
540        Ok(())
541    }
542
543    /// Performs the resource flush operation.
544    fn resource_flush(
545        &self,
546        resource_id: u32,
547        x: u32,
548        y: u32,
549        width: u32,
550        height: u32,
551    ) -> Result<(), &'static str> {
552        let cmd = CmdResourceFlush {
553            hdr: CtrlHeader {
554                cmd_and_flags: VIRTIO_GPU_CMD_RESOURCE_FLUSH,
555                fence_id: 0,
556                ctx_id: 0,
557                _padding: 0,
558            },
559            rect: GpuRect {
560                x,
561                y,
562                width,
563                height,
564            },
565            resource_id,
566            _padding: 0,
567        };
568
569        let resp: CtrlHeader = self.send_command(&cmd)?;
570        if resp.cmd_and_flags != VIRTIO_GPU_RESP_OK_NODATA {
571            return Err("RESOURCE_FLUSH failed");
572        }
573        Ok(())
574    }
575
576    /// Performs the send command operation.
577    fn send_command<T: Copy, R: Copy>(&self, cmd: &T) -> Result<R, &'static str> {
578        self.send_command_with_payload::<T, R>(cmd, None)
579    }
580
581    /// Performs the send command with payload operation.
582    fn send_command_with_payload<T: Copy, R: Copy>(
583        &self,
584        cmd: &T,
585        payload: Option<&[u8]>,
586    ) -> Result<R, &'static str> {
587        let cmd_size = core::mem::size_of::<T>();
588        let resp_size = core::mem::size_of::<R>();
589        if cmd_size > PAGE_SIZE || resp_size > PAGE_SIZE {
590            return Err("Command or response too large");
591        }
592
593        let payload_len = payload.map_or(0, |p| p.len());
594        let mut ctrl_queue = self.ctrl_queue.lock();
595        if payload_len > ctrl_queue.payload_capacity {
596            return Err("Payload too large");
597        }
598
599        let needed_desc = if payload_len > 0 { 3 } else { 2 };
600        if ctrl_queue.free_len < needed_desc {
601            return Err("Not enough free descriptors");
602        }
603
604        let head_idx = ctrl_queue.pop_desc().ok_or("Missing descriptor")?;
605        let middle_idx = if payload_len > 0 {
606            Some(ctrl_queue.pop_desc().ok_or("Missing payload descriptor")?)
607        } else {
608            None
609        };
610        let resp_idx = ctrl_queue.pop_desc().ok_or("Missing response descriptor")?;
611
612        unsafe {
613            core::ptr::copy_nonoverlapping(
614                cmd as *const _ as *const u8,
615                ctrl_queue.cmd_virt,
616                cmd_size,
617            );
618            if let Some(data) = payload {
619                core::ptr::copy_nonoverlapping(data.as_ptr(), ctrl_queue.payload_virt, data.len());
620            }
621
622            let head_desc = &mut *ctrl_queue.desc.add(head_idx as usize);
623            head_desc.addr = Le::<u64>::from_ne(ctrl_queue.cmd_phys);
624            head_desc.len = Le::<u32>::from_ne(cmd_size as u32);
625            head_desc.flags = Le::<u16>::from_ne(VIRTQ_DESC_F_NEXT);
626            head_desc.next = Le::<u16>::from_ne(middle_idx.unwrap_or(resp_idx));
627
628            if let Some(mid) = middle_idx {
629                let data_desc = &mut *ctrl_queue.desc.add(mid as usize);
630                data_desc.addr = Le::<u64>::from_ne(ctrl_queue.payload_phys);
631                data_desc.len = Le::<u32>::from_ne(payload_len as u32);
632                data_desc.flags = Le::<u16>::from_ne(VIRTQ_DESC_F_NEXT);
633                data_desc.next = Le::<u16>::from_ne(resp_idx);
634            }
635
636            let resp_desc = &mut *ctrl_queue.desc.add(resp_idx as usize);
637            resp_desc.addr = Le::<u64>::from_ne(ctrl_queue.resp_phys);
638            resp_desc.len = Le::<u32>::from_ne(resp_size as u32);
639            resp_desc.flags = Le::<u16>::from_ne(VIRTQ_DESC_F_WRITE);
640            resp_desc.next = Le::<u16>::from_ne(0u16);
641
642            let avail = &mut *ctrl_queue.avail;
643            let ring_idx = (avail.idx.to_ne() as usize) % (ctrl_queue.queue_size as usize);
644            avail.ring[ring_idx] = Le::<u16>::from_ne(head_idx);
645            avail.idx = Le::<u16>::from_ne(avail.idx.to_ne().wrapping_add(1));
646        }
647
648        unsafe {
649            (ctrl_queue.notify_addr as *mut u32).write_volatile(ctrl_queue.queue_idx as u32);
650        }
651
652        let mut spins: u32 = 0;
653        loop {
654            unsafe {
655                let used = &*ctrl_queue.used;
656                if ctrl_queue.last_used_idx != used.idx.to_ne() {
657                    let idx =
658                        (ctrl_queue.last_used_idx as usize) % (ctrl_queue.queue_size as usize);
659                    let elem = used.ring[idx];
660                    ctrl_queue.last_used_idx = ctrl_queue.last_used_idx.wrapping_add(1);
661                    if elem.id.to_ne() as u16 == head_idx {
662                        break;
663                    }
664                }
665            }
666
667            spins = spins.wrapping_add(1);
668            if (spins & 0x3ff) == 0 {
669                crate::process::yield_task();
670            } else {
671                core::hint::spin_loop();
672            }
673        }
674
675        let response = unsafe { core::ptr::read_unaligned(ctrl_queue.resp_virt as *const R) };
676
677        ctrl_queue.push_desc(head_idx);
678        if let Some(mid) = middle_idx {
679            ctrl_queue.push_desc(mid);
680        }
681        ctrl_queue.push_desc(resp_idx);
682
683        Ok(response)
684    }
685
686    /// Performs the copy to backing operation.
687    fn copy_to_backing(
688        &self,
689        mut src: *const u8,
690        mut dst_offset: usize,
691        mut len: usize,
692    ) -> Result<(), &'static str> {
693        let end = dst_offset.checked_add(len).ok_or("Copy overflow")?;
694        if end > self.framebuffer_size {
695            return Err("Copy out of bounds");
696        }
697        while len > 0 {
698            let seg_idx = dst_offset / PAGE_SIZE;
699            let seg_off = dst_offset % PAGE_SIZE;
700            let seg = self
701                .framebuffer_segments
702                .get(seg_idx)
703                .ok_or("Segment out of bounds")?;
704            if seg_off >= seg.len {
705                return Err("Segment offset out of bounds");
706            }
707            let chunk = core::cmp::min(len, seg.len - seg_off);
708            unsafe {
709                core::ptr::copy_nonoverlapping(src, seg.virt.add(seg_off), chunk);
710            }
711            unsafe {
712                src = src.add(chunk);
713            }
714            dst_offset += chunk;
715            len -= chunk;
716        }
717        Ok(())
718    }
719
720    /// Performs the present from linear operation.
721    pub fn present_from_linear(
722        &self,
723        src: *const u8,
724        src_stride: u32,
725        x: u32,
726        y: u32,
727        width: u32,
728        height: u32,
729    ) -> Result<(), &'static str> {
730        if src.is_null() {
731            return Err("Invalid source pointer");
732        }
733        if width == 0 || height == 0 {
734            return Ok(());
735        }
736        if x >= self.info.width || y >= self.info.height {
737            return Ok(());
738        }
739
740        let width = width.min(self.info.width - x);
741        let height = height.min(self.info.height - y);
742        let src_stride = src_stride as usize;
743        let dst_stride = self.info.stride as usize;
744        let row_bytes = (width as usize).checked_mul(4).ok_or("Row overflow")?;
745
746        for row in 0..height as usize {
747            let src_off = (y as usize + row)
748                .checked_mul(src_stride)
749                .and_then(|o| o.checked_add(x as usize * 4))
750                .ok_or("Source offset overflow")?;
751            let dst_off = (y as usize + row)
752                .checked_mul(dst_stride)
753                .and_then(|o| o.checked_add(x as usize * 4))
754                .ok_or("Destination offset overflow")?;
755            let src_row = unsafe { src.add(src_off) };
756            self.copy_to_backing(src_row, dst_off, row_bytes)?;
757        }
758
759        self.transfer_to_host_2d(1, x, y, width, height)?;
760        self.resource_flush(1, x, y, width, height)?;
761        Ok(())
762    }
763
764    /// Performs the info operation.
765    pub fn info(&self) -> GpuInfo {
766        self.info
767    }
768
769    /// Performs the flush operation.
770    pub fn flush(&self, x: u32, y: u32, width: u32, height: u32) {
771        if width == 0 || height == 0 {
772            return;
773        }
774        let mut dirty = self.dirty.lock();
775        dirty.include(x, y, width, height);
776        if dirty.pending_ops < FLUSH_OPS_THRESHOLD {
777            return;
778        }
779        let x0 = dirty.x0;
780        let y0 = dirty.y0;
781        let w = dirty.x1.saturating_sub(dirty.x0);
782        let h = dirty.y1.saturating_sub(dirty.y0);
783        *dirty = DirtyRect::empty();
784        drop(dirty);
785        let _ = self.transfer_to_host_2d(1, x0, y0, w, h);
786        let _ = self.resource_flush(1, x0, y0, w, h);
787    }
788
789    /// Performs the flush now operation.
790    pub fn flush_now(&self) {
791        let (x0, y0, w, h) = {
792            let mut dirty = self.dirty.lock();
793            if !dirty.valid {
794                return;
795            }
796            let x0 = dirty.x0;
797            let y0 = dirty.y0;
798            let w = dirty.x1.saturating_sub(dirty.x0);
799            let h = dirty.y1.saturating_sub(dirty.y0);
800            *dirty = DirtyRect::empty();
801            (x0, y0, w, h)
802        };
803        let _ = self.transfer_to_host_2d(1, x0, y0, w, h);
804        let _ = self.resource_flush(1, x0, y0, w, h);
805    }
806}
807
808impl VirtioDevice {
809    /// Performs the reset operation.
810    fn reset(&mut self) {
811        unsafe {
812            (self.mmio as *mut u32).write_volatile(0);
813        }
814        core::hint::spin_loop();
815    }
816
817    /// Performs the add status operation.
818    fn add_status(&mut self, status: u8) {
819        unsafe {
820            let current = ((self.mmio + 0x14) as *const u8).read_volatile();
821            ((self.mmio + 0x14) as *mut u8).write_volatile(current | status);
822        }
823    }
824
825    /// Reads status.
826    fn read_status(&self) -> u8 {
827        unsafe { ((self.mmio + 0x14) as *const u8).read_volatile() }
828    }
829
830    /// Reads features.
831    fn read_features(&self) -> u64 {
832        unsafe {
833            let lo = (self.mmio as *const u32).read_volatile() as u64;
834            let hi = ((self.mmio + 4) as *const u32).read_volatile() as u64;
835            (hi << 32) | lo
836        }
837    }
838
839    /// Writes features.
840    fn write_features(&mut self, features: u64) {
841        unsafe {
842            (self.mmio as *mut u32).write_volatile((features & 0xFFFF_FFFF) as u32);
843            ((self.mmio + 4) as *mut u32).write_volatile(((features >> 32) & 0xFFFF_FFFF) as u32);
844        }
845    }
846}
847
848impl Virtqueue {
849    /// Creates a new instance.
850    fn new(device: &mut VirtioDevice, queue_idx: u16) -> Result<Self, &'static str> {
851        unsafe {
852            ((device.mmio + 0x16) as *mut u16).write_volatile(queue_idx);
853            let max_size = ((device.mmio + 0x18) as *const u16).read_volatile() as usize;
854            if max_size == 0 {
855                return Err("Queue size is zero");
856            }
857
858            let queue_size = core::cmp::min(max_size, VIRTIO_RING_SIZE) as u16;
859            ((device.mmio + 0x16) as *mut u16).write_volatile(queue_size);
860
861            let desc_frame = allocate_zeroed_frame().ok_or("Failed to allocate desc")?;
862            let avail_frame = allocate_zeroed_frame().ok_or("Failed to allocate avail")?;
863            let used_frame = allocate_zeroed_frame().ok_or("Failed to allocate used")?;
864            let cmd_frame = allocate_zeroed_frame().ok_or("Failed to allocate command buffer")?;
865            let resp_frame = allocate_zeroed_frame().ok_or("Failed to allocate response buffer")?;
866
867            let payload_frame = crate::sync::with_irqs_disabled(|token| {
868                memory::allocate_phys_contiguous(token, VIRTQ_PAYLOAD_ORDER)
869            })
870            .map_err(|_| "Failed to allocate payload buffer")?;
871
872            let desc_phys = desc_frame.start_address.as_u64();
873            let avail_phys = avail_frame.start_address.as_u64();
874            let used_phys = used_frame.start_address.as_u64();
875            let cmd_phys = cmd_frame.start_address.as_u64();
876            let payload_phys = payload_frame.start_address.as_u64();
877            let resp_phys = resp_frame.start_address.as_u64();
878
879            let desc_virt = phys_to_virt(desc_phys) as *mut VirtqDesc;
880            let avail_virt = phys_to_virt(avail_phys) as *mut VirtqAvail;
881            let used_virt = phys_to_virt(used_phys) as *mut VirtqUsed;
882            let cmd_virt = phys_to_virt(cmd_phys) as *mut u8;
883            let payload_virt = phys_to_virt(payload_phys) as *mut u8;
884            let resp_virt = phys_to_virt(resp_phys) as *mut u8;
885
886            core::ptr::write_bytes(
887                desc_virt as *mut u8,
888                0,
889                core::mem::size_of::<VirtqDesc>() * VIRTIO_RING_SIZE,
890            );
891            core::ptr::write_bytes(avail_virt as *mut u8, 0, core::mem::size_of::<VirtqAvail>());
892            core::ptr::write_bytes(used_virt as *mut u8, 0, core::mem::size_of::<VirtqUsed>());
893            core::ptr::write_bytes(payload_virt, 0, PAGE_SIZE << (VIRTQ_PAYLOAD_ORDER as usize));
894
895            ((device.mmio + 0x10) as *mut u32).write_volatile((desc_phys & 0xFFFF_FFFF) as u32);
896            ((device.mmio + 0x1A) as *mut u16).write_volatile(0xFFFF);
897
898            let mut free_stack = [0u16; VIRTIO_RING_SIZE];
899            for i in 0..(queue_size as usize) {
900                free_stack[i] = i as u16;
901            }
902
903            Ok(Self {
904                desc: desc_virt,
905                avail: avail_virt,
906                used: used_virt,
907                queue_idx,
908                queue_size,
909                notify_addr: device.queue_notify_addr,
910                free_stack,
911                free_len: queue_size as usize,
912                last_used_idx: 0,
913                cmd_phys,
914                cmd_virt,
915                payload_phys,
916                payload_virt,
917                payload_capacity: PAGE_SIZE << (VIRTQ_PAYLOAD_ORDER as usize),
918                resp_phys,
919                resp_virt,
920            })
921        }
922    }
923
924    /// Performs the pop desc operation.
925    fn pop_desc(&mut self) -> Option<u16> {
926        if self.free_len == 0 {
927            None
928        } else {
929            self.free_len -= 1;
930            Some(self.free_stack[self.free_len])
931        }
932    }
933
934    /// Performs the push desc operation.
935    fn push_desc(&mut self, idx: u16) {
936        if self.free_len < self.free_stack.len() {
937            self.free_stack[self.free_len] = idx;
938            self.free_len += 1;
939        }
940    }
941}
942
943static GPU_INSTANCE: Once<Arc<VirtioGpu>> = Once::new();
944static GPU_INITIALIZED: AtomicBool = AtomicBool::new(false);
945
946/// Performs the init operation.
947pub fn init() {
948    log::info!("[VirtIO-GPU] Scanning for VirtIO GPU devices...");
949
950    let candidates = pci::probe_all(ProbeCriteria {
951        vendor_id: Some(pci::vendor::VIRTIO),
952        device_id: Some(pci::device::VIRTIO_GPU),
953        class_code: None,
954        subclass: None,
955        prog_if: None,
956    });
957
958    for pci_dev in candidates.into_iter() {
959        log::info!(
960            "VirtIO-GPU: Found device at {:?} (VEN:{:04x} DEV:{:04x})",
961            pci_dev.address,
962            pci_dev.vendor_id,
963            pci_dev.device_id
964        );
965
966        pci_dev.enable_bus_master();
967
968        match unsafe { VirtioGpu::new(pci_dev) } {
969            Ok(gpu) => {
970                let arc = Arc::new(gpu);
971                GPU_INSTANCE.call_once(|| arc.clone());
972                GPU_INITIALIZED.store(true, Ordering::SeqCst);
973
974                let info = arc.info();
975                log::info!(
976                    "[VirtIO-GPU] Initialized: {}x{} @ 32bpp",
977                    info.width,
978                    info.height
979                );
980                return;
981            }
982            Err(e) => {
983                log::warn!("VirtIO-GPU: Failed to initialize device: {}", e);
984            }
985        }
986    }
987
988    log::info!("[VirtIO-GPU] No device found");
989}
990
991/// Returns gpu.
992pub fn get_gpu() -> Option<Arc<VirtioGpu>> {
993    GPU_INSTANCE.get().cloned()
994}
995
996/// Returns whether available.
997pub fn is_available() -> bool {
998    GPU_INITIALIZED.load(Ordering::Relaxed)
999}
1000
1001/// Returns framebuffer info.
1002pub fn get_framebuffer_info() -> Option<GpuInfo> {
1003    GPU_INSTANCE.get().map(|gpu| gpu.info())
1004}