1use 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
16pub struct VirtIOGpu<H: Hal, T: Transport> {
24 transport: T,
25 rect: Option<Rect>,
26 frame_buffer_dma: Option<Dma<H>>,
28 cursor_buffer_dma: Option<Dma<H>>,
30 control_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
32 cursor_queue: VirtQueue<H, { QUEUE_SIZE as usize }>,
34 queue_buf_send: Box<[u8]>,
36 queue_buf_recv: Box<[u8]>,
38}
39
40impl<H: Hal, T: Transport> VirtIOGpu<H, T> {
41 pub fn new(mut transport: T) -> Result<Self> {
43 let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
44
45 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 pub fn ack_interrupt(&mut self) -> bool {
88 self.transport.ack_interrupt()
89 }
90
91 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 pub fn setup_framebuffer(&mut self) -> Result<&mut [u8]> {
99 let display_info = self.get_display_info()?;
101 info!("=> {:?}", display_info);
102 self.rect = Some(display_info.rect);
103
104 self.resource_create_2d(
106 RESOURCE_ID_FB,
107 display_info.rect.width,
108 display_info.rect.height,
109 )?;
110
111 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 self.resource_attach_backing(RESOURCE_ID_FB, frame_buffer_dma.paddr() as u64, size)?;
117
118 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 pub fn flush(&mut self) -> Result {
128 let rect = self.rect.ok_or(Error::NotReady)?;
129 self.transfer_to_host_2d(rect, 0, RESOURCE_ID_FB)?;
131 self.resource_flush(rect, RESOURCE_ID_FB)?;
133 Ok(())
134 }
135
136 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 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 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 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 self.transport.queue_unset(QUEUE_TRANSMIT);
293 self.transport.queue_unset(QUEUE_CURSOR);
294 }
295}
296
297#[repr(C)]
298struct Config {
299 events_read: ReadOnly<u32>,
301
302 events_clear: WriteOnly<u32>,
304
305 num_scanouts: Volatile<u32>,
309}
310
311const EVENT_DISPLAY: u32 = 1 << 0;
313
314bitflags! {
315 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
316 struct Features: u64 {
317 const VIRGL = 1 << 0;
319 const EDID = 1 << 1;
321
322 const NOTIFY_ON_EMPTY = 1 << 24; const ANY_LAYOUT = 1 << 27; const RING_INDIRECT_DESC = 1 << 28;
326 const RING_EVENT_IDX = 1 << 29;
327 const UNUSED = 1 << 30; const VERSION_1 = 1 << 32; 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 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, 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};