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