1use 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn info(&self) -> GpuInfo {
766 self.info
767 }
768
769 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 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 fn reset(&mut self) {
811 unsafe {
812 (self.mmio as *mut u32).write_volatile(0);
813 }
814 core::hint::spin_loop();
815 }
816
817 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 fn read_status(&self) -> u8 {
827 unsafe { ((self.mmio + 0x14) as *const u8).read_volatile() }
828 }
829
830 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 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 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 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 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
946pub 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
991pub fn get_gpu() -> Option<Arc<VirtioGpu>> {
993 GPU_INSTANCE.get().cloned()
994}
995
996pub fn is_available() -> bool {
998 GPU_INITIALIZED.load(Ordering::Relaxed)
999}
1000
1001pub fn get_framebuffer_info() -> Option<GpuInfo> {
1003 GPU_INSTANCE.get().map(|gpu| gpu.info())
1004}