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