1mod edid;
4
5pub use self::edid::Edid;
6
7use crate::config::{ReadOnly, WriteOnly, read_config};
8use crate::hal::{BufferDirection, Dma, Hal};
9use crate::queue::VirtQueue;
10use crate::transport::{InterruptStatus, Transport};
11use crate::{Error, PAGE_SIZE, Result, pages};
12use alloc::boxed::Box;
13use alloc::vec::Vec;
14use bitflags::bitflags;
15use log::info;
16use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout};
17
18const QUEUE_SIZE: u16 = 2;
19const SUPPORTED_FEATURES: Features = Features::RING_EVENT_IDX
20 .union(Features::RING_INDIRECT_DESC)
21 .union(Features::VERSION_1)
22 .union(Features::EDID);
23
24pub struct VirtIOGpu<H: Hal, T: Transport> {
32 transport: T,
33 rect: Option<Rect>,
34 frame_buffer_dma: Option<Dma<H>>,
36 cursor_buffer_dma: Option<Dma<H>>,
38 control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
40 cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
42 queue_buf_send: Box<[u8]>,
44 queue_buf_recv: Box<[u8]>,
46 has_edid: bool,
48}
49
50impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
51 pub fn new(mut transport: T) -> Result<Self> {
53 let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
54
55 let events_read = read_config!(transport, Config, events_read)?;
57 let num_scanouts = read_config!(transport, Config, num_scanouts)?;
58 info!(
59 "events_read: {:#x}, num_scanouts: {:#x}",
60 events_read, num_scanouts
61 );
62
63 let control_queue = VirtQueue::new(
64 &mut transport,
65 QUEUE_TRANSMIT,
66 negotiated_features.contains(Features::RING_INDIRECT_DESC),
67 negotiated_features.contains(Features::RING_EVENT_IDX),
68 )?;
69 let cursor_queue = VirtQueue::new(
70 &mut transport,
71 QUEUE_CURSOR,
72 negotiated_features.contains(Features::RING_INDIRECT_DESC),
73 negotiated_features.contains(Features::RING_EVENT_IDX),
74 )?;
75
76 let queue_buf_send = FromZeros::new_box_zeroed_with_elems(PAGE_SIZE).unwrap();
77 let queue_buf_recv = FromZeros::new_box_zeroed_with_elems(PAGE_SIZE).unwrap();
78
79 transport.finish_init();
80
81 let has_edid = negotiated_features.contains(Features::EDID);
82
83 Ok(VirtIOGpu {
84 transport,
85 frame_buffer_dma: None,
86 cursor_buffer_dma: None,
87 rect: None,
88 control_queue,
89 cursor_queue,
90 queue_buf_send,
91 queue_buf_recv,
92 has_edid,
93 })
94 }
95
96 pub fn ack_interrupt(&mut self) -> InterruptStatus {
98 self.transport.ack_interrupt()
99 }
100
101 pub fn resolution(&mut self) -> Result<(u32, u32)> {
103 let display_info = self.get_display_info()?;
104 Ok((display_info.rect.width, display_info.rect.height))
105 }
106
107 pub fn get_edid(&mut self, scanout: u32) -> Result<Edid> {
112 if !self.has_edid {
113 return Err(Error::Unsupported);
114 }
115 let rsp: RespEdid = self.request(CmdGetEdid {
116 header: CtrlHeader::with_type(Command::GET_EDID),
117 scanout,
118 _padding: 0,
119 })?;
120 rsp.header.check_type(Command::OK_EDID)?;
121 Ok(Edid {
122 data: rsp.edid,
123 size: rsp.size,
124 })
125 }
126
127 pub fn edid_preferred_resolution(&mut self) -> Result<(u32, u32)> {
132 let edid = self.get_edid(SCANOUT_ID)?;
133 edid.preferred_resolution()
134 }
135
136 pub fn edid_supported_resolutions(&mut self) -> Result<Vec<(u32, u32)>> {
141 let edid = self.get_edid(SCANOUT_ID)?;
142 Ok(edid.standard_timings())
143 }
144
145 pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
147 let display_info = self.get_display_info()?;
148 info!("=> {:?}", display_info);
149 self.change_resolution(display_info.rect.width, display_info.rect.height)
150 }
151
152 pub fn change_resolution(&mut self, width: u32, height: u32) -> Result<&mut [u8]> {
158 let rect = Rect {
159 x: 0,
160 y: 0,
161 width,
162 height,
163 };
164
165 if self.frame_buffer_dma.is_some() {
167 self.set_scanout(Rect::default(), SCANOUT_ID, 0)?;
168 self.resource_detach_backing(RESOURCE_ID_FB)?;
169 self.resource_unref(RESOURCE_ID_FB)?;
170 self.frame_buffer_dma = None;
171 }
172
173 self.rect = Some(rect);
174 self.resource_create_2d(RESOURCE_ID_FB, width, height)?;
175
176 let size = width * height * 4;
177 let frame_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
178
179 self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
180 self.set_scanout(rect, SCANOUT_ID, RESOURCE_ID_FB)?;
181
182 let buf = unsafe { frame_buffer_dma.raw_slice().as_mut() };
190 self.frame_buffer_dma = Some(frame_buffer_dma);
191 Ok(buf)
192 }
193
194 pub fn flush(&mut self) -> Result {
196 let rect = self.rect.ok_or(Error::NotReady)?;
197 self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
199 self.resource_flush(rect, RESOURCE_ID_FB)?;
201 Ok(())
202 }
203
204 pub fn setup_cursor(
206 &mut self,
207 cursor_image: &[u8],
208 pos_x: u32,
209 pos_y: u32,
210 hot_x: u32,
211 hot_y: u32,
212 ) -> Result {
213 let size = CURSOR_RECT.width * CURSOR_RECT.height * 4;
214 if cursor_image.len() != size as usize {
215 return Err(Error::InvalidParam);
216 }
217 let cursor_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
218
219 let buf = unsafe { cursor_buffer_dma.raw_slice().as_mut() };
224 buf.copy_from_slice(cursor_image);
225
226 self.resource_create_2d(RESOURCE_ID_CURSOR, CURSOR_RECT.width, CURSOR_RECT.height)?;
227 self.resource_attach_backing(RESOURCE_ID_CURSOR, cursor_buffer_dma.paddr() as u64, size)?;
228 self.transfer_to_host_2d(CURSOR_RECT, 0, RESOURCE_ID_CURSOR)?;
229 self.update_cursor(
230 RESOURCE_ID_CURSOR,
231 SCANOUT_ID,
232 pos_x,
233 pos_y,
234 hot_x,
235 hot_y,
236 false,
237 )?;
238 self.cursor_buffer_dma = Some(cursor_buffer_dma);
239 Ok(())
240 }
241
242 pub fn move_cursor(&mut self, pos_x: u32, pos_y: u32) -> Result {
244 self.update_cursor(RESOURCE_ID_CURSOR, SCANOUT_ID, pos_x, pos_y, 0, 0, true)?;
245 Ok(())
246 }
247
248 fn request<Req: IntoBytes + Immutable, Rsp: FromBytes>(&mut self, req: Req) -> Result<Rsp> {
250 req.write_to_prefix(&mut self.queue_buf_send).unwrap();
251 self.control_queue.add_notify_wait_pop(
252 &[&self.queue_buf_send],
253 &mut [&mut self.queue_buf_recv],
254 &mut self.transport,
255 )?;
256 Ok(Rsp::read_from_prefix(&self.queue_buf_recv).unwrap().0)
257 }
258
259 fn cursor_request<Req: IntoBytes + Immutable>(&mut self, req: Req) -> Result {
261 req.write_to_prefix(&mut self.queue_buf_send).unwrap();
262 self.cursor_queue.add_notify_wait_pop(
263 &[&self.queue_buf_send],
264 &mut [],
265 &mut self.transport,
266 )?;
267 Ok(())
268 }
269
270 fn get_display_info(&mut self) -> Result<RespDisplayInfo> {
271 let info: RespDisplayInfo =
272 self.request(CtrlHeader::with_type(Command::GET_DISPLAY_INFO))?;
273 info.header.check_type(Command::OK_DISPLAY_INFO)?;
274 Ok(info)
275 }
276
277 fn resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result {
278 let rsp: CtrlHeader = self.request(ResourceCreate2D {
279 header: CtrlHeader::with_type(Command::RESOURCE_CREATE_2D),
280 resource_id,
281 format: Format::B8G8R8A8UNORM,
282 width,
283 height,
284 })?;
285 rsp.check_type(Command::OK_NODATA)
286 }
287
288 fn set_scanout(&mut self, rect: Rect, scanout_id: u32, resource_id: u32) -> Result {
289 let rsp: CtrlHeader = self.request(SetScanout {
290 header: CtrlHeader::with_type(Command::SET_SCANOUT),
291 rect,
292 scanout_id,
293 resource_id,
294 })?;
295 rsp.check_type(Command::OK_NODATA)
296 }
297
298 fn resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result {
299 let rsp: CtrlHeader = self.request(ResourceFlush {
300 header: CtrlHeader::with_type(Command::RESOURCE_FLUSH),
301 rect,
302 resource_id,
303 _padding: 0,
304 })?;
305 rsp.check_type(Command::OK_NODATA)
306 }
307
308 fn transfer_to_host_2d(&mut self, rect: Rect, offset: u64, resource_id: u32) -> Result {
309 let rsp: CtrlHeader = self.request(TransferToHost2D {
310 header: CtrlHeader::with_type(Command::TRANSFER_TO_HOST_2D),
311 rect,
312 offset,
313 resource_id,
314 _padding: 0,
315 })?;
316 rsp.check_type(Command::OK_NODATA)
317 }
318
319 fn resource_attach_backing(&mut self, resource_id: u32, paddr: u64, length: u32) -> Result {
320 let rsp: CtrlHeader = self.request(ResourceAttachBacking {
321 header: CtrlHeader::with_type(Command::RESOURCE_ATTACH_BACKING),
322 resource_id,
323 nr_entries: 1,
324 addr: paddr,
325 length,
326 _padding: 0,
327 })?;
328 rsp.check_type(Command::OK_NODATA)
329 }
330
331 fn resource_detach_backing(&mut self, resource_id: u32) -> Result {
332 let rsp: CtrlHeader = self.request(ResourceDetachBacking {
333 header: CtrlHeader::with_type(Command::RESOURCE_DETACH_BACKING),
334 resource_id,
335 _padding: 0,
336 })?;
337 rsp.check_type(Command::OK_NODATA)
338 }
339
340 fn resource_unref(&mut self, resource_id: u32) -> Result {
341 let rsp: CtrlHeader = self.request(ResourceUnref {
342 header: CtrlHeader::with_type(Command::RESOURCE_UNREF),
343 resource_id,
344 _padding: 0,
345 })?;
346 rsp.check_type(Command::OK_NODATA)
347 }
348
349 #[allow(clippy::too_many_arguments)]
350 fn update_cursor(
351 &mut self,
352 resource_id: u32,
353 scanout_id: u32,
354 pos_x: u32,
355 pos_y: u32,
356 hot_x: u32,
357 hot_y: u32,
358 is_move: bool,
359 ) -> Result {
360 self.cursor_request(UpdateCursor {
361 header: if is_move {
362 CtrlHeader::with_type(Command::MOVE_CURSOR)
363 } else {
364 CtrlHeader::with_type(Command::UPDATE_CURSOR)
365 },
366 pos: CursorPos {
367 scanout_id,
368 x: pos_x,
369 y: pos_y,
370 _padding: 0,
371 },
372 resource_id,
373 hot_x,
374 hot_y,
375 _padding: 0,
376 })
377 }
378}
379
380impl<H: Hal, T: Transport> Drop for VirtIOGpu<H, T> {
381 fn drop(&mut self) {
382 self.transport.queue_unset(QUEUE_TRANSMIT);
385 self.transport.queue_unset(QUEUE_CURSOR);
386 }
387}
388
389#[repr(C)]
390struct Config {
391 events_read: ReadOnly<u32>,
393
394 events_clear: WriteOnly<u32>,
396
397 num_scanouts: ReadOnly<u32>,
401}
402
403const EVENT_DISPLAY: u32 = 1 << 0;
405
406bitflags! {
407 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
408 struct Features: u64 {
409 const VIRGL = 1 << 0;
411 const EDID = 1 << 1;
413
414 const NOTIFY_ON_EMPTY = 1 << 24; const ANY_LAYOUT = 1 << 27; const RING_INDIRECT_DESC = 1 << 28;
418 const RING_EVENT_IDX = 1 << 29;
419 const UNUSED = 1 << 30; const VERSION_1 = 1 << 32; const ACCESS_PLATFORM = 1 << 33;
424 const RING_PACKED = 1 << 34;
425 const IN_ORDER = 1 << 35;
426 const ORDER_PLATFORM = 1 << 36;
427 const SR_IOV = 1 << 37;
428 const NOTIFICATION_DATA = 1 << 38;
429 }
430}
431
432#[repr(transparent)]
433#[derive(Clone, Copy, Debug, Eq, FromBytes, Immutable, IntoBytes, KnownLayout, PartialEq)]
434struct Command(u32);
435
436impl Command {
437 const GET_DISPLAY_INFO: Command = Command(0x100);
438 const RESOURCE_CREATE_2D: Command = Command(0x101);
439 const RESOURCE_UNREF: Command = Command(0x102);
440 const SET_SCANOUT: Command = Command(0x103);
441 const RESOURCE_FLUSH: Command = Command(0x104);
442 const TRANSFER_TO_HOST_2D: Command = Command(0x105);
443 const RESOURCE_ATTACH_BACKING: Command = Command(0x106);
444 const RESOURCE_DETACH_BACKING: Command = Command(0x107);
445 const GET_CAPSET_INFO: Command = Command(0x108);
446 const GET_CAPSET: Command = Command(0x109);
447 const GET_EDID: Command = Command(0x10a);
448
449 const UPDATE_CURSOR: Command = Command(0x300);
450 const MOVE_CURSOR: Command = Command(0x301);
451
452 const OK_NODATA: Command = Command(0x1100);
453 const OK_DISPLAY_INFO: Command = Command(0x1101);
454 const OK_CAPSET_INFO: Command = Command(0x1102);
455 const OK_CAPSET: Command = Command(0x1103);
456 const OK_EDID: Command = Command(0x1104);
457
458 const ERR_UNSPEC: Command = Command(0x1200);
459 const ERR_OUT_OF_MEMORY: Command = Command(0x1201);
460 const ERR_INVALID_SCANOUT_ID: Command = Command(0x1202);
461}
462
463const GPU_FLAG_FENCE: u32 = 1 << 0;
464
465#[repr(C)]
466#[derive(Debug, Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)]
467struct CtrlHeader {
468 hdr_type: Command,
469 flags: u32,
470 fence_id: u64,
471 ctx_id: u32,
472 _padding: u32,
473}
474
475impl CtrlHeader {
476 fn with_type(hdr_type: Command) -> CtrlHeader {
477 CtrlHeader {
478 hdr_type,
479 flags: 0,
480 fence_id: 0,
481 ctx_id: 0,
482 _padding: 0,
483 }
484 }
485
486 fn check_type(&self, expected: Command) -> Result {
488 if self.hdr_type == expected {
489 Ok(())
490 } else {
491 Err(Error::IoError)
492 }
493 }
494}
495
496#[repr(C)]
497#[derive(Debug, Copy, Clone, Default, FromBytes, Immutable, IntoBytes, KnownLayout)]
498struct Rect {
499 x: u32,
500 y: u32,
501 width: u32,
502 height: u32,
503}
504
505#[repr(C)]
506#[derive(Debug, FromBytes, Immutable, KnownLayout)]
507struct RespDisplayInfo {
508 header: CtrlHeader,
509 rect: Rect,
510 enabled: u32,
511 flags: u32,
512}
513
514#[repr(C)]
515#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
516struct CmdGetEdid {
517 header: CtrlHeader,
518 scanout: u32,
519 _padding: u32,
520}
521
522#[repr(C)]
523#[derive(Debug, FromBytes, Immutable, KnownLayout)]
524struct RespEdid {
525 header: CtrlHeader,
526 size: u32,
527 _padding: u32,
528 edid: [u8; 1024],
529}
530
531#[repr(C)]
532#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
533struct ResourceCreate2D {
534 header: CtrlHeader,
535 resource_id: u32,
536 format: Format,
537 width: u32,
538 height: u32,
539}
540
541#[repr(u32)]
542#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
543enum Format {
544 B8G8R8A8UNORM = 1,
545}
546
547#[repr(C)]
548#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
549struct ResourceAttachBacking {
550 header: CtrlHeader,
551 resource_id: u32,
552 nr_entries: u32, addr: u64,
554 length: u32,
555 _padding: u32,
556}
557
558#[repr(C)]
559#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
560struct ResourceDetachBacking {
561 header: CtrlHeader,
562 resource_id: u32,
563 _padding: u32,
564}
565
566#[repr(C)]
567#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
568struct ResourceUnref {
569 header: CtrlHeader,
570 resource_id: u32,
571 _padding: u32,
572}
573
574#[repr(C)]
575#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
576struct SetScanout {
577 header: CtrlHeader,
578 rect: Rect,
579 scanout_id: u32,
580 resource_id: u32,
581}
582
583#[repr(C)]
584#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
585struct TransferToHost2D {
586 header: CtrlHeader,
587 rect: Rect,
588 offset: u64,
589 resource_id: u32,
590 _padding: u32,
591}
592
593#[repr(C)]
594#[derive(Debug, Immutable, IntoBytes, KnownLayout)]
595struct ResourceFlush {
596 header: CtrlHeader,
597 rect: Rect,
598 resource_id: u32,
599 _padding: u32,
600}
601
602#[repr(C)]
603#[derive(Copy, Clone, Debug, Immutable, IntoBytes, KnownLayout)]
604struct CursorPos {
605 scanout_id: u32,
606 x: u32,
607 y: u32,
608 _padding: u32,
609}
610
611#[repr(C)]
612#[derive(Copy, Clone, Debug, Immutable, IntoBytes, KnownLayout)]
613struct UpdateCursor {
614 header: CtrlHeader,
615 pos: CursorPos,
616 resource_id: u32,
617 hot_x: u32,
618 hot_y: u32,
619 _padding: u32,
620}
621
622const QUEUE_TRANSMIT: u16 = 0;
623const QUEUE_CURSOR: u16 = 1;
624
625const SCANOUT_ID: u32 = 0;
626const RESOURCE_ID_FB: u32 = 0xbabe;
627const RESOURCE_ID_CURSOR: u32 = 0xdade;
628
629const CURSOR_RECT: Rect = Rect {
630 x: 0,
631 y: 0,
632 width: 64,
633 height: 64,
634};