• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Driver for VirtIO GPU devices.
2 
3 use crate::hal::{BufferDirection, Dma, Hal};
4 use crate::queue::VirtQueue;
5 use crate::transport::Transport;
6 use crate::volatile::{volread, ReadOnly, Volatile, WriteOnly};
7 use crate::{pages, Error, Result};
8 use bitflags::bitflags;
9 use log::info;
10 use zerocopy::{AsBytes, FromBytes};
11 
12 const QUEUE_SIZE: u16 = 2;
13 
14 /// A virtio based graphics adapter.
15 ///
16 /// It can operate in 2D mode and in 3D (virgl) mode.
17 /// 3D mode will offload rendering ops to the host gpu and therefore requires
18 /// a gpu with 3D support on the host machine.
19 /// In 2D mode the virtio-gpu device provides support for ARGB Hardware cursors
20 /// and multiple scanouts (aka heads).
21 pub struct VirtIOGpu<'a, H: Hal, T: Transport> {
22     transport: T,
23     rect: Option<Rect>,
24     /// DMA area of frame buffer.
25     frame_buffer_dma: Option<Dma<H>>,
26     /// DMA area of cursor image buffer.
27     cursor_buffer_dma: Option<Dma<H>>,
28     /// Queue for sending control commands.
29     control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
30     /// Queue for sending cursor commands.
31     cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
32     /// DMA region for sending data to the device.
33     dma_send: Dma<H>,
34     /// DMA region for receiving data from the device.
35     dma_recv: Dma<H>,
36     /// Send buffer for queue.
37     queue_buf_send: &'a mut [u8],
38     /// Recv buffer for queue.
39     queue_buf_recv: &'a mut [u8],
40 }
41 
42 impl<H: Hal, T: Transport> VirtIOGpu<'_, H, T> {
43     /// Create a new VirtIO-Gpu driver.
new(mut transport: T) -> Result<Self>44     pub fn new(mut transport: T) -> Result<Self> {
45         transport.begin_init(|features| {
46             let features = Features::from_bits_truncate(features);
47             info!("Device features {:?}", features);
48             let supported_features = Features::empty();
49             (features & supported_features).bits()
50         });
51 
52         // read configuration space
53         let config_space = transport.config_space::<Config>()?;
54         unsafe {
55             let events_read = volread!(config_space, events_read);
56             let num_scanouts = volread!(config_space, num_scanouts);
57             info!(
58                 "events_read: {:#x}, num_scanouts: {:#x}",
59                 events_read, num_scanouts
60             );
61         }
62 
63         let control_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT)?;
64         let cursor_queue = VirtQueue::new(&mut transport, QUEUE_CURSOR)?;
65 
66         let dma_send = Dma::new(1, BufferDirection::DriverToDevice)?;
67         let dma_recv = Dma::new(1, BufferDirection::DeviceToDriver)?;
68         let queue_buf_send = unsafe { dma_send.raw_slice().as_mut() };
69         let queue_buf_recv = unsafe { dma_recv.raw_slice().as_mut() };
70 
71         transport.finish_init();
72 
73         Ok(VirtIOGpu {
74             transport,
75             frame_buffer_dma: None,
76             cursor_buffer_dma: None,
77             rect: None,
78             control_queue,
79             cursor_queue,
80             dma_send,
81             dma_recv,
82             queue_buf_send,
83             queue_buf_recv,
84         })
85     }
86 
87     /// Acknowledge interrupt.
ack_interrupt(&mut self) -> bool88     pub fn ack_interrupt(&mut self) -> bool {
89         self.transport.ack_interrupt()
90     }
91 
92     /// Get the resolution (width, height).
resolution(&mut self) -> Result<(u32, u32)>93     pub fn resolution(&mut self) -> Result<(u32, u32)> {
94         let display_info = self.get_display_info()?;
95         Ok((display_info.rect.width, display_info.rect.height))
96     }
97 
98     /// Setup framebuffer
setup_framebuffer(&mut self) -> Result<&mut [u8]>99     pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
100         // get display info
101         let display_info = self.get_display_info()?;
102         info!("=> {:?}", display_info);
103         self.rect = Some(display_info.rect);
104 
105         // create resource 2d
106         self.resource_create_2d(
107             RESOURCE_ID_FB,
108             display_info.rect.width,
109             display_info.rect.height,
110         )?;
111 
112         // alloc continuous pages for the frame buffer
113         let size = display_info.rect.width * display_info.rect.height * 4;
114         let frame_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
115 
116         // resource_attach_backing
117         self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
118 
119         // map frame buffer to screen
120         self.set_scanout(display_info.rect, SCANOUT_ID, RESOURCE_ID_FB)?;
121 
122         let buf = unsafe { frame_buffer_dma.raw_slice().as_mut() };
123         self.frame_buffer_dma = Some(frame_buffer_dma);
124         Ok(buf)
125     }
126 
127     /// Flush framebuffer to screen.
flush(&mut self) -> Result128     pub fn flush(&mut self) -> Result {
129         let rect = self.rect.ok_or(Error::NotReady)?;
130         // copy data from guest to host
131         self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
132         // flush data to screen
133         self.resource_flush(rect, RESOURCE_ID_FB)?;
134         Ok(())
135     }
136 
137     /// Set the pointer shape and position.
setup_cursor( &mut self, cursor_image: &[u8], pos_x: u32, pos_y: u32, hot_x: u32, hot_y: u32, ) -> Result138     pub fn setup_cursor(
139         &mut self,
140         cursor_image: &[u8],
141         pos_x: u32,
142         pos_y: u32,
143         hot_x: u32,
144         hot_y: u32,
145     ) -> Result {
146         let size = CURSOR_RECT.width * CURSOR_RECT.height * 4;
147         if cursor_image.len() != size as usize {
148             return Err(Error::InvalidParam);
149         }
150         let cursor_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
151         let buf = unsafe { cursor_buffer_dma.raw_slice().as_mut() };
152         buf.copy_from_slice(cursor_image);
153 
154         self.resource_create_2d(RESOURCE_ID_CURSOR, CURSOR_RECT.width, CURSOR_RECT.height)?;
155         self.resource_attach_backing(RESOURCE_ID_CURSOR, cursor_buffer_dma.paddr() as u64, size)?;
156         self.transfer_to_host_2d(CURSOR_RECT, 0, RESOURCE_ID_CURSOR)?;
157         self.update_cursor(
158             RESOURCE_ID_CURSOR,
159             SCANOUT_ID,
160             pos_x,
161             pos_y,
162             hot_x,
163             hot_y,
164             false,
165         )?;
166         self.cursor_buffer_dma = Some(cursor_buffer_dma);
167         Ok(())
168     }
169 
170     /// Move the pointer without updating the shape.
move_cursor(&mut self, pos_x: u32, pos_y: u32) -> Result171     pub fn move_cursor(&mut self, pos_x: u32, pos_y: u32) -> Result {
172         self.update_cursor(RESOURCE_ID_CURSOR, SCANOUT_ID, pos_x, pos_y, 0, 0, true)?;
173         Ok(())
174     }
175 
176     /// Send a request to the device and block for a response.
request<Req: AsBytes, Rsp: FromBytes>(&mut self, req: Req) -> Result<Rsp>177     fn request<Req: AsBytes, Rsp: FromBytes>(&mut self, req: Req) -> Result<Rsp> {
178         req.write_to_prefix(&mut *self.queue_buf_send).unwrap();
179         self.control_queue.add_notify_wait_pop(
180             &[self.queue_buf_send],
181             &mut [self.queue_buf_recv],
182             &mut self.transport,
183         )?;
184         Ok(Rsp::read_from_prefix(&*self.queue_buf_recv).unwrap())
185     }
186 
187     /// Send a mouse cursor operation request to the device and block for a response.
cursor_request<Req: AsBytes>(&mut self, req: Req) -> Result188     fn cursor_request<Req: AsBytes>(&mut self, req: Req) -> Result {
189         req.write_to_prefix(&mut *self.queue_buf_send).unwrap();
190         self.cursor_queue.add_notify_wait_pop(
191             &[self.queue_buf_send],
192             &mut [],
193             &mut self.transport,
194         )?;
195         Ok(())
196     }
197 
get_display_info(&mut self) -> Result<RespDisplayInfo>198     fn get_display_info(&mut self) -> Result<RespDisplayInfo> {
199         let info: RespDisplayInfo =
200             self.request(CtrlHeader::with_type(Command::GET_DISPLAY_INFO))?;
201         info.header.check_type(Command::OK_DISPLAY_INFO)?;
202         Ok(info)
203     }
204 
resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result205     fn resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result {
206         let rsp: CtrlHeader = self.request(ResourceCreate2D {
207             header: CtrlHeader::with_type(Command::RESOURCE_CREATE_2D),
208             resource_id,
209             format: Format::B8G8R8A8UNORM,
210             width,
211             height,
212         })?;
213         rsp.check_type(Command::OK_NODATA)
214     }
215 
set_scanout(&mut self, rect: Rect, scanout_id: u32, resource_id: u32) -> Result216     fn set_scanout(&mut self, rect: Rect, scanout_id: u32, resource_id: u32) -> Result {
217         let rsp: CtrlHeader = self.request(SetScanout {
218             header: CtrlHeader::with_type(Command::SET_SCANOUT),
219             rect,
220             scanout_id,
221             resource_id,
222         })?;
223         rsp.check_type(Command::OK_NODATA)
224     }
225 
resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result226     fn resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result {
227         let rsp: CtrlHeader = self.request(ResourceFlush {
228             header: CtrlHeader::with_type(Command::RESOURCE_FLUSH),
229             rect,
230             resource_id,
231             _padding: 0,
232         })?;
233         rsp.check_type(Command::OK_NODATA)
234     }
235 
transfer_to_host_2d(&mut self, rect: Rect, offset: u64, resource_id: u32) -> Result236     fn transfer_to_host_2d(&mut self, rect: Rect, offset: u64, resource_id: u32) -> Result {
237         let rsp: CtrlHeader = self.request(TransferToHost2D {
238             header: CtrlHeader::with_type(Command::TRANSFER_TO_HOST_2D),
239             rect,
240             offset,
241             resource_id,
242             _padding: 0,
243         })?;
244         rsp.check_type(Command::OK_NODATA)
245     }
246 
resource_attach_backing(&mut self, resource_id: u32, paddr: u64, length: u32) -> Result247     fn resource_attach_backing(&mut self, resource_id: u32, paddr: u64, length: u32) -> Result {
248         let rsp: CtrlHeader = self.request(ResourceAttachBacking {
249             header: CtrlHeader::with_type(Command::RESOURCE_ATTACH_BACKING),
250             resource_id,
251             nr_entries: 1,
252             addr: paddr,
253             length,
254             _padding: 0,
255         })?;
256         rsp.check_type(Command::OK_NODATA)
257     }
258 
update_cursor( &mut self, resource_id: u32, scanout_id: u32, pos_x: u32, pos_y: u32, hot_x: u32, hot_y: u32, is_move: bool, ) -> Result259     fn update_cursor(
260         &mut self,
261         resource_id: u32,
262         scanout_id: u32,
263         pos_x: u32,
264         pos_y: u32,
265         hot_x: u32,
266         hot_y: u32,
267         is_move: bool,
268     ) -> Result {
269         self.cursor_request(UpdateCursor {
270             header: if is_move {
271                 CtrlHeader::with_type(Command::MOVE_CURSOR)
272             } else {
273                 CtrlHeader::with_type(Command::UPDATE_CURSOR)
274             },
275             pos: CursorPos {
276                 scanout_id,
277                 x: pos_x,
278                 y: pos_y,
279                 _padding: 0,
280             },
281             resource_id,
282             hot_x,
283             hot_y,
284             _padding: 0,
285         })
286     }
287 }
288 
289 impl<H: Hal, T: Transport> Drop for VirtIOGpu<'_, H, T> {
drop(&mut self)290     fn drop(&mut self) {
291         // Clear any pointers pointing to DMA regions, so the device doesn't try to access them
292         // after they have been freed.
293         self.transport.queue_unset(QUEUE_TRANSMIT);
294         self.transport.queue_unset(QUEUE_CURSOR);
295     }
296 }
297 
298 #[repr(C)]
299 struct Config {
300     /// Signals pending events to the driver。
301     events_read: ReadOnly<u32>,
302 
303     /// Clears pending events in the device.
304     events_clear: WriteOnly<u32>,
305 
306     /// Specifies the maximum number of scanouts supported by the device.
307     ///
308     /// Minimum value is 1, maximum value is 16.
309     num_scanouts: Volatile<u32>,
310 }
311 
312 /// Display configuration has changed.
313 const EVENT_DISPLAY: u32 = 1 << 0;
314 
315 bitflags! {
316     struct Features: u64 {
317         /// virgl 3D mode is supported.
318         const VIRGL                 = 1 << 0;
319         /// EDID is supported.
320         const EDID                  = 1 << 1;
321 
322         // device independent
323         const NOTIFY_ON_EMPTY       = 1 << 24; // legacy
324         const ANY_LAYOUT            = 1 << 27; // legacy
325         const RING_INDIRECT_DESC    = 1 << 28;
326         const RING_EVENT_IDX        = 1 << 29;
327         const UNUSED                = 1 << 30; // legacy
328         const VERSION_1             = 1 << 32; // detect legacy
329 
330         // since virtio v1.1
331         const ACCESS_PLATFORM       = 1 << 33;
332         const RING_PACKED           = 1 << 34;
333         const IN_ORDER              = 1 << 35;
334         const ORDER_PLATFORM        = 1 << 36;
335         const SR_IOV                = 1 << 37;
336         const NOTIFICATION_DATA     = 1 << 38;
337     }
338 }
339 
340 #[repr(transparent)]
341 #[derive(AsBytes, Clone, Copy, Debug, Eq, PartialEq, FromBytes)]
342 struct Command(u32);
343 
344 impl Command {
345     const GET_DISPLAY_INFO: Command = Command(0x100);
346     const RESOURCE_CREATE_2D: Command = Command(0x101);
347     const RESOURCE_UNREF: Command = Command(0x102);
348     const SET_SCANOUT: Command = Command(0x103);
349     const RESOURCE_FLUSH: Command = Command(0x104);
350     const TRANSFER_TO_HOST_2D: Command = Command(0x105);
351     const RESOURCE_ATTACH_BACKING: Command = Command(0x106);
352     const RESOURCE_DETACH_BACKING: Command = Command(0x107);
353     const GET_CAPSET_INFO: Command = Command(0x108);
354     const GET_CAPSET: Command = Command(0x109);
355     const GET_EDID: Command = Command(0x10a);
356 
357     const UPDATE_CURSOR: Command = Command(0x300);
358     const MOVE_CURSOR: Command = Command(0x301);
359 
360     const OK_NODATA: Command = Command(0x1100);
361     const OK_DISPLAY_INFO: Command = Command(0x1101);
362     const OK_CAPSET_INFO: Command = Command(0x1102);
363     const OK_CAPSET: Command = Command(0x1103);
364     const OK_EDID: Command = Command(0x1104);
365 
366     const ERR_UNSPEC: Command = Command(0x1200);
367     const ERR_OUT_OF_MEMORY: Command = Command(0x1201);
368     const ERR_INVALID_SCANOUT_ID: Command = Command(0x1202);
369 }
370 
371 const GPU_FLAG_FENCE: u32 = 1 << 0;
372 
373 #[repr(C)]
374 #[derive(AsBytes, Debug, Clone, Copy, FromBytes)]
375 struct CtrlHeader {
376     hdr_type: Command,
377     flags: u32,
378     fence_id: u64,
379     ctx_id: u32,
380     _padding: u32,
381 }
382 
383 impl CtrlHeader {
with_type(hdr_type: Command) -> CtrlHeader384     fn with_type(hdr_type: Command) -> CtrlHeader {
385         CtrlHeader {
386             hdr_type,
387             flags: 0,
388             fence_id: 0,
389             ctx_id: 0,
390             _padding: 0,
391         }
392     }
393 
394     /// Return error if the type is not same as expected.
check_type(&self, expected: Command) -> Result395     fn check_type(&self, expected: Command) -> Result {
396         if self.hdr_type == expected {
397             Ok(())
398         } else {
399             Err(Error::IoError)
400         }
401     }
402 }
403 
404 #[repr(C)]
405 #[derive(AsBytes, Debug, Copy, Clone, Default, FromBytes)]
406 struct Rect {
407     x: u32,
408     y: u32,
409     width: u32,
410     height: u32,
411 }
412 
413 #[repr(C)]
414 #[derive(Debug, FromBytes)]
415 struct RespDisplayInfo {
416     header: CtrlHeader,
417     rect: Rect,
418     enabled: u32,
419     flags: u32,
420 }
421 
422 #[repr(C)]
423 #[derive(AsBytes, Debug)]
424 struct ResourceCreate2D {
425     header: CtrlHeader,
426     resource_id: u32,
427     format: Format,
428     width: u32,
429     height: u32,
430 }
431 
432 #[repr(u32)]
433 #[derive(AsBytes, Debug)]
434 enum Format {
435     B8G8R8A8UNORM = 1,
436 }
437 
438 #[repr(C)]
439 #[derive(AsBytes, Debug)]
440 struct ResourceAttachBacking {
441     header: CtrlHeader,
442     resource_id: u32,
443     nr_entries: u32, // always 1
444     addr: u64,
445     length: u32,
446     _padding: u32,
447 }
448 
449 #[repr(C)]
450 #[derive(AsBytes, Debug)]
451 struct SetScanout {
452     header: CtrlHeader,
453     rect: Rect,
454     scanout_id: u32,
455     resource_id: u32,
456 }
457 
458 #[repr(C)]
459 #[derive(AsBytes, Debug)]
460 struct TransferToHost2D {
461     header: CtrlHeader,
462     rect: Rect,
463     offset: u64,
464     resource_id: u32,
465     _padding: u32,
466 }
467 
468 #[repr(C)]
469 #[derive(AsBytes, Debug)]
470 struct ResourceFlush {
471     header: CtrlHeader,
472     rect: Rect,
473     resource_id: u32,
474     _padding: u32,
475 }
476 
477 #[repr(C)]
478 #[derive(AsBytes, Debug, Clone, Copy)]
479 struct CursorPos {
480     scanout_id: u32,
481     x: u32,
482     y: u32,
483     _padding: u32,
484 }
485 
486 #[repr(C)]
487 #[derive(AsBytes, Debug, Clone, Copy)]
488 struct UpdateCursor {
489     header: CtrlHeader,
490     pos: CursorPos,
491     resource_id: u32,
492     hot_x: u32,
493     hot_y: u32,
494     _padding: u32,
495 }
496 
497 const QUEUE_TRANSMIT: u16 = 0;
498 const QUEUE_CURSOR: u16 = 1;
499 
500 const SCANOUT_ID: u32 = 0;
501 const RESOURCE_ID_FB: u32 = 0xbabe;
502 const RESOURCE_ID_CURSOR: u32 = 0xdade;
503 
504 const CURSOR_RECT: Rect = Rect {
505     x: 0,
506     y: 0,
507     width: 64,
508     height: 64,
509 };
510