virtio_drivers/device/
gpu.rs

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