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