1use 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
20struct BackingState {
27 segments: Vec<FramebufferSegment>,
29 size: usize,
31 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn using_external_backing(&self) -> bool {
811 self.backing.lock().external
812 }
813
814 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 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 pub fn info(&self) -> GpuInfo {
884 self.info
885 }
886
887 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 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 fn reset(&mut self) {
929 unsafe {
930 (self.mmio as *mut u32).write_volatile(0);
931 }
932 core::hint::spin_loop();
933 }
934
935 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 fn read_status(&self) -> u8 {
945 unsafe { ((self.mmio + 0x14) as *const u8).read_volatile() }
946 }
947
948 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 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 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 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 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
1064pub 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
1109pub fn get_gpu() -> Option<Arc<VirtioGpu>> {
1111 GPU_INSTANCE.get().cloned()
1112}
1113
1114pub fn is_available() -> bool {
1116 GPU_INITIALIZED.load(Ordering::Relaxed)
1117}
1118
1119pub fn get_framebuffer_info() -> Option<GpuInfo> {
1121 GPU_INSTANCE.get().map(|gpu| gpu.info())
1122}