Skip to main content

virtio_drivers/device/gpu/
mod.rs

1//! Driver for VirtIO GPU devices.
2
3mod 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
24/// A virtio based graphics adapter.
25///
26/// It can operate in 2D mode and in 3D (virgl) mode.
27/// 3D mode will offload rendering ops to the host gpu and therefore requires
28/// a gpu with 3D support on the host machine.
29/// In 2D mode the virtio-gpu device provides support for ARGB Hardware cursors
30/// and multiple scanouts (aka heads).
31pub struct VirtIOGpu<H: Hal, T: Transport> {
32    transport: T,
33    rect: Option<Rect>,
34    /// DMA area of frame buffer.
35    frame_buffer_dma: Option<Dma<H>>,
36    /// DMA area of cursor image buffer.
37    cursor_buffer_dma: Option<Dma<H>>,
38    /// Queue for sending control commands.
39    control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
40    /// Queue for sending cursor commands.
41    cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
42    /// Send buffer for queue.
43    queue_buf_send: Box<[u8]>,
44    /// Recv buffer for queue.
45    queue_buf_recv: Box<[u8]>,
46    /// Whether EDID feature was negotiated.
47    has_edid: bool,
48}
49
50impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
51    /// Create a new VirtIO-Gpu driver.
52    pub fn new(mut transport: T) -> Result<Self> {
53        let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
54
55        // read configuration space
56        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    /// Acknowledge interrupt.
97    pub fn ack_interrupt(&mut self) -> InterruptStatus {
98        self.transport.ack_interrupt()
99    }
100
101    /// Get the resolution (width, height).
102    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    /// Get the EDID data for the specified scanout.
108    ///
109    /// Returns an [`Edid`] struct wrapping the EDID blob.
110    /// Requires the EDID feature to have been negotiated.
111    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    /// Get the preferred resolution from the EDID data.
128    ///
129    /// Parses the first Detailed Timing Descriptor in the EDID to extract
130    /// the preferred display resolution. Returns (width, height).
131    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    /// Get the list of supported resolutions from EDID data.
137    ///
138    /// Returns up to 8 resolutions from the Standard Timings block, sorted
139    /// by total pixel count (largest first). Each entry is (width, height).
140    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    /// Setup framebuffer at the display's default resolution.
146    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    /// Set or change the framebuffer resolution. If a framebuffer already exists, tears down the
153    /// existing resource before creating the new one. Can be called before or after
154    /// [`setup_framebuffer`](Self::setup_framebuffer) to set an explicit resolution.
155    ///
156    /// Returns a mutable slice to the new framebuffer memory.
157    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        // Tear down existing framebuffer if one exists
166        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        // SAFETY: `Dma::new` guarantees that the pointer returned from
183        // `raw_slice` is non-null, aligned, and the allocation is zeroed. We
184        // store the `Dma` object in `self.frame_buffer_dma`, which prevents the
185        // allocation from being freed while `self` exists. The returned ptr
186        // borrows `self` mutably, which prevents other code from getting
187        // another reference to `frame_buffer_dma` while the returned slice is
188        // still in use.
189        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    /// Flush framebuffer to screen.
195    pub fn flush(&mut self) -> Result {
196        let rect = self.rect.ok_or(Error::NotReady)?;
197        // copy data from guest to host
198        self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
199        // flush data to screen
200        self.resource_flush(rect, RESOURCE_ID_FB)?;
201        Ok(())
202    }
203
204    /// Set the pointer shape and position.
205    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        // SAFETY: `Dma::new` guarantees that the pointer returned from
220        // `raw_slice` is non-null, aligned, and the allocation is zeroed. The
221        // returned reference is only used within this function while
222        // `cursor_buffer_dma` is alive.
223        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    /// Move the pointer without updating the shape.
243    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    /// Send a request to the device and block for a response.
249    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    /// Send a mouse cursor operation request to the device and block for a response.
260    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        // Clear any pointers pointing to DMA regions, so the device doesn't try to access them
383        // after they have been freed.
384        self.transport.queue_unset(QUEUE_TRANSMIT);
385        self.transport.queue_unset(QUEUE_CURSOR);
386    }
387}
388
389#[repr(C)]
390struct Config {
391    /// Signals pending events to the driver。
392    events_read: ReadOnly<u32>,
393
394    /// Clears pending events in the device.
395    events_clear: WriteOnly<u32>,
396
397    /// Specifies the maximum number of scanouts supported by the device.
398    ///
399    /// Minimum value is 1, maximum value is 16.
400    num_scanouts: ReadOnly<u32>,
401}
402
403/// Display configuration has changed.
404const EVENT_DISPLAY: u32 = 1 << 0;
405
406bitflags! {
407    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
408    struct Features: u64 {
409        /// virgl 3D mode is supported.
410        const VIRGL                 = 1 << 0;
411        /// EDID is supported.
412        const EDID                  = 1 << 1;
413
414        // device independent
415        const NOTIFY_ON_EMPTY       = 1 << 24; // legacy
416        const ANY_LAYOUT            = 1 << 27; // legacy
417        const RING_INDIRECT_DESC    = 1 << 28;
418        const RING_EVENT_IDX        = 1 << 29;
419        const UNUSED                = 1 << 30; // legacy
420        const VERSION_1             = 1 << 32; // detect legacy
421
422        // since virtio v1.1
423        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    /// Return error if the type is not same as expected.
487    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, // always 1
553    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};