1use 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
18pub struct VirtIOGpu<H: Hal, T: Transport> {
26 transport: T,
27 rect: Option<Rect>,
28 frame_buffer_dma: Option<Dma<H>>,
30 cursor_buffer_dma: Option<Dma<H>>,
32 control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
34 cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
36 queue_buf_send: Box<[u8]>,
38 queue_buf_recv: Box<[u8]>,
40}
41
42impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
43 pub fn new(mut transport: T) -> Result<Self> {
45 let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
46
47 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 pub fn ack_interrupt(&mut self) -> InterruptStatus {
87 self.transport.ack_interrupt()
88 }
89
90 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 pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
98 let display_info = self.get_display_info()?;
100 info!("=> {:?}", display_info);
101 self.rect = Some(display_info.rect);
102
103 self.resource_create_2d(
105 RESOURCE_ID_FB,
106 display_info.rect.width,
107 display_info.rect.height,
108 )?;
109
110 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 self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
116
117 self.set_scanout(display_info.rect, SCANOUT_ID, RESOURCE_ID_FB)?;
119
120 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 pub fn flush(&mut self) -> Result {
134 let rect = self.rect.ok_or(Error::NotReady)?;
135 self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
137 self.resource_flush(rect, RESOURCE_ID_FB)?;
139 Ok(())
140 }
141
142 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 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 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 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 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 self.transport.queue_unset(QUEUE_TRANSMIT);
304 self.transport.queue_unset(QUEUE_CURSOR);
305 }
306}
307
308#[repr(C)]
309struct Config {
310 events_read: ReadOnly<u32>,
312
313 events_clear: WriteOnly<u32>,
315
316 num_scanouts: ReadOnly<u32>,
320}
321
322const EVENT_DISPLAY: u32 = 1 << 0;
324
325bitflags! {
326 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
327 struct Features: u64 {
328 const VIRGL = 1 << 0;
330 const EDID = 1 << 1;
332
333 const NOTIFY_ON_EMPTY = 1 << 24; const ANY_LAYOUT = 1 << 27; const RING_INDIRECT_DESC = 1 << 28;
337 const RING_EVENT_IDX = 1 << 29;
338 const UNUSED = 1 << 30; const VERSION_1 = 1 << 32; 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 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, 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};