virtio_drivers/device/
gpu.rs

1//! Driver for VirtIO GPU devices.
2
3use crate::hal::{BufferDirection, Dma, Hal};
4use crate::queue::VirtQueue;
5use crate::transport::Transport;
6use crate::volatile::{volread, ReadOnly, Volatile, WriteOnly};
7use crate::{pages, Error, Result, PAGE_SIZE};
8use alloc::boxed::Box;
9use bitflags::bitflags;
10use log::info;
11use zerocopy::{AsBytes, FromBytes, FromZeroes};
12
13const QUEUE_SIZE: u16 = 2;
14const 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).
23pub 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
40impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
41    /// Create a new VirtIO-Gpu driver.
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 config_space = transport.config_space::<Config>()?;
47        unsafe {
48            let events_read = volread!(config_space, events_read);
49            let num_scanouts = volread!(config_space, num_scanouts);
50            info!(
51                "events_read: {:#x}, num_scanouts: {:#x}",
52                events_read, num_scanouts
53            );
54        }
55
56        let control_queue = VirtQueue::new(
57            &mut transport,
58            QUEUE_TRANSMIT,
59            negotiated_features.contains(Features::RING_INDIRECT_DESC),
60            negotiated_features.contains(Features::RING_EVENT_IDX),
61        )?;
62        let cursor_queue = VirtQueue::new(
63            &mut transport,
64            QUEUE_CURSOR,
65            negotiated_features.contains(Features::RING_INDIRECT_DESC),
66            negotiated_features.contains(Features::RING_EVENT_IDX),
67        )?;
68
69        let queue_buf_send = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
70        let queue_buf_recv = FromZeroes::new_box_slice_zeroed(PAGE_SIZE);
71
72        transport.finish_init();
73
74        Ok(VirtIOGpu {
75            transport,
76            frame_buffer_dma: None,
77            cursor_buffer_dma: None,
78            rect: None,
79            control_queue,
80            cursor_queue,
81            queue_buf_send,
82            queue_buf_recv,
83        })
84    }
85
86    /// Acknowledge interrupt.
87    pub fn ack_interrupt(&mut self) -> bool {
88        self.transport.ack_interrupt()
89    }
90
91    /// Get the resolution (width, height).
92    pub fn resolution(&mut self) -> Result<(u32, u32)> {
93        let display_info = self.get_display_info()?;
94        Ok((display_info.rect.width, display_info.rect.height))
95    }
96
97    /// Setup framebuffer
98    pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
99        // get display info
100        let display_info = self.get_display_info()?;
101        info!("=> {:?}", display_info);
102        self.rect = Some(display_info.rect);
103
104        // create resource 2d
105        self.resource_create_2d(
106            RESOURCE_ID_FB,
107            display_info.rect.width,
108            display_info.rect.height,
109        )?;
110
111        // alloc continuous pages for the frame buffer
112        let size = display_info.rect.width * display_info.rect.height * 4;
113        let frame_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
114
115        // resource_attach_backing
116        self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
117
118        // map frame buffer to screen
119        self.set_scanout(display_info.rect, SCANOUT_ID, RESOURCE_ID_FB)?;
120
121        let buf = unsafe { frame_buffer_dma.raw_slice().as_mut() };
122        self.frame_buffer_dma = Some(frame_buffer_dma);
123        Ok(buf)
124    }
125
126    /// Flush framebuffer to screen.
127    pub fn flush(&mut self) -> Result {
128        let rect = self.rect.ok_or(Error::NotReady)?;
129        // copy data from guest to host
130        self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
131        // flush data to screen
132        self.resource_flush(rect, RESOURCE_ID_FB)?;
133        Ok(())
134    }
135
136    /// Set the pointer shape and position.
137    pub fn setup_cursor(
138        &mut self,
139        cursor_image: &[u8],
140        pos_x: u32,
141        pos_y: u32,
142        hot_x: u32,
143        hot_y: u32,
144    ) -> Result {
145        let size = CURSOR_RECT.width * CURSOR_RECT.height * 4;
146        if cursor_image.len() != size as usize {
147            return Err(Error::InvalidParam);
148        }
149        let cursor_buffer_dma = Dma::new(pages(size as usize), BufferDirection::DriverToDevice)?;
150        let buf = unsafe { cursor_buffer_dma.raw_slice().as_mut() };
151        buf.copy_from_slice(cursor_image);
152
153        self.resource_create_2d(RESOURCE_ID_CURSOR, CURSOR_RECT.width, CURSOR_RECT.height)?;
154        self.resource_attach_backing(RESOURCE_ID_CURSOR, cursor_buffer_dma.paddr() as u64, size)?;
155        self.transfer_to_host_2d(CURSOR_RECT, 0, RESOURCE_ID_CURSOR)?;
156        self.update_cursor(
157            RESOURCE_ID_CURSOR,
158            SCANOUT_ID,
159            pos_x,
160            pos_y,
161            hot_x,
162            hot_y,
163            false,
164        )?;
165        self.cursor_buffer_dma = Some(cursor_buffer_dma);
166        Ok(())
167    }
168
169    /// Move the pointer without updating the shape.
170    pub fn move_cursor(&mut self, pos_x: u32, pos_y: u32) -> Result {
171        self.update_cursor(RESOURCE_ID_CURSOR, SCANOUT_ID, pos_x, pos_y, 0, 0, true)?;
172        Ok(())
173    }
174
175    /// Send a request to the device and block for a response.
176    fn request<Req: AsBytes, Rsp: FromBytes>(&mut self, req: Req) -> Result<Rsp> {
177        req.write_to_prefix(&mut self.queue_buf_send).unwrap();
178        self.control_queue.add_notify_wait_pop(
179            &[&self.queue_buf_send],
180            &mut [&mut self.queue_buf_recv],
181            &mut self.transport,
182        )?;
183        Ok(Rsp::read_from_prefix(&self.queue_buf_recv).unwrap())
184    }
185
186    /// Send a mouse cursor operation request to the device and block for a response.
187    fn cursor_request<Req: AsBytes>(&mut self, req: Req) -> Result {
188        req.write_to_prefix(&mut self.queue_buf_send).unwrap();
189        self.cursor_queue.add_notify_wait_pop(
190            &[&self.queue_buf_send],
191            &mut [],
192            &mut self.transport,
193        )?;
194        Ok(())
195    }
196
197    fn get_display_info(&mut self) -> Result<RespDisplayInfo> {
198        let info: RespDisplayInfo =
199            self.request(CtrlHeader::with_type(Command::GET_DISPLAY_INFO))?;
200        info.header.check_type(Command::OK_DISPLAY_INFO)?;
201        Ok(info)
202    }
203
204    fn resource_create_2d(&mut self, resource_id: u32, width: u32, height: u32) -> Result {
205        let rsp: CtrlHeader = self.request(ResourceCreate2D {
206            header: CtrlHeader::with_type(Command::RESOURCE_CREATE_2D),
207            resource_id,
208            format: Format::B8G8R8A8UNORM,
209            width,
210            height,
211        })?;
212        rsp.check_type(Command::OK_NODATA)
213    }
214
215    fn set_scanout(&mut self, rect: Rect, scanout_id: u32, resource_id: u32) -> Result {
216        let rsp: CtrlHeader = self.request(SetScanout {
217            header: CtrlHeader::with_type(Command::SET_SCANOUT),
218            rect,
219            scanout_id,
220            resource_id,
221        })?;
222        rsp.check_type(Command::OK_NODATA)
223    }
224
225    fn resource_flush(&mut self, rect: Rect, resource_id: u32) -> Result {
226        let rsp: CtrlHeader = self.request(ResourceFlush {
227            header: CtrlHeader::with_type(Command::RESOURCE_FLUSH),
228            rect,
229            resource_id,
230            _padding: 0,
231        })?;
232        rsp.check_type(Command::OK_NODATA)
233    }
234
235    fn transfer_to_host_2d(&mut self, rect: Rect, offset: u64, resource_id: u32) -> Result {
236        let rsp: CtrlHeader = self.request(TransferToHost2D {
237            header: CtrlHeader::with_type(Command::TRANSFER_TO_HOST_2D),
238            rect,
239            offset,
240            resource_id,
241            _padding: 0,
242        })?;
243        rsp.check_type(Command::OK_NODATA)
244    }
245
246    fn resource_attach_backing(&mut self, resource_id: u32, paddr: u64, length: u32) -> Result {
247        let rsp: CtrlHeader = self.request(ResourceAttachBacking {
248            header: CtrlHeader::with_type(Command::RESOURCE_ATTACH_BACKING),
249            resource_id,
250            nr_entries: 1,
251            addr: paddr,
252            length,
253            _padding: 0,
254        })?;
255        rsp.check_type(Command::OK_NODATA)
256    }
257
258    fn update_cursor(
259        &mut self,
260        resource_id: u32,
261        scanout_id: u32,
262        pos_x: u32,
263        pos_y: u32,
264        hot_x: u32,
265        hot_y: u32,
266        is_move: bool,
267    ) -> Result {
268        self.cursor_request(UpdateCursor {
269            header: if is_move {
270                CtrlHeader::with_type(Command::MOVE_CURSOR)
271            } else {
272                CtrlHeader::with_type(Command::UPDATE_CURSOR)
273            },
274            pos: CursorPos {
275                scanout_id,
276                x: pos_x,
277                y: pos_y,
278                _padding: 0,
279            },
280            resource_id,
281            hot_x,
282            hot_y,
283            _padding: 0,
284        })
285    }
286}
287
288impl<H: Hal, T: Transport> Drop for VirtIOGpu<H, T> {
289    fn drop(&mut self) {
290        // Clear any pointers pointing to DMA regions, so the device doesn't try to access them
291        // after they have been freed.
292        self.transport.queue_unset(QUEUE_TRANSMIT);
293        self.transport.queue_unset(QUEUE_CURSOR);
294    }
295}
296
297#[repr(C)]
298struct Config {
299    /// Signals pending events to the driver。
300    events_read: ReadOnly<u32>,
301
302    /// Clears pending events in the device.
303    events_clear: WriteOnly<u32>,
304
305    /// Specifies the maximum number of scanouts supported by the device.
306    ///
307    /// Minimum value is 1, maximum value is 16.
308    num_scanouts: Volatile<u32>,
309}
310
311/// Display configuration has changed.
312const EVENT_DISPLAY: u32 = 1 << 0;
313
314bitflags! {
315    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
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, FromZeroes)]
342struct Command(u32);
343
344impl 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
371const GPU_FLAG_FENCE: u32 = 1 << 0;
372
373#[repr(C)]
374#[derive(AsBytes, Debug, Clone, Copy, FromBytes, FromZeroes)]
375struct CtrlHeader {
376    hdr_type: Command,
377    flags: u32,
378    fence_id: u64,
379    ctx_id: u32,
380    _padding: u32,
381}
382
383impl CtrlHeader {
384    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.
395    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, FromZeroes)]
406struct Rect {
407    x: u32,
408    y: u32,
409    width: u32,
410    height: u32,
411}
412
413#[repr(C)]
414#[derive(Debug, FromBytes, FromZeroes)]
415struct RespDisplayInfo {
416    header: CtrlHeader,
417    rect: Rect,
418    enabled: u32,
419    flags: u32,
420}
421
422#[repr(C)]
423#[derive(AsBytes, Debug)]
424struct 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)]
434enum Format {
435    B8G8R8A8UNORM = 1,
436}
437
438#[repr(C)]
439#[derive(AsBytes, Debug)]
440struct 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)]
451struct SetScanout {
452    header: CtrlHeader,
453    rect: Rect,
454    scanout_id: u32,
455    resource_id: u32,
456}
457
458#[repr(C)]
459#[derive(AsBytes, Debug)]
460struct 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)]
470struct 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)]
479struct 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)]
488struct UpdateCursor {
489    header: CtrlHeader,
490    pos: CursorPos,
491    resource_id: u32,
492    hot_x: u32,
493    hot_y: u32,
494    _padding: u32,
495}
496
497const QUEUE_TRANSMIT: u16 = 0;
498const QUEUE_CURSOR: u16 = 1;
499
500const SCANOUT_ID: u32 = 0;
501const RESOURCE_ID_FB: u32 = 0xbabe;
502const RESOURCE_ID_CURSOR: u32 = 0xdade;
503
504const CURSOR_RECT: Rect = Rect {
505    x: 0,
506    y: 0,
507    width: 64,
508    height: 64,
509};