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