├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── config.rs ├── dump.rs ├── format.rs ├── human_number.rs ├── ioctl.rs ├── lib.rs ├── log.rs ├── nvidia ├── ctrl0000vgpu.rs ├── ctrl0080gpu.rs ├── ctrl2080bus.rs ├── ctrl2080gpu.rs ├── ctrl9096.rs ├── ctrla081.rs ├── ctrla082.rs ├── error.rs ├── ioctl.rs ├── mod.rs ├── nvos.rs └── nvtypes.rs ├── to_bytes.rs ├── utils.rs └── uuid.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vgpu_unlock-rs" 3 | version = "2.5.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | ctor = "0.2.7" 11 | libc = "0.2.102" 12 | parking_lot = "0.12.1" 13 | serde = { version = "1.0.130", features = ["derive"] } 14 | toml = "0.8.11" 15 | 16 | [features] 17 | # Feature flag to enable syntactic sugar for proxmox users 18 | default = ["proxmox"] 19 | proxmox = [] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jonathan Johansson 4 | Copyright (c) 2021 Matt Bilker 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust-based vgpu\_unlock 2 | 3 | Unlock vGPU functionality for consumer-grade NVIDIA GPUs. 4 | 5 | **This tool is to be used with the kernel patches from the main 6 | [`vgpu_unlock`](https://github.com/DualCoder/vgpu_unlock) repository!** 7 | 8 | ## Dependencies 9 | 10 | * This tool requires Rust. You can install it via your package manager or via 11 | [](https://rustup.rs). 12 | * Rust requires a linker to be installed to be able to create the shared 13 | library. Typically, this is installed with the C compiler through your 14 | distribution's package manager. 15 | * The dependencies from the main `vgpu_unlock` project excluding Python and 16 | `frida`. 17 | 18 | ## Installation 19 | 20 | In the following instructions `` needs to be replaced 21 | with the path to this repository on the target system. 22 | 23 | Install the NVIDIA vGPU driver and kernel driver patches as detailed in the 24 | main `vgpu_unlock` project README. Ignore the steps regarding editing the 25 | systemd service unit files. 26 | 27 | Run `cargo build --release` to compile the shared library. 28 | 29 | Create the directories `/etc/systemd/system/nvidia-vgpud.service.d` and 30 | `/etc/systemd/system/nvidia-vgpu-mgr.service.d`. 31 | 32 | Create the files `/etc/systemd/system/nvidia-vgpud.service.d/vgpu_unlock.conf` 33 | and `/etc/systemd/system/nvidia-vgpu-mgr.service.d/vgpu_unlock.conf` 34 | with the following: 35 | ``` 36 | [Service] 37 | Environment=LD_PRELOAD=/target/release/libvgpu_unlock_rs.so 38 | ``` 39 | 40 | Create the directory `/etc/vgpu_unlock` which will house the vGPU profile 41 | override configuration file. 42 | 43 | Create the file `/etc/vgpu_unlock/profile_override.toml` with the profile 44 | fields that are to be overridden. The following is an example for `nvidia-55` 45 | (GRID P40-2A) that sets the number of heads to 1, sets the framebuffer to be 46 | 1920x1080 (1920 * 1080 = 2073600 pixels), enables CUDA, and disables the 47 | frame-rate limiter. 48 | 49 | ```toml 50 | [profile.nvidia-55] 51 | num_displays = 1 52 | display_width = 1920 53 | display_height = 1080 54 | max_pixels = 2073600 55 | cuda_enabled = 1 56 | frl_enabled = 0 57 | ``` 58 | 59 | If you want to enable VM migration or snapshotting, you must 60 | recompile the `nvidia-vgpu-vfio` kernel module with `NV_KVM_MIGRATION_UAPI` 61 | equal to 1. Then, create the file `/etc/vgpu_unlock/config.toml` and add the 62 | following: 63 | 64 | ```toml 65 | unlock_migration = true 66 | ``` 67 | 68 | Happy hacking! 69 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use serde::Deserialize; 4 | 5 | struct Defaults; 6 | 7 | impl Defaults { 8 | #[inline] 9 | const fn unlock() -> bool { 10 | true 11 | } 12 | 13 | #[inline] 14 | const fn unlock_migration() -> bool { 15 | false 16 | } 17 | } 18 | 19 | #[derive(Deserialize)] 20 | pub struct Config { 21 | #[serde(default = "Defaults::unlock")] 22 | pub unlock: bool, 23 | #[serde(default = "Defaults::unlock_migration")] 24 | pub unlock_migration: bool, 25 | } 26 | 27 | impl Default for Config { 28 | #[inline] 29 | fn default() -> Self { 30 | Self { 31 | unlock: Defaults::unlock(), 32 | unlock_migration: Defaults::unlock_migration(), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/dump.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::cmp; 4 | use std::fmt::Write; 5 | 6 | #[allow(dead_code)] 7 | pub fn dump(data: &[u8]) -> String { 8 | let mut output = String::new(); 9 | 10 | if data.is_empty() { 11 | output.push_str("\t--- Empty ---"); 12 | } 13 | 14 | for i in (0..data.len()).step_by(16) { 15 | let to_print = cmp::min(16, data.len() - i); 16 | let to_pad = 16 - to_print; 17 | let data = &data[i..i + to_print]; 18 | 19 | let _ = write!(output, " {:08x}", i); 20 | 21 | for byte in data { 22 | let _ = write!(output, " {:02x}", byte); 23 | } 24 | 25 | for _ in 0..to_pad { 26 | output.push_str(" "); 27 | } 28 | 29 | output.push(' '); 30 | output.extend(data.iter().map(|&c| { 31 | if !(0x20..0x7f).contains(&c) { 32 | '.' 33 | } else { 34 | c as char 35 | } 36 | })); 37 | output.push('\n'); 38 | } 39 | 40 | output.push('\n'); 41 | 42 | output 43 | } 44 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | // (`char::decode_utf16` and `char::REPLACEMENT_CHAR` were exposed on the fundamental type 4 | // in Rust 1.52) 5 | use std::char; 6 | use std::fmt::{self, Write}; 7 | 8 | use crate::to_bytes::ToBytes; 9 | use crate::utils; 10 | 11 | pub struct CStrFormat<'a>(pub &'a [u8]); 12 | 13 | impl<'a> fmt::Debug for CStrFormat<'a> { 14 | #[inline] 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | fmt::Display::fmt(self, f) 17 | } 18 | } 19 | 20 | impl<'a> fmt::Display for CStrFormat<'a> { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | let s = utils::from_c_str(self.0); 23 | 24 | fmt::Debug::fmt(&s, f) 25 | } 26 | } 27 | 28 | pub struct HexFormat(pub T); 29 | 30 | impl fmt::Debug for HexFormat { 31 | #[inline] 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | fmt::Display::fmt(self, f) 34 | } 35 | } 36 | 37 | impl fmt::Display for HexFormat { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | write!(f, "0x{:x}", self.0) 40 | } 41 | } 42 | 43 | pub struct HexFormatSlice<'a, T>(pub &'a [T]); 44 | 45 | impl<'a, T: Copy + fmt::LowerHex + ToBytes> fmt::Debug for HexFormatSlice<'a, T> { 46 | #[inline] 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | fmt::Display::fmt(self, f) 49 | } 50 | } 51 | 52 | impl<'a, T: Copy + fmt::LowerHex + ToBytes> fmt::Display for HexFormatSlice<'a, T> { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | if self.0.is_empty() { 55 | f.write_str("[]") 56 | } else { 57 | f.write_str("0x")?; 58 | 59 | for v in self.0.iter() { 60 | for b in v.to_ne_bytes() { 61 | write!(f, "{b:02x}")?; 62 | } 63 | } 64 | 65 | Ok(()) 66 | } 67 | } 68 | } 69 | 70 | pub struct WideCharFormat<'a>(pub &'a [u16]); 71 | 72 | impl<'a> fmt::Debug for WideCharFormat<'a> { 73 | #[inline] 74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 75 | f.write_char('"')?; 76 | 77 | fmt::Display::fmt(self, f)?; 78 | 79 | f.write_char('"') 80 | } 81 | } 82 | 83 | impl<'a> fmt::Display for WideCharFormat<'a> { 84 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 85 | for item in char::decode_utf16(self.0.iter().copied().take_while(|&ch| ch != 0)) { 86 | f.write_char(item.unwrap_or(char::REPLACEMENT_CHARACTER))?; 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | pub struct StraightFormat(pub T); 94 | 95 | impl fmt::Debug for StraightFormat { 96 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 97 | write!(f, "{:?}", self.0) 98 | } 99 | } 100 | 101 | impl fmt::Display for StraightFormat { 102 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 103 | write!(f, "{}", self.0) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/human_number.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::convert::TryInto; 4 | use std::fmt; 5 | 6 | use serde::de::{Deserializer, Error, Unexpected, Visitor}; 7 | 8 | pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> 9 | where 10 | D: Deserializer<'de>, 11 | { 12 | deserializer.deserialize_any(HumanNumberVisitor) 13 | } 14 | 15 | struct HumanNumberVisitor; 16 | 17 | impl<'de> Visitor<'de> for HumanNumberVisitor { 18 | type Value = Option; 19 | 20 | fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 21 | formatter.write_str("unsigned number or quoted human-readable unsigned number") 22 | } 23 | 24 | fn visit_i64(self, v: i64) -> Result 25 | where 26 | E: Error, 27 | { 28 | v.try_into() 29 | .map_err(Error::custom) 30 | .and_then(|v| self.visit_u64(v)) 31 | } 32 | 33 | fn visit_u64(self, v: u64) -> Result 34 | where 35 | E: Error, 36 | { 37 | Ok(Some(v)) 38 | } 39 | 40 | fn visit_str(self, v: &str) -> Result 41 | where 42 | E: Error, 43 | { 44 | let v = v.trim(); 45 | 46 | if v.is_empty() { 47 | return Err(Error::invalid_value( 48 | Unexpected::Str(v), 49 | &"non-empty string", 50 | )); 51 | } 52 | 53 | // Using `bytes` instead of `chars` here because `split_at` takes a byte offset 54 | match v 55 | .bytes() 56 | .map(|byte| byte as char) 57 | .position(|ch| !(ch.is_numeric() || ch == '.')) 58 | { 59 | Some(unit_index) => { 60 | let (value, unit) = v.split_at(unit_index); 61 | let value: f64 = value.parse().map_err(Error::custom)?; 62 | 63 | let multiple: u64 = match unit.trim_start() { 64 | "KB" | "kB" => 1000, 65 | "MB" => 1000 * 1000, 66 | "GB" => 1000 * 1000 * 1000, 67 | "TB" => 1000 * 1000 * 1000 * 1000, 68 | 69 | "KiB" => 1024, 70 | "MiB" => 1024 * 1024, 71 | "GiB" => 1024 * 1024 * 1024, 72 | "TiB" => 1024 * 1024 * 1024 * 1024, 73 | 74 | unit => { 75 | return Err(Error::invalid_value( 76 | Unexpected::Str(unit), 77 | &"known unit of measurement", 78 | )) 79 | } 80 | }; 81 | let value = value * (multiple as f64); 82 | 83 | Ok(Some(value.round() as u64)) 84 | } 85 | None => { 86 | // No unit found, interpret as raw number 87 | v.parse().map(Some).map_err(Error::custom) 88 | } 89 | } 90 | } 91 | 92 | fn visit_none(self) -> Result 93 | where 94 | E: Error, 95 | { 96 | Ok(None) 97 | } 98 | 99 | fn visit_some(self, deserializer: D) -> Result 100 | where 101 | D: Deserializer<'de>, 102 | { 103 | deserializer.deserialize_any(self) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod test { 109 | use serde::de::value::Error; 110 | use serde::de::IntoDeserializer; 111 | 112 | use super::deserialize; 113 | 114 | #[test] 115 | fn test_deserialize() { 116 | fn check_result(input: &str, value: u64) { 117 | assert_eq!( 118 | deserialize(input.into_deserializer()), 119 | Ok::<_, Error>(Some(value)) 120 | ); 121 | } 122 | 123 | check_result("1234", 1234); 124 | check_result("1234 ", 1234); 125 | check_result(" 1234", 1234); 126 | check_result(" 1234 ", 1234); 127 | 128 | check_result("1234kB", 1234 * 1000); 129 | check_result("1234KB", 1234 * 1000); 130 | check_result("1234MB", 1234 * 1000 * 1000); 131 | check_result("1234GB", 1234 * 1000 * 1000 * 1000); 132 | check_result("1234TB", 1234 * 1000 * 1000 * 1000 * 1000); 133 | 134 | check_result("1234KiB", 1234 * 1024); 135 | check_result("1234MiB", 1234 * 1024 * 1024); 136 | check_result("1234GiB", 1234 * 1024 * 1024 * 1024); 137 | check_result("1234TiB", 1234 * 1024 * 1024 * 1024 * 1024); 138 | 139 | check_result("1234 kB", 1234 * 1000); 140 | check_result("1234 KB", 1234 * 1000); 141 | check_result("1234 MB", 1234 * 1000 * 1000); 142 | check_result("1234 GB", 1234 * 1000 * 1000 * 1000); 143 | check_result("1234 TB", 1234 * 1000 * 1000 * 1000 * 1000); 144 | 145 | check_result("1234 KiB", 1234 * 1024); 146 | check_result("1234 MiB", 1234 * 1024 * 1024); 147 | check_result("1234 GiB", 1234 * 1024 * 1024 * 1024); 148 | check_result("1234 TiB", 1234 * 1024 * 1024 * 1024 * 1024); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/ioctl.rs: -------------------------------------------------------------------------------- 1 | // SPDX-FileContributor: musl 2 | // SPDX-License-Identifier: MIT 3 | // 4 | // Derived from musl 1.2.3 `generic/bits/ioctl.h`. musl 1.2.3 is MIT licensed as well so deriving 5 | // constant and function definitions here from musl 1.2.3 should be alright. 6 | 7 | use std::mem; 8 | 9 | use libc::c_ulong; 10 | 11 | const _IOC_WRITE: c_ulong = 1; 12 | const _IOC_READ: c_ulong = 2; 13 | 14 | #[allow(non_snake_case)] 15 | #[inline] 16 | pub const fn _IOC(a: c_ulong, b: c_ulong, c: c_ulong, d: c_ulong) -> c_ulong { 17 | a << 30 | b << 8 | c | d << 16 18 | } 19 | 20 | #[allow(non_snake_case)] 21 | #[inline] 22 | pub const fn _IOWR(b: c_ulong, c: c_ulong) -> c_ulong { 23 | _IOC(_IOC_READ | _IOC_WRITE, b, c, mem::size_of::() as c_ulong) 24 | } 25 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | //! Credit to community members for most of the work with notable contributions by: 4 | //! 5 | //! - DualCoder for the original [`vgpu_unlock`](https://github.com/DualCoder/vgpu_unlock) 6 | //! - DualCoder, snowman, Felix, Elec for vGPU profile modification at runtime 7 | //! - NVIDIA for their open-source driver [sources](https://github.com/NVIDIA/open-gpu-kernel-modules) 8 | //! - Arc Compute for their work on Mdev-GPU and GVM documenting more field names in the vGPU 9 | //! configuration structure 10 | 11 | use std::cmp; 12 | use std::collections::HashMap; 13 | use std::env; 14 | use std::fs; 15 | use std::io::{ErrorKind, Write}; 16 | use std::mem; 17 | use std::os::raw::{c_int, c_ulong, c_void}; 18 | use std::os::unix::io::RawFd; 19 | use std::path::PathBuf; 20 | use std::process; 21 | use std::str; 22 | 23 | use ctor::ctor; 24 | use libc::RTLD_NEXT; 25 | use parking_lot::Mutex; 26 | use serde::Deserialize; 27 | 28 | mod config; 29 | mod dump; 30 | mod format; 31 | mod human_number; 32 | mod ioctl; 33 | mod log; 34 | mod nvidia; 35 | mod to_bytes; 36 | mod utils; 37 | mod uuid; 38 | 39 | use crate::config::Config; 40 | use crate::format::WideCharFormat; 41 | use crate::log::{error, info}; 42 | use crate::nvidia::ctrl0000vgpu::{ 43 | Nv0000CtrlVgpuCreateDeviceParams, Nv0000CtrlVgpuGetStartDataParams, 44 | NV0000_CTRL_CMD_VGPU_CREATE_DEVICE, NV0000_CTRL_CMD_VGPU_GET_START_DATA, 45 | }; 46 | use crate::nvidia::ctrl0080gpu::{ 47 | Nv0080CtrlGpuGetVirtualizationModeParams, NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE, 48 | NV0080_CTRL_GPU_VIRTUALIZATION_MODE_HOST, 49 | }; 50 | use crate::nvidia::ctrl2080bus::{Nv2080CtrlBusGetPciInfoParams, NV2080_CTRL_CMD_BUS_GET_PCI_INFO}; 51 | use crate::nvidia::ctrl2080gpu::NV2080_CTRL_CMD_GPU_GET_INFOROM_OBJECT_VERSION; 52 | use crate::nvidia::ctrl9096::NV9096_CTRL_CMD_GET_ZBC_CLEAR_TABLE; 53 | use crate::nvidia::ctrla081::{ 54 | NvA081CtrlCmdVgpuConfigGetMigrationCapParams, NvA081CtrlVgpuConfigGetVgpuTypeInfoParams, 55 | NvA081CtrlVgpuInfo, NVA081_CTRL_CMD_VGPU_CONFIG_GET_MIGRATION_CAP, 56 | NVA081_CTRL_CMD_VGPU_CONFIG_GET_VGPU_TYPE_INFO, 57 | }; 58 | use crate::nvidia::ctrla082::{ 59 | NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams, 60 | NVA082_CTRL_CMD_HOST_VGPU_DEVICE_GET_VGPU_TYPE_INFO, 61 | }; 62 | use crate::nvidia::error::{ 63 | NV_ERR_BUSY_RETRY, NV_ERR_NOT_SUPPORTED, NV_ERR_OBJECT_NOT_FOUND, NV_OK, 64 | }; 65 | use crate::nvidia::nvos::{Nvos54Parameters, NV_ESC_RM_CONTROL}; 66 | #[cfg(feature = "proxmox")] 67 | use crate::utils::uuid_to_vmid; 68 | use crate::uuid::Uuid; 69 | 70 | static LAST_MDEV_UUID: Mutex> = parking_lot::const_mutex(None); 71 | 72 | #[ctor] 73 | static CONFIG: Config = { 74 | match fs::read_to_string(DEFAULT_CONFIG_PATH) { 75 | Ok(config) => match toml::from_str::(&config) { 76 | Ok(config) => config, 77 | Err(e) => { 78 | eprintln!("Failed to decode config: {}", e); 79 | 80 | process::abort(); 81 | } 82 | }, 83 | Err(e) => { 84 | if e.kind() != ErrorKind::NotFound { 85 | eprintln!("Failed to read config: {}", e); 86 | } 87 | 88 | Default::default() 89 | } 90 | } 91 | }; 92 | 93 | const DEFAULT_CONFIG_PATH: &str = "/etc/vgpu_unlock/config.toml"; 94 | const DEFAULT_PROFILE_OVERRIDE_CONFIG_PATH: &str = "/etc/vgpu_unlock/profile_override.toml"; 95 | 96 | trait VgpuConfigLike { 97 | fn vgpu_type(&mut self) -> &mut u32; 98 | fn vgpu_name(&mut self) -> &mut [u8; 32]; 99 | fn vgpu_class(&mut self) -> &mut [u8; 32]; 100 | //fn vgpu_signature(&mut self) -> &mut [u8; 128]; 101 | fn license(&mut self) -> &mut [u8; 128]; 102 | fn max_instance(&mut self) -> &mut u32; 103 | fn num_heads(&mut self) -> &mut u32; 104 | fn max_resolution_x(&mut self) -> &mut u32; 105 | fn max_resolution_y(&mut self) -> &mut u32; 106 | fn max_pixels(&mut self) -> &mut u32; 107 | fn frl_config(&mut self) -> &mut u32; 108 | fn cuda_enabled(&mut self) -> &mut u32; 109 | fn ecc_supported(&mut self) -> &mut u32; 110 | fn mig_instance_size(&mut self) -> &mut u32; 111 | fn multi_vgpu_supported(&mut self) -> &mut u32; 112 | fn vdev_id(&mut self) -> &mut u64; 113 | fn pdev_id(&mut self) -> &mut u64; 114 | //fn profile_size(&mut self) -> Option<&mut u64>; 115 | fn fb_length(&mut self) -> &mut u64; 116 | fn mappable_video_size(&mut self) -> &mut u64; 117 | fn fb_reservation(&mut self) -> &mut u64; 118 | fn encoder_capacity(&mut self) -> &mut u32; 119 | fn bar1_length(&mut self) -> &mut u64; 120 | fn frl_enable(&mut self) -> &mut u32; 121 | fn adapter_name(&mut self) -> &mut [u8; 64]; 122 | fn adapter_name_unicode(&mut self) -> &mut [u16; 64]; 123 | fn short_gpu_name_string(&mut self) -> &mut [u8; 64]; 124 | fn licensed_product_name(&mut self) -> &mut [u8; 128]; 125 | //fn vgpu_extra_params(&mut self) -> &mut [u8]; 126 | } 127 | 128 | macro_rules! impl_trait_fn { 129 | ($field:ident, $t:ty) => { 130 | fn $field(&mut self) -> &mut $t { 131 | &mut self.$field 132 | } 133 | }; 134 | ($source_field:ident => $target_field:ident, $t:ty) => { 135 | fn $target_field(&mut self) -> &mut $t { 136 | &mut self.$source_field 137 | } 138 | }; 139 | } 140 | macro_rules! impl_trait_fn_aligned { 141 | ($field:ident, $t:ty) => { 142 | fn $field(&mut self) -> &mut $t { 143 | &mut self.$field.0 144 | } 145 | }; 146 | } 147 | 148 | impl VgpuConfigLike for NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams { 149 | impl_trait_fn!(vgpu_type, u32); 150 | impl_trait_fn!(vgpu_name, [u8; 32]); 151 | impl_trait_fn!(vgpu_class, [u8; 32]); 152 | //impl_trait_fn!(vgpu_signature, [u8; 128]); 153 | impl_trait_fn!(license, [u8; 128]); 154 | impl_trait_fn!(max_instance, u32); 155 | impl_trait_fn!(num_heads, u32); 156 | impl_trait_fn!(max_resolution_x, u32); 157 | impl_trait_fn!(max_resolution_y, u32); 158 | impl_trait_fn!(max_pixels, u32); 159 | impl_trait_fn!(frl_config, u32); 160 | impl_trait_fn!(cuda_enabled, u32); 161 | impl_trait_fn!(ecc_supported, u32); 162 | impl_trait_fn!(mig_instance_size, u32); 163 | impl_trait_fn!(multi_vgpu_supported, u32); 164 | impl_trait_fn!(vdev_id, u64); 165 | impl_trait_fn!(pdev_id, u64); 166 | 167 | /* 168 | fn profile_size(&mut self) -> Option<&mut u64> { 169 | None 170 | } 171 | */ 172 | 173 | impl_trait_fn!(fb_length, u64); 174 | impl_trait_fn!(mappable_video_size, u64); 175 | impl_trait_fn!(fb_reservation, u64); 176 | impl_trait_fn!(encoder_capacity, u32); 177 | impl_trait_fn!(bar1_length, u64); 178 | impl_trait_fn!(frl_enable, u32); 179 | impl_trait_fn!(adapter_name, [u8; 64]); 180 | impl_trait_fn!(adapter_name_unicode, [u16; 64]); 181 | impl_trait_fn!(short_gpu_name_string, [u8; 64]); 182 | impl_trait_fn!(licensed_product_name, [u8; 128]); 183 | //impl_trait_fn!(vgpu_extra_params, [u8]); 184 | } 185 | 186 | impl VgpuConfigLike for NvA081CtrlVgpuInfo { 187 | impl_trait_fn!(vgpu_type, u32); 188 | impl_trait_fn!(vgpu_name, [u8; 32]); 189 | impl_trait_fn!(vgpu_class, [u8; 32]); 190 | //impl_trait_fn!(vgpu_signature, [u8; 128]); 191 | impl_trait_fn!(license, [u8; 128]); 192 | impl_trait_fn!(max_instance, u32); 193 | impl_trait_fn!(num_heads, u32); 194 | impl_trait_fn!(max_resolution_x, u32); 195 | impl_trait_fn!(max_resolution_y, u32); 196 | impl_trait_fn!(max_pixels, u32); 197 | impl_trait_fn!(frl_config, u32); 198 | impl_trait_fn!(cuda_enabled, u32); 199 | impl_trait_fn!(ecc_supported, u32); 200 | impl_trait_fn!(gpu_instance_size => mig_instance_size, u32); 201 | impl_trait_fn!(multi_vgpu_supported, u32); 202 | impl_trait_fn_aligned!(vdev_id, u64); 203 | impl_trait_fn_aligned!(pdev_id, u64); 204 | 205 | /* 206 | fn profile_size(&mut self) -> Option<&mut u64> { 207 | Some(&mut self.profile_size.0) 208 | } 209 | */ 210 | 211 | impl_trait_fn_aligned!(fb_length, u64); 212 | impl_trait_fn_aligned!(mappable_video_size, u64); 213 | impl_trait_fn_aligned!(fb_reservation, u64); 214 | impl_trait_fn!(encoder_capacity, u32); 215 | impl_trait_fn_aligned!(bar1_length, u64); 216 | impl_trait_fn!(frl_enable, u32); 217 | impl_trait_fn!(adapter_name, [u8; 64]); 218 | impl_trait_fn!(adapter_name_unicode, [u16; 64]); 219 | impl_trait_fn!(short_gpu_name_string, [u8; 64]); 220 | impl_trait_fn!(licensed_product_name, [u8; 128]); 221 | //impl_trait_fn!(vgpu_extra_params, [u8]); 222 | } 223 | 224 | #[derive(Deserialize)] 225 | struct ProfileOverridesConfig { 226 | #[serde(default)] 227 | profile: HashMap, 228 | #[serde(default)] 229 | mdev: HashMap, 230 | #[cfg(feature = "proxmox")] 231 | #[serde(default)] 232 | vm: HashMap, 233 | } 234 | 235 | #[derive(Deserialize)] 236 | struct VgpuProfileOverride { 237 | gpu_type: Option, 238 | card_name: Option, 239 | vgpu_type: Option, 240 | features: Option, 241 | max_instances: Option, 242 | num_displays: Option, 243 | display_width: Option, 244 | display_height: Option, 245 | max_pixels: Option, 246 | frl_config: Option, 247 | cuda_enabled: Option, 248 | ecc_supported: Option, 249 | mig_instance_size: Option, 250 | multi_vgpu_supported: Option, 251 | pci_id: Option, 252 | pci_device_id: Option, 253 | #[serde(default, with = "human_number")] 254 | framebuffer: Option, 255 | #[serde(default, with = "human_number")] 256 | mappable_video_size: Option, 257 | #[serde(default, with = "human_number")] 258 | framebuffer_reservation: Option, 259 | encoder_capacity: Option, 260 | bar1_length: Option, 261 | frl_enabled: Option, 262 | adapter_name: Option, 263 | short_gpu_name: Option, 264 | license_type: Option, 265 | } 266 | 267 | fn check_size(name: &str, actual_size: usize, expected_size: usize) -> bool { 268 | if actual_size != expected_size { 269 | error!( 270 | "Parameters size for {} was {} bytes, expected {} bytes", 271 | name, actual_size, expected_size 272 | ); 273 | 274 | false 275 | } else { 276 | true 277 | } 278 | } 279 | 280 | /// # Safety 281 | /// 282 | /// This is actually unsafe since `ioctl` is variadic. All the `ioctl` calls in the 283 | /// 460.32.04 `nvidia-vgpu-mgr` and `nvidia-vgpud` binaries use only one argument. 284 | #[no_mangle] 285 | pub unsafe extern "C" fn ioctl(fd: RawFd, request: c_ulong, argp: *mut c_void) -> c_int { 286 | static mut IOCTL_FN_PTR: Option c_int> = None; 287 | 288 | //info!("ioctl({}, {}, {:?})", fd, request, data); 289 | 290 | let next_ioctl = match IOCTL_FN_PTR { 291 | Some(func) => func, 292 | None => { 293 | let next_ioctl = mem::transmute(libc::dlsym(RTLD_NEXT, b"ioctl\0".as_ptr() as _)); 294 | 295 | IOCTL_FN_PTR = mem::transmute(next_ioctl); 296 | 297 | next_ioctl 298 | } 299 | }; 300 | 301 | let ret = next_ioctl(fd, request, argp); 302 | 303 | if request != NV_ESC_RM_CONTROL { 304 | // Not a call we care about. 305 | return ret; 306 | } 307 | 308 | if ret < 0 { 309 | // Call failed. 310 | return ret; 311 | } 312 | 313 | // Safety: NVIDIA's driver itself uses `sizeof` when calculating the ioctl number and so does 314 | // this hook so the structure passed in should be of the correct size. 315 | let io_data: &mut Nvos54Parameters = &mut *argp.cast(); 316 | 317 | if io_data.status == NV_ERR_BUSY_RETRY { 318 | // Driver will try again. 319 | return ret; 320 | } 321 | 322 | //info!("{:#x?}", io_data); 323 | 324 | macro_rules! check_size { 325 | ($name:ident, $expected_type:ty) => { 326 | check_size( 327 | stringify!($name), 328 | io_data.params_size as usize, 329 | mem::size_of::<$expected_type>(), 330 | ) 331 | }; 332 | ($name:ident, size: $expected_size:expr) => { 333 | check_size( 334 | stringify!($name), 335 | io_data.params_size as usize, 336 | $expected_size, 337 | ) 338 | }; 339 | } 340 | 341 | match io_data.cmd { 342 | NV2080_CTRL_CMD_BUS_GET_PCI_INFO 343 | if check_size!( 344 | NV2080_CTRL_CMD_BUS_GET_PCI_INFO, 345 | Nv2080CtrlBusGetPciInfoParams 346 | ) && CONFIG.unlock => 347 | { 348 | let params: &mut Nv2080CtrlBusGetPciInfoParams = &mut *io_data.params.cast(); 349 | 350 | let orig_device_id = params.pci_device_id; 351 | let orig_sub_system_id = params.pci_sub_system_id; 352 | 353 | let actual_device_id = (orig_device_id & 0xffff0000) >> 16; 354 | let actual_sub_system_id = (orig_sub_system_id & 0xffff0000) >> 16; 355 | 356 | let (spoofed_devid, spoofed_subsysid) = match actual_device_id { 357 | // Maxwell 358 | 0x1340..=0x13bd | 0x174d..=0x179c => { 359 | // Tesla M10 360 | (0x13bd, 0x1160) 361 | } 362 | // Maxwell 2.0 363 | 0x13c0..=0x1436 | 0x1617..=0x1667 | 0x17c2..=0x17fd => { 364 | // Tesla M60 365 | (0x13f2, actual_sub_system_id) 366 | } 367 | // Pascal 368 | 0x15f0 | 0x15f1 | 0x1b00..=0x1d56 | 0x1725..=0x172f => { 369 | // Tesla P40 370 | (0x1b38, actual_sub_system_id) 371 | } 372 | // GV100 Volta 373 | // 374 | // 0x1d81 = TITAN V 375 | // 0x1dba = Quadro GV100 32GB 376 | 0x1d81 | 0x1dba => { 377 | // Tesla V100 32GB PCIE 378 | (0x1db6, actual_sub_system_id) 379 | } 380 | // Turing 381 | 0x1e02..=0x1ff9 | 0x2182..=0x21d1 => { 382 | // Quadro RTX 6000 383 | (0x1e30, 0x12ba) 384 | } 385 | // Ampere 386 | 0x2200..=0x2600 => { 387 | // RTX A6000 388 | (0x2230, actual_sub_system_id) 389 | } 390 | _ => (actual_device_id, actual_sub_system_id), 391 | }; 392 | 393 | params.pci_device_id = (orig_device_id & 0xffff) | (spoofed_devid << 16); 394 | params.pci_sub_system_id = (orig_sub_system_id & 0xffff) | (spoofed_subsysid << 16); 395 | } 396 | NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE 397 | // 18.0 driver sends larger struct with size 8 bytes. Only extra members added at the end, 398 | // nothing in between or changed, so accessing the larger struct is "safe" 399 | if io_data.params_size == 8 400 | || check_size!( 401 | NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE, 402 | Nv0080CtrlGpuGetVirtualizationModeParams 403 | ) && CONFIG.unlock => 404 | { 405 | let params: &mut Nv0080CtrlGpuGetVirtualizationModeParams = &mut *io_data.params.cast(); 406 | 407 | // Set device type to vGPU capable. 408 | params.virtualization_mode = NV0080_CTRL_GPU_VIRTUALIZATION_MODE_HOST; 409 | } 410 | NVA081_CTRL_CMD_VGPU_CONFIG_GET_MIGRATION_CAP 411 | if check_size!( 412 | NVA081_CTRL_CMD_VGPU_CONFIG_GET_MIGRATION_CAP, 413 | NvA081CtrlCmdVgpuConfigGetMigrationCapParams 414 | ) && CONFIG.unlock_migration => 415 | { 416 | let params: &mut NvA081CtrlCmdVgpuConfigGetMigrationCapParams = 417 | &mut *io_data.params.cast(); 418 | 419 | params.migration_cap = 1; 420 | } 421 | _ => {} 422 | } 423 | 424 | if io_data.status == NV_OK { 425 | match io_data.cmd { 426 | NV0000_CTRL_CMD_VGPU_GET_START_DATA 427 | if check_size!( 428 | NV0000_CTRL_CMD_VGPU_GET_START_DATA, 429 | Nv0000CtrlVgpuGetStartDataParams 430 | ) => 431 | { 432 | let config: &Nv0000CtrlVgpuGetStartDataParams = &*io_data.params.cast(); 433 | info!("{:#?}", config); 434 | 435 | *LAST_MDEV_UUID.lock() = Some(config.mdev_uuid); 436 | } 437 | NV0000_CTRL_CMD_VGPU_CREATE_DEVICE 438 | // 18.0 driver sends larger struct with size 40 bytes. Only extra members added at the end, 439 | // nothing in between or changed, so accessing the larger struct is "safe" 440 | if io_data.params_size == 40 441 | || check_size!( 442 | NV0000_CTRL_CMD_VGPU_CREATE_DEVICE, 443 | Nv0000CtrlVgpuCreateDeviceParams 444 | ) => 445 | { 446 | // 17.0 driver provides mdev uuid as vgpu_name in this command 447 | let params: &mut Nv0000CtrlVgpuCreateDeviceParams = &mut *io_data.params.cast(); 448 | info!("{:#?}", params); 449 | 450 | *LAST_MDEV_UUID.lock() = Some(params.vgpu_name); 451 | } 452 | NVA081_CTRL_CMD_VGPU_CONFIG_GET_VGPU_TYPE_INFO => { 453 | // 18.0 driver sends larger struct with size 5232 bytes, 17.0 driver sends larger struct with size 5096 bytes. Only extra members added at the end, 454 | // nothing in between or changed, so accessing the larger struct is "safe" 455 | if io_data.params_size == 5232 456 | || io_data.params_size == 5096 457 | || check_size!( 458 | NVA081_CTRL_CMD_VGPU_CONFIG_GET_VGPU_TYPE_INFO, 459 | NvA081CtrlVgpuConfigGetVgpuTypeInfoParams 460 | ) 461 | { 462 | let params: &mut NvA081CtrlVgpuConfigGetVgpuTypeInfoParams = 463 | &mut *io_data.params.cast(); 464 | info!("{:#?}", params); 465 | 466 | if !handle_profile_override(&mut params.vgpu_type_info) { 467 | error!("Failed to apply profile override"); 468 | return -1; 469 | } 470 | } 471 | } 472 | NVA082_CTRL_CMD_HOST_VGPU_DEVICE_GET_VGPU_TYPE_INFO 473 | if check_size!( 474 | NVA082_CTRL_CMD_HOST_VGPU_DEVICE_GET_VGPU_TYPE_INFO, 475 | NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams 476 | ) => 477 | { 478 | let params: &mut NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams = 479 | &mut *io_data.params.cast(); 480 | info!("{:#?}", params); 481 | 482 | if !handle_profile_override(params) { 483 | error!("Failed to apply profile override"); 484 | return -1; 485 | } 486 | } 487 | _ => {} 488 | } 489 | } 490 | 491 | if io_data.status != NV_OK { 492 | // Things seems to work fine even if some operations that fail result in failed assertions. 493 | // So here we change the status value for these cases to cleanup the logs for 494 | // `nvidia-vgpu-mgr`. 495 | if io_data.cmd == 0xa0820104 || io_data.cmd == NV9096_CTRL_CMD_GET_ZBC_CLEAR_TABLE { 496 | io_data.status = NV_OK; 497 | } else { 498 | error!("cmd: {:#x} failed.", io_data.cmd); 499 | } 500 | } 501 | 502 | // Workaround for some Maxwell cards not supporting reading inforom. 503 | if io_data.cmd == NV2080_CTRL_CMD_GPU_GET_INFOROM_OBJECT_VERSION 504 | && io_data.status == NV_ERR_NOT_SUPPORTED 505 | { 506 | io_data.status = NV_ERR_OBJECT_NOT_FOUND; 507 | } 508 | 509 | ret 510 | } 511 | 512 | fn load_overrides() -> Result { 513 | let config_path = match env::var_os("VGPU_UNLOCK_PROFILE_OVERRIDE_CONFIG_PATH") { 514 | Some(path) => PathBuf::from(path), 515 | None => PathBuf::from(DEFAULT_PROFILE_OVERRIDE_CONFIG_PATH), 516 | }; 517 | 518 | let config_overrides = match fs::read_to_string(&config_path) { 519 | Ok(data) => data, 520 | Err(e) => { 521 | if e.kind() == ErrorKind::NotFound { 522 | error!("Config file '{}' not found", config_path.display()); 523 | return Err(true); 524 | } 525 | 526 | error!("Failed to read '{}': {}", config_path.display(), e); 527 | return Err(false); 528 | } 529 | }; 530 | 531 | Ok(config_overrides) 532 | } 533 | 534 | fn handle_profile_override(config: &mut C) -> bool { 535 | let config_overrides = match load_overrides() { 536 | Ok(overrides) => overrides, 537 | Err(e) => return e, 538 | }; 539 | 540 | let config_overrides: ProfileOverridesConfig = match toml::from_str(&config_overrides) { 541 | Ok(config) => config, 542 | Err(e) => { 543 | error!("Failed to decode config: {}", e); 544 | return false; 545 | } 546 | }; 547 | 548 | let vgpu_type = format!("nvidia-{}", config.vgpu_type()); 549 | let mdev_uuid = LAST_MDEV_UUID.lock().clone(); 550 | 551 | if let Some(config_override) = config_overrides.profile.get(vgpu_type.as_str()) { 552 | info!("Applying profile {} overrides", vgpu_type); 553 | 554 | if !apply_profile_override(config, &vgpu_type, config_override) { 555 | return false; 556 | } 557 | } 558 | if let Some(mdev_uuid) = mdev_uuid.map(|uuid| uuid.to_string()) { 559 | if let Some(config_override) = config_overrides.mdev.get(mdev_uuid.as_str()) { 560 | info!("Applying mdev UUID {} profile overrides", mdev_uuid); 561 | 562 | if !apply_profile_override(config, &vgpu_type, config_override) { 563 | return false; 564 | } 565 | } 566 | } 567 | 568 | #[cfg(feature = "proxmox")] 569 | if let Some(vmid) = mdev_uuid.and_then(uuid_to_vmid) { 570 | let vmid = vmid.to_string(); 571 | if let Some(config_override) = config_overrides.vm.get(vmid.as_str()) { 572 | info!("Applying proxmox VMID {} profile overrides", vmid); 573 | 574 | if !apply_profile_override(config, &vgpu_type, config_override) { 575 | return false; 576 | } 577 | } 578 | } 579 | 580 | true 581 | } 582 | 583 | fn apply_profile_override( 584 | config: &mut C, 585 | vgpu_type: &str, 586 | config_override: &VgpuProfileOverride, 587 | ) -> bool { 588 | macro_rules! patch_msg { 589 | ($target_field:ident, $value:expr) => { 590 | info!( 591 | "Patching {}/{}: {} -> {}", 592 | vgpu_type, 593 | stringify!($target_field), 594 | config.$target_field(), 595 | $value 596 | ); 597 | }; 598 | ($target_field:ident, $preprocess:expr, $value:expr) => { 599 | info!( 600 | "Patching {}/{}: {} -> {}", 601 | vgpu_type, 602 | stringify!($target_field), 603 | $preprocess(config.$target_field()), 604 | $value 605 | ); 606 | }; 607 | } 608 | macro_rules! error_too_long { 609 | ($target_field:ident, $value:expr) => { 610 | error!( 611 | "Patching {}/{}: value '{}' is too long", 612 | vgpu_type, 613 | stringify!($target_field), 614 | $value 615 | ); 616 | 617 | return false; 618 | }; 619 | } 620 | 621 | macro_rules! handle_override { 622 | // Override entrypoint when the same field name is used as the source and target without 623 | // an explicit `=>`. 624 | ( 625 | class: $class:ident, 626 | source_field: $field:ident, 627 | ) => { 628 | handle_override! { 629 | class: $class, 630 | source_field: $field, 631 | target_field: $field, 632 | } 633 | }; 634 | 635 | // Override entrypoint when both the source and target field names are defined explicitly. 636 | ( 637 | class: $class:ident, 638 | source_field: $source_field:ident, 639 | target_field: $target_field:ident, 640 | ) => { 641 | if let Some(value) = config_override.$source_field.as_ref() { 642 | handle_override! { 643 | class: $class, 644 | value: value, 645 | source_field: $source_field, 646 | target_field: $target_field, 647 | } 648 | } 649 | }; 650 | 651 | // The following are override handlers for each field class type (`bool`, `copy`, `str`, 652 | // and `wide_str`). 653 | ( 654 | class: bool, 655 | value: $value:ident, 656 | source_field: $source_field:ident, 657 | target_field: $target_field:ident, 658 | ) => { 659 | let $value = cmp::max(cmp::min(*$value, 1), 0); 660 | 661 | patch_msg!($target_field, $value); 662 | 663 | *config.$target_field() = $value; 664 | }; 665 | ( 666 | class: copy, 667 | value: $value:ident, 668 | source_field: $source_field:ident, 669 | target_field: $target_field:ident, 670 | ) => { 671 | patch_msg!($target_field, $value); 672 | 673 | *config.$target_field() = *$value; 674 | }; 675 | ( 676 | class: str, 677 | value: $value:ident, 678 | source_field: $source_field:ident, 679 | target_field: $target_field:ident, 680 | ) => { 681 | let value_bytes = $value.as_bytes(); 682 | 683 | // Use `len - 1` to account for the required NULL terminator. 684 | if value_bytes.len() > config.$target_field().len() - 1 { 685 | error_too_long!($target_field, $value); 686 | } else { 687 | patch_msg!($target_field, utils::from_c_str, $value); 688 | 689 | // Zero out the field first. 690 | // (`fill` was stabilized in Rust 1.50, but Debian Bullseye ships with 1.48) 691 | for v in config.$target_field().iter_mut() { 692 | *v = 0; 693 | } 694 | 695 | // Write the string bytes. 696 | let _ = config.$target_field()[..].as_mut().write_all(value_bytes); 697 | } 698 | }; 699 | ( 700 | class: wide_str, 701 | value: $value:ident, 702 | source_field: $source_field:ident, 703 | target_field: $target_field:ident, 704 | ) => { 705 | // Use `len - 1` to account for the required NULL terminator. 706 | if $value.encode_utf16().count() > config.$target_field().len() - 1 { 707 | error_too_long!($target_field, $value); 708 | } else { 709 | patch_msg!($target_field, WideCharFormat, $value); 710 | 711 | // Zero out the field first. 712 | // (`fill` was stabilized in Rust 1.50, but Debian Bullseye ships with 1.48) 713 | for v in config.$target_field().iter_mut() { 714 | *v = 0; 715 | } 716 | 717 | // Write the string bytes. 718 | for (v, ch) in config.$target_field()[..] 719 | .iter_mut() 720 | .zip($value.encode_utf16().chain(Some(0))) 721 | { 722 | *v = ch; 723 | } 724 | } 725 | }; 726 | } 727 | macro_rules! handle_overrides { 728 | ( 729 | $($class:ident: [ 730 | $($source_field:ident $(=> $target_field:ident)?),*$(,)? 731 | ]),*$(,)? 732 | ) => { 733 | $( 734 | $( 735 | handle_override! { 736 | class: $class, 737 | source_field: $source_field, 738 | $(target_field: $target_field,)? 739 | } 740 | )* 741 | )* 742 | }; 743 | } 744 | 745 | // While the following could be done with fewer branches, I wanted the log statements to be in 746 | // field order. 747 | 748 | handle_overrides! { 749 | copy: [ 750 | gpu_type => vgpu_type, 751 | ], 752 | str: [ 753 | card_name => vgpu_name, 754 | vgpu_type => vgpu_class, 755 | features => license, 756 | ], 757 | copy: [ 758 | max_instances => max_instance, 759 | num_displays => num_heads, 760 | display_width => max_resolution_x, 761 | display_height => max_resolution_y, 762 | max_pixels, 763 | frl_config, 764 | ], 765 | bool: [ 766 | cuda_enabled, 767 | ecc_supported, 768 | ], 769 | copy: [ 770 | mig_instance_size, 771 | ], 772 | bool: [ 773 | multi_vgpu_supported, 774 | ], 775 | copy: [ 776 | pci_id => vdev_id, 777 | pci_device_id => pdev_id, 778 | framebuffer => fb_length, 779 | mappable_video_size, 780 | framebuffer_reservation => fb_reservation, 781 | encoder_capacity, 782 | bar1_length, 783 | ], 784 | bool: [ 785 | frl_enabled => frl_enable, 786 | ], 787 | str: [ 788 | adapter_name, 789 | ], 790 | wide_str: [ 791 | adapter_name => adapter_name_unicode, 792 | ], 793 | str: [ 794 | short_gpu_name => short_gpu_name_string, 795 | license_type => licensed_product_name, 796 | ], 797 | } 798 | 799 | true 800 | } 801 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | use std::cell::RefCell; 4 | use std::fmt; 5 | use std::io::Write; 6 | 7 | use libc::{c_int, LOG_ERR}; 8 | 9 | pub(crate) fn syslog(level: c_int, args: fmt::Arguments<'_>) { 10 | thread_local!(static MSG_BUFFER: RefCell> = RefCell::new(Vec::with_capacity(512))); 11 | 12 | MSG_BUFFER.with(|msg_buffer| { 13 | let mut msg_buffer = msg_buffer.borrow_mut(); 14 | 15 | msg_buffer.clear(); 16 | 17 | match msg_buffer.write_fmt(args) { 18 | Ok(_) => { 19 | msg_buffer.push(b'\0'); 20 | 21 | unsafe { libc::syslog(level, b"%s\0".as_ptr() as _, msg_buffer.as_ptr()) }; 22 | } 23 | Err(e) => { 24 | msg_buffer.clear(); 25 | 26 | let _ = msg_buffer.write_all(b"Failed to format message: "); 27 | 28 | if write!(&mut msg_buffer, "{}", e).is_err() { 29 | msg_buffer.clear(); 30 | 31 | let _ = msg_buffer.write_all(b"Failed to format message and error message"); 32 | } 33 | 34 | msg_buffer.push(b'\0'); 35 | 36 | unsafe { libc::syslog(LOG_ERR, b"%s\0".as_ptr() as _, msg_buffer.as_ptr()) } 37 | } 38 | } 39 | }); 40 | } 41 | 42 | macro_rules! error { 43 | ($($arg:tt)+) => { 44 | $crate::log::syslog(::libc::LOG_ERR, format_args!($($arg)+)) 45 | }; 46 | } 47 | macro_rules! info { 48 | ($($arg:tt)+) => { 49 | $crate::log::syslog(::libc::LOG_NOTICE, format_args!($($arg)+)) 50 | }; 51 | } 52 | 53 | pub(crate) use error; 54 | pub(crate) use info; 55 | -------------------------------------------------------------------------------- /src/nvidia/ctrl0000vgpu.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/758b4ee8189c5198504cb1c3c5bc29027a9118a3/src/common/sdk/nvidia/inc/ctrl/ctrl0000/ctrl0000vgpu.h 2 | use std::fmt; 3 | 4 | use crate::format::{CStrFormat, HexFormat}; 5 | use crate::uuid::Uuid; 6 | 7 | pub const NV0000_CTRL_CMD_VGPU_GET_START_DATA: u32 = 0xc01; 8 | 9 | /// See `NV0000_CTRL_VGPU_GET_START_DATA_PARAMS` 10 | #[repr(C)] 11 | pub struct Nv0000CtrlVgpuGetStartDataParams { 12 | // [u8; VM_UUID_SIZE] 13 | pub mdev_uuid: Uuid, 14 | pub config_params: [u8; 1024], 15 | pub qemu_pid: u32, 16 | pub gpu_pci_id: u32, 17 | pub vgpu_id: u16, 18 | pub gpu_pci_bdf: u32, 19 | } 20 | 21 | impl fmt::Debug for Nv0000CtrlVgpuGetStartDataParams { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | f.debug_struct("Nv0000CtrlVgpuGetStartDataParams") 24 | .field("mdev_uuid", &format_args!("{{{}}}", self.mdev_uuid)) 25 | .field("config_params", &CStrFormat(&self.config_params)) 26 | .field("qemu_pid", &self.qemu_pid) 27 | .field("gpu_pci_id", &HexFormat(&self.gpu_pci_id)) 28 | .field("vgpu_id", &self.vgpu_id) 29 | .field("gpu_pci_bdf", &self.gpu_pci_bdf) 30 | .finish() 31 | } 32 | } 33 | 34 | pub const NV0000_CTRL_CMD_VGPU_CREATE_DEVICE: u32 = 0xc02; 35 | 36 | #[repr(C)] 37 | pub struct Nv0000CtrlVgpuCreateDeviceParams { 38 | pub vgpu_name: Uuid, 39 | pub gpu_pci_id: u32, 40 | pub gpu_pci_bdf: u32, 41 | pub vgpu_type_id: u32, 42 | pub vgpu_id: u16, 43 | // R570 adds additional fields, leave them out for now for backwards compat with 16.x and 17.x 44 | // https://github.com/NVIDIA/open-gpu-kernel-modules/blob/570/src/common/sdk/nvidia/inc/ctrl/ctrl0000/ctrl0000vgpu.h#L94-L95 45 | // 46 | // pub gpuInstanceId: u32, 47 | // pub placementId: u32, 48 | } 49 | 50 | impl fmt::Debug for Nv0000CtrlVgpuCreateDeviceParams { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | f.debug_struct("Nv0000CtrlVgpuCreateDeviceParams") 53 | .field("vgpu_name", &format_args!("{{{}}}", self.vgpu_name)) 54 | .field("gpu_pci_id", &HexFormat(&self.gpu_pci_id)) 55 | .field("gpu_pci_bdf", &self.gpu_pci_bdf) 56 | .field("vgpu_type_id", &self.vgpu_type_id) 57 | .field("vgpu_id", &self.vgpu_id) 58 | .finish() 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod test { 64 | use std::mem; 65 | 66 | use super::{Nv0000CtrlVgpuCreateDeviceParams, Nv0000CtrlVgpuGetStartDataParams}; 67 | 68 | #[test] 69 | fn verify_sizes() { 70 | assert_eq!(mem::size_of::(), 0x420); 71 | assert_eq!(mem::size_of::(), 0x20); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/nvidia/ctrl0080gpu.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/758b4ee8189c5198504cb1c3c5bc29027a9118a3/src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080gpu.h 2 | 3 | pub const NV0080_CTRL_CMD_GPU_GET_VIRTUALIZATION_MODE: u32 = 0x800289; 4 | 5 | /// `nvidia-vgpu-mgr` expects this value for a vGPU capable GPU. 6 | pub const NV0080_CTRL_GPU_VIRTUALIZATION_MODE_HOST: u32 = 0x00000003; 7 | 8 | /// See `NV0080_CTRL_GPU_GET_VIRTUALIZATION_MODE_PARAMS` 9 | #[repr(C)] 10 | pub struct Nv0080CtrlGpuGetVirtualizationModeParams { 11 | pub virtualization_mode: u32, 12 | // R570 adds additional fields, leave them out for now for backwards compat with 16.x and 17.x 13 | // https://github.com/NVIDIA/open-gpu-kernel-modules/blob/570/src/common/sdk/nvidia/inc/ctrl/ctrl0080/ctrl0080gpu.h#L313 14 | // 15 | // pub isGridBuild: bool, 16 | } 17 | -------------------------------------------------------------------------------- /src/nvidia/ctrl2080bus.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/5f40a5aee5ef9c92085836bf5b5a9056174f07f1/src/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080bus.h 2 | 3 | pub const NV2080_CTRL_CMD_BUS_GET_PCI_INFO: u32 = 0x20801801; 4 | 5 | /// See `NV2080_CTRL_BUS_GET_PCI_INFO_PARAMS` 6 | //#[derive(Debug)] 7 | #[repr(C)] 8 | pub struct Nv2080CtrlBusGetPciInfoParams { 9 | pub pci_device_id: u32, 10 | pub pci_sub_system_id: u32, 11 | pub pci_revision_id: u32, 12 | pub pci_ext_device_id: u32, 13 | } 14 | -------------------------------------------------------------------------------- /src/nvidia/ctrl2080gpu.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/5f40a5aee5ef9c92085836bf5b5a9056174f07f1/src/common/sdk/nvidia/inc/ctrl/ctrl2080/ctrl2080gpu.h 2 | 3 | pub const NV_GRID_LICENSE_INFO_MAX_LENGTH: usize = 128; 4 | 5 | pub const NV2080_GPU_MAX_NAME_STRING_LENGTH: usize = 0x0000040; 6 | 7 | pub const NV2080_CTRL_CMD_GPU_GET_INFOROM_OBJECT_VERSION: u32 = 0x2080014b; 8 | -------------------------------------------------------------------------------- /src/nvidia/ctrl9096.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/90eb10774f1c53d2364eacf9fa8f0c7a92b1b824/src/common/sdk/nvidia/inc/ctrl/ctrl9096.h 2 | 3 | pub const NV9096_CTRL_CMD_GET_ZBC_CLEAR_TABLE: u32 = 0x90960103; 4 | -------------------------------------------------------------------------------- /src/nvidia/ctrla081.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/758b4ee8189c5198504cb1c3c5bc29027a9118a3/src/common/sdk/nvidia/inc/ctrl/ctrla081.h 2 | use std::fmt; 3 | 4 | use super::ctrl2080gpu::{NV2080_GPU_MAX_NAME_STRING_LENGTH, NV_GRID_LICENSE_INFO_MAX_LENGTH}; 5 | use crate::format::{CStrFormat, HexFormat, HexFormatSlice, WideCharFormat}; 6 | use crate::utils::AlignedU64; 7 | 8 | pub const NVA081_VGPU_STRING_BUFFER_SIZE: usize = 32; 9 | pub const NVA081_VGPU_SIGNATURE_SIZE: usize = 128; 10 | 11 | pub const NVA081_EXTRA_PARAMETERS_SIZE: usize = 1024; 12 | 13 | // pub const NVA081_MAX_VGPU_PER_PGPU: usize = 32; 14 | 15 | /// See `NVA081_CTRL_VGPU_CONFIG_INFO` 16 | // Set `align(8)` for `NVA081_CTRL_VGPU_CONFIG_GET_VGPU_TYPE_INFO_PARAMS` 17 | #[repr(C, align(8))] 18 | pub struct NvA081CtrlVgpuInfo { 19 | pub vgpu_type: u32, 20 | pub vgpu_name: [u8; NVA081_VGPU_STRING_BUFFER_SIZE], 21 | pub vgpu_class: [u8; NVA081_VGPU_STRING_BUFFER_SIZE], 22 | pub vgpu_signature: [u8; NVA081_VGPU_SIGNATURE_SIZE], 23 | pub license: [u8; NV_GRID_LICENSE_INFO_MAX_LENGTH], 24 | pub max_instance: u32, 25 | pub num_heads: u32, 26 | pub max_resolution_x: u32, 27 | pub max_resolution_y: u32, 28 | pub max_pixels: u32, 29 | pub frl_config: u32, 30 | pub cuda_enabled: u32, 31 | pub ecc_supported: u32, 32 | pub gpu_instance_size: u32, 33 | pub multi_vgpu_supported: u32, 34 | pub vdev_id: AlignedU64, 35 | pub pdev_id: AlignedU64, 36 | pub profile_size: AlignedU64, 37 | pub fb_length: AlignedU64, 38 | pub gsp_heap_size: AlignedU64, 39 | pub fb_reservation: AlignedU64, 40 | pub mappable_video_size: AlignedU64, 41 | pub encoder_capacity: u32, 42 | pub bar1_length: AlignedU64, 43 | pub frl_enable: u32, 44 | pub adapter_name: [u8; NV2080_GPU_MAX_NAME_STRING_LENGTH], 45 | pub adapter_name_unicode: [u16; NV2080_GPU_MAX_NAME_STRING_LENGTH], 46 | pub short_gpu_name_string: [u8; NV2080_GPU_MAX_NAME_STRING_LENGTH], 47 | pub licensed_product_name: [u8; NV_GRID_LICENSE_INFO_MAX_LENGTH], 48 | pub vgpu_extra_params: [u32; NVA081_EXTRA_PARAMETERS_SIZE], 49 | pub ftrace_enable: u32, 50 | pub gpu_direct_supported: u32, 51 | pub nvlink_p2p_supported: u32, 52 | pub multi_vgpu_exclusive: u32, 53 | pub exclusive_type: u32, 54 | pub exclusive_size: u32, 55 | pub gpu_instance_profile_id: u32, 56 | // R550 adds additional fields, leave them out for now for backwards compat with 16.x 57 | // https://github.com/NVIDIA/open-gpu-kernel-modules/blob/550/src/common/sdk/nvidia/inc/ctrl/ctrla081.h#L126-L128 58 | // R570 rename these fields 59 | // https://github.com/NVIDIA/open-gpu-kernel-modules/blob/570/src/common/sdk/nvidia/inc/ctrl/ctrla081.h#L126-L128 60 | // 61 | // pub placement_size: u32, 62 | // pub homogeneousPlacementCount: u32, // pub placement_count: u32, 63 | // pub homogeneousPlacementIds: [u32; NVA081_MAX_VGPU_PER_PGPU], // pub placement_ids: [u32; NVA081_MAX_VGPU_PER_PGPU], 64 | // 65 | // R570 adds additional fields, leave them out for now for backwards compat with 16.x and 17.x 66 | // https://github.com/NVIDIA/open-gpu-kernel-modules/blob/570/src/common/sdk/nvidia/inc/ctrl/ctrla081.h#L129-L130 67 | // 68 | // pub heterogeneousPlacementCount: u32, 69 | // pub heterogeneousPlacementIds: [u32; NVA081_MAX_VGPU_PER_PGPU], 70 | } 71 | 72 | pub const NVA081_CTRL_CMD_VGPU_CONFIG_GET_VGPU_TYPE_INFO: u32 = 0xa0810103; 73 | 74 | /// This RM control command is used starting in vGPU version 15.0 (525.60.12). 75 | /// 76 | /// See `NVA081_CTRL_VGPU_CONFIG_GET_VGPU_TYPE_INFO_PARAMS` 77 | #[repr(C)] 78 | pub struct NvA081CtrlVgpuConfigGetVgpuTypeInfoParams { 79 | pub vgpu_type: u32, 80 | pub vgpu_type_info: NvA081CtrlVgpuInfo, 81 | } 82 | 83 | pub const NVA081_CTRL_CMD_VGPU_CONFIG_GET_MIGRATION_CAP: u32 = 0xa0810112; 84 | 85 | /// See `NVA081_CTRL_CMD_VGPU_CONFIG_GET_MIGRATION_CAP_PARAMS` 86 | #[repr(C)] 87 | pub struct NvA081CtrlCmdVgpuConfigGetMigrationCapParams { 88 | pub migration_cap: u8, 89 | } 90 | 91 | impl fmt::Debug for NvA081CtrlVgpuInfo { 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | let vgpu_signature = if self.vgpu_signature[..].iter().any(|&x| x != 0) { 94 | &self.vgpu_signature[..] 95 | } else { 96 | &[] 97 | }; 98 | let vgpu_extra_params = if self.vgpu_extra_params[..].iter().any(|&x| x != 0) { 99 | &self.vgpu_extra_params[..] 100 | } else { 101 | &[] 102 | }; 103 | 104 | f.debug_struct("NvA081CtrlVgpuInfo") 105 | .field("vgpu_type", &self.vgpu_type) 106 | .field("vgpu_name", &CStrFormat(&self.vgpu_name)) 107 | .field("vgpu_class", &CStrFormat(&self.vgpu_class)) 108 | .field("vgpu_signature", &HexFormatSlice(vgpu_signature)) 109 | .field("license", &CStrFormat(&self.license)) 110 | .field("max_instance", &self.max_instance) 111 | .field("num_heads", &self.num_heads) 112 | .field("max_resolution_x", &self.max_resolution_x) 113 | .field("max_resolution_y", &self.max_resolution_y) 114 | .field("max_pixels", &self.max_pixels) 115 | .field("frl_config", &self.frl_config) 116 | .field("cuda_enabled", &self.cuda_enabled) 117 | .field("ecc_supported", &self.ecc_supported) 118 | .field("gpu_instance_size", &self.gpu_instance_size) 119 | .field("multi_vgpu_supported", &self.multi_vgpu_supported) 120 | .field("vdev_id", &HexFormat(self.vdev_id)) 121 | .field("pdev_id", &HexFormat(self.pdev_id)) 122 | .field("profile_size", &HexFormat(self.profile_size)) 123 | .field("fb_length", &HexFormat(self.fb_length)) 124 | .field("gsp_heap_size", &HexFormat(self.gsp_heap_size)) 125 | .field("fb_reservation", &HexFormat(self.fb_reservation)) 126 | .field("mappable_video_size", &HexFormat(self.mappable_video_size)) 127 | .field("encoder_capacity", &HexFormat(self.encoder_capacity)) 128 | .field("bar1_length", &HexFormat(self.bar1_length)) 129 | .field("frl_enable", &self.frl_enable) 130 | .field("adapter_name", &CStrFormat(&self.adapter_name)) 131 | .field( 132 | "adapter_name_unicode", 133 | &WideCharFormat(&self.adapter_name_unicode), 134 | ) 135 | .field( 136 | "short_gpu_name_string", 137 | &CStrFormat(&self.short_gpu_name_string), 138 | ) 139 | .field( 140 | "licensed_product_name", 141 | &CStrFormat(&self.licensed_product_name), 142 | ) 143 | .field("vgpu_extra_params", &HexFormatSlice(vgpu_extra_params)) 144 | .field("ftrace_enable", &self.ftrace_enable) 145 | .field("gpu_direct_supported", &self.gpu_direct_supported) 146 | .field("nvlink_p2p_supported", &self.nvlink_p2p_supported) 147 | .field("multi_vgpu_exclusive", &self.multi_vgpu_exclusive) 148 | .field("exclusive_type", &self.exclusive_type) 149 | .field("exclusive_size", &self.exclusive_size) 150 | .field("gpu_instance_profile_id", &self.gpu_instance_profile_id) 151 | .finish() 152 | } 153 | } 154 | 155 | impl fmt::Debug for NvA081CtrlVgpuConfigGetVgpuTypeInfoParams { 156 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 157 | f.debug_struct("NvA081CtrlVgpuConfigGetVgpuTypeInfoParams") 158 | .field("vgpu_type", &self.vgpu_type) 159 | .field("vgpu_type_info", &self.vgpu_type_info) 160 | .finish() 161 | } 162 | } 163 | 164 | #[cfg(test)] 165 | mod test { 166 | use std::mem; 167 | 168 | use super::{NvA081CtrlVgpuConfigGetVgpuTypeInfoParams, NvA081CtrlVgpuInfo}; 169 | 170 | #[test] 171 | fn verify_sizes() { 172 | assert_eq!(mem::size_of::(), 0x1358); 173 | assert_eq!( 174 | mem::size_of::(), 175 | 0x1360 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/nvidia/ctrla082.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::format::{CStrFormat, HexFormat, HexFormatSlice, WideCharFormat}; 4 | 5 | /// Inferred based on `NVA082_CTRL_CMD_HOST_VGPU_DEVICE_GET_VGPU_TYPE_INFO_PARAMS` 6 | pub const NVA082_CTRL_CMD_HOST_VGPU_DEVICE_GET_VGPU_TYPE_INFO: u32 = 0xa0820102; 7 | 8 | /// Pulled from a comment in [`NVA081_CTRL_VGPU_INFO`](https://github.com/NVIDIA/open-gpu-kernel-modules/blob/758b4ee8189c5198504cb1c3c5bc29027a9118a3/src/common/sdk/nvidia/inc/ctrl/ctrla081.h#L82) 9 | #[repr(C)] 10 | pub struct NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams { 11 | pub vgpu_type: u32, 12 | pub vgpu_name: [u8; 32], 13 | pub vgpu_class: [u8; 32], 14 | pub vgpu_signature: [u8; 128], 15 | pub license: [u8; 128], 16 | pub max_instance: u32, 17 | pub num_heads: u32, 18 | pub max_resolution_x: u32, 19 | pub max_resolution_y: u32, 20 | pub max_pixels: u32, 21 | pub frl_config: u32, 22 | pub cuda_enabled: u32, 23 | pub ecc_supported: u32, 24 | // This field might not exist anymore and instead the space became padding as 25 | // `NVA081_CTRL_VGPU_INFO` forces the alignment of `vdevId` to `8`. 26 | pub mig_instance_size: u32, 27 | pub multi_vgpu_supported: u32, 28 | pub vdev_id: u64, 29 | pub pdev_id: u64, 30 | pub fb_length: u64, 31 | pub mappable_video_size: u64, 32 | pub fb_reservation: u64, 33 | pub encoder_capacity: u32, 34 | pub bar1_length: u64, 35 | pub frl_enable: u32, 36 | pub adapter_name: [u8; 64], 37 | pub adapter_name_unicode: [u16; 64], 38 | pub short_gpu_name_string: [u8; 64], 39 | pub licensed_product_name: [u8; 128], 40 | pub vgpu_extra_params: [u8; 1024], 41 | unknown_end: [u8; 8], 42 | } 43 | 44 | impl fmt::Debug for NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | let vgpu_signature = if self.vgpu_signature[..].iter().any(|&x| x != 0) { 47 | &self.vgpu_signature[..] 48 | } else { 49 | &[] 50 | }; 51 | let vgpu_extra_params = if self.vgpu_extra_params[..].iter().any(|&x| x != 0) { 52 | &self.vgpu_extra_params[..] 53 | } else { 54 | &[] 55 | }; 56 | 57 | f.debug_struct("NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams") 58 | .field("vgpu_type", &self.vgpu_type) 59 | .field("vgpu_name", &CStrFormat(&self.vgpu_name)) 60 | .field("vgpu_class", &CStrFormat(&self.vgpu_class)) 61 | .field("vgpu_signature", &HexFormatSlice(vgpu_signature)) 62 | .field("license", &CStrFormat(&self.license)) 63 | .field("max_instance", &self.max_instance) 64 | .field("num_heads", &self.num_heads) 65 | .field("max_resolution_x", &self.max_resolution_x) 66 | .field("max_resolution_y", &self.max_resolution_y) 67 | .field("max_pixels", &self.max_pixels) 68 | .field("frl_config", &self.frl_config) 69 | .field("cuda_enabled", &self.cuda_enabled) 70 | .field("ecc_supported", &self.ecc_supported) 71 | .field("mig_instance_size", &self.mig_instance_size) 72 | .field("multi_vgpu_supported", &self.multi_vgpu_supported) 73 | .field("vdev_id", &HexFormat(self.vdev_id)) 74 | .field("pdev_id", &HexFormat(self.pdev_id)) 75 | .field("fb_length", &HexFormat(self.fb_length)) 76 | .field("mappable_video_size", &HexFormat(self.mappable_video_size)) 77 | .field("fb_reservation", &HexFormat(self.fb_reservation)) 78 | .field("encoder_capacity", &HexFormat(self.encoder_capacity)) 79 | .field("bar1_length", &HexFormat(self.bar1_length)) 80 | .field("frl_enable", &self.frl_enable) 81 | .field("adapter_name", &CStrFormat(&self.adapter_name)) 82 | .field( 83 | "adapter_name_unicode", 84 | &WideCharFormat(&self.adapter_name_unicode), 85 | ) 86 | .field( 87 | "short_gpu_name_string", 88 | &CStrFormat(&self.short_gpu_name_string), 89 | ) 90 | .field( 91 | "licensed_product_name", 92 | &CStrFormat(&self.licensed_product_name), 93 | ) 94 | .field("vgpu_extra_params", &HexFormatSlice(vgpu_extra_params)) 95 | .finish() 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod test { 101 | use std::mem; 102 | 103 | use super::NvA082CtrlCmdHostVgpuDeviceGetVgpuTypeInfoParams; 104 | 105 | #[test] 106 | fn verify_sizes() { 107 | assert_eq!( 108 | mem::size_of::(), 109 | 0x738 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/nvidia/error.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | ///! When ioctl returns success (retval >= 0) but sets the status value of the arg structure to 3 4 | ///! then `nvidia-vgpud` will sleep for a bit (first 0.1s then 1s then 10s) then issue the same 5 | ///! ioctl call again until the status differs from 3. It will attempt this for up to 24h before 6 | ///! giving up. 7 | ///! 8 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/5f40a5aee5ef9c92085836bf5b5a9056174f07f1/kernel-open/common/inc/nvstatuscodes.h 9 | 10 | pub const NV_OK: u32 = 0x00000000; 11 | pub const NV_ERR_GENERIC: u32 = 0x0000ffff; 12 | pub const NV_ERR_BROKEN_FB: u32 = 0x00000001; 13 | pub const NV_ERR_BUFFER_TOO_SMALL: u32 = 0x00000002; 14 | pub const NV_ERR_BUSY_RETRY: u32 = 0x00000003; 15 | pub const NV_ERR_CALLBACK_NOT_SCHEDULED: u32 = 0x00000004; 16 | pub const NV_ERR_CARD_NOT_PRESENT: u32 = 0x00000005; 17 | pub const NV_ERR_CYCLE_DETECTED: u32 = 0x00000006; 18 | pub const NV_ERR_DMA_IN_USE: u32 = 0x00000007; 19 | pub const NV_ERR_DMA_MEM_NOT_LOCKED: u32 = 0x00000008; 20 | pub const NV_ERR_DMA_MEM_NOT_UNLOCKED: u32 = 0x00000009; 21 | pub const NV_ERR_DUAL_LINK_INUSE: u32 = 0x0000000a; 22 | pub const NV_ERR_ECC_ERROR: u32 = 0x0000000b; 23 | pub const NV_ERR_FIFO_BAD_ACCESS: u32 = 0x0000000c; 24 | pub const NV_ERR_FREQ_NOT_SUPPORTED: u32 = 0x0000000d; 25 | pub const NV_ERR_GPU_DMA_NOT_INITIALIZED: u32 = 0x0000000e; 26 | pub const NV_ERR_GPU_IS_LOST: u32 = 0x0000000f; 27 | pub const NV_ERR_GPU_IN_FULLCHIP_RESET: u32 = 0x00000010; 28 | pub const NV_ERR_GPU_NOT_FULL_POWER: u32 = 0x00000011; 29 | pub const NV_ERR_GPU_UUID_NOT_FOUND: u32 = 0x00000012; 30 | pub const NV_ERR_HOT_SWITCH: u32 = 0x00000013; 31 | pub const NV_ERR_I2C_ERROR: u32 = 0x00000014; 32 | pub const NV_ERR_I2C_SPEED_TOO_HIGH: u32 = 0x00000015; 33 | pub const NV_ERR_ILLEGAL_ACTION: u32 = 0x00000016; 34 | pub const NV_ERR_IN_USE: u32 = 0x00000017; 35 | pub const NV_ERR_INFLATE_COMPRESSED_DATA_FAILED: u32 = 0x00000018; 36 | pub const NV_ERR_INSERT_DUPLICATE_NAME: u32 = 0x00000019; 37 | pub const NV_ERR_INSUFFICIENT_RESOURCES: u32 = 0x0000001a; 38 | pub const NV_ERR_INSUFFICIENT_PERMISSIONS: u32 = 0x0000001b; 39 | pub const NV_ERR_INSUFFICIENT_POWER: u32 = 0x0000001c; 40 | pub const NV_ERR_INVALID_ACCESS_TYPE: u32 = 0x0000001d; 41 | pub const NV_ERR_INVALID_ADDRESS: u32 = 0x0000001e; 42 | pub const NV_ERR_INVALID_ARGUMENT: u32 = 0x0000001f; 43 | pub const NV_ERR_INVALID_BASE: u32 = 0x00000020; 44 | pub const NV_ERR_INVALID_CHANNEL: u32 = 0x00000021; 45 | pub const NV_ERR_INVALID_CLASS: u32 = 0x00000022; 46 | pub const NV_ERR_INVALID_CLIENT: u32 = 0x00000023; 47 | pub const NV_ERR_INVALID_COMMAND: u32 = 0x00000024; 48 | pub const NV_ERR_INVALID_DATA: u32 = 0x00000025; 49 | pub const NV_ERR_INVALID_DEVICE: u32 = 0x00000026; 50 | pub const NV_ERR_INVALID_DMA_SPECIFIER: u32 = 0x00000027; 51 | pub const NV_ERR_INVALID_EVENT: u32 = 0x00000028; 52 | pub const NV_ERR_INVALID_FLAGS: u32 = 0x00000029; 53 | pub const NV_ERR_INVALID_FUNCTION: u32 = 0x0000002a; 54 | pub const NV_ERR_INVALID_HEAP: u32 = 0x0000002b; 55 | pub const NV_ERR_INVALID_INDEX: u32 = 0x0000002c; 56 | pub const NV_ERR_INVALID_IRQ_LEVEL: u32 = 0x0000002d; 57 | pub const NV_ERR_INVALID_LIMIT: u32 = 0x0000002e; 58 | pub const NV_ERR_INVALID_LOCK_STATE: u32 = 0x0000002f; 59 | pub const NV_ERR_INVALID_METHOD: u32 = 0x00000030; 60 | pub const NV_ERR_INVALID_OBJECT: u32 = 0x00000031; 61 | pub const NV_ERR_INVALID_OBJECT_BUFFER: u32 = 0x00000032; 62 | pub const NV_ERR_INVALID_OBJECT_HANDLE: u32 = 0x00000033; 63 | pub const NV_ERR_INVALID_OBJECT_NEW: u32 = 0x00000034; 64 | pub const NV_ERR_INVALID_OBJECT_OLD: u32 = 0x00000035; 65 | pub const NV_ERR_INVALID_OBJECT_PARENT: u32 = 0x00000036; 66 | pub const NV_ERR_INVALID_OFFSET: u32 = 0x00000037; 67 | pub const NV_ERR_INVALID_OPERATION: u32 = 0x00000038; 68 | pub const NV_ERR_INVALID_OWNER: u32 = 0x00000039; 69 | pub const NV_ERR_INVALID_PARAM_STRUCT: u32 = 0x0000003a; 70 | pub const NV_ERR_INVALID_PARAMETER: u32 = 0x0000003b; 71 | pub const NV_ERR_INVALID_PATH: u32 = 0x0000003c; 72 | pub const NV_ERR_INVALID_POINTER: u32 = 0x0000003d; 73 | pub const NV_ERR_INVALID_REGISTRY_KEY: u32 = 0x0000003e; 74 | pub const NV_ERR_INVALID_REQUEST: u32 = 0x0000003f; 75 | pub const NV_ERR_INVALID_STATE: u32 = 0x00000040; 76 | pub const NV_ERR_INVALID_STRING_LENGTH: u32 = 0x00000041; 77 | pub const NV_ERR_INVALID_READ: u32 = 0x00000042; 78 | pub const NV_ERR_INVALID_WRITE: u32 = 0x00000043; 79 | pub const NV_ERR_INVALID_XLATE: u32 = 0x00000044; 80 | pub const NV_ERR_IRQ_NOT_FIRING: u32 = 0x00000045; 81 | pub const NV_ERR_IRQ_EDGE_TRIGGERED: u32 = 0x00000046; 82 | pub const NV_ERR_MEMORY_TRAINING_FAILED: u32 = 0x00000047; 83 | pub const NV_ERR_MISMATCHED_SLAVE: u32 = 0x00000048; 84 | pub const NV_ERR_MISMATCHED_TARGET: u32 = 0x00000049; 85 | pub const NV_ERR_MISSING_TABLE_ENTRY: u32 = 0x0000004a; 86 | pub const NV_ERR_MODULE_LOAD_FAILED: u32 = 0x0000004b; 87 | pub const NV_ERR_MORE_DATA_AVAILABLE: u32 = 0x0000004c; 88 | pub const NV_ERR_MORE_PROCESSING_REQUIRED: u32 = 0x0000004d; 89 | pub const NV_ERR_MULTIPLE_MEMORY_TYPES: u32 = 0x0000004e; 90 | pub const NV_ERR_NO_FREE_FIFOS: u32 = 0x0000004f; 91 | pub const NV_ERR_NO_INTR_PENDING: u32 = 0x00000050; 92 | pub const NV_ERR_NO_MEMORY: u32 = 0x00000051; 93 | pub const NV_ERR_NO_SUCH_DOMAIN: u32 = 0x00000052; 94 | pub const NV_ERR_NO_VALID_PATH: u32 = 0x00000053; 95 | pub const NV_ERR_NOT_COMPATIBLE: u32 = 0x00000054; 96 | pub const NV_ERR_NOT_READY: u32 = 0x00000055; 97 | pub const NV_ERR_NOT_SUPPORTED: u32 = 0x00000056; 98 | pub const NV_ERR_OBJECT_NOT_FOUND: u32 = 0x00000057; 99 | pub const NV_ERR_OBJECT_TYPE_MISMATCH: u32 = 0x00000058; 100 | pub const NV_ERR_OPERATING_SYSTEM: u32 = 0x00000059; 101 | pub const NV_ERR_OTHER_DEVICE_FOUND: u32 = 0x0000005a; 102 | pub const NV_ERR_OUT_OF_RANGE: u32 = 0x0000005b; 103 | pub const NV_ERR_OVERLAPPING_UVM_COMMIT: u32 = 0x0000005c; 104 | pub const NV_ERR_PAGE_TABLE_NOT_AVAIL: u32 = 0x0000005d; 105 | pub const NV_ERR_PID_NOT_FOUND: u32 = 0x0000005e; 106 | pub const NV_ERR_PROTECTION_FAULT: u32 = 0x0000005f; 107 | pub const NV_ERR_RC_ERROR: u32 = 0x00000060; 108 | pub const NV_ERR_REJECTED_VBIOS: u32 = 0x00000061; 109 | pub const NV_ERR_RESET_REQUIRED: u32 = 0x00000062; 110 | pub const NV_ERR_STATE_IN_USE: u32 = 0x00000063; 111 | pub const NV_ERR_SIGNAL_PENDING: u32 = 0x00000064; 112 | pub const NV_ERR_TIMEOUT: u32 = 0x00000065; 113 | pub const NV_ERR_TIMEOUT_RETRY: u32 = 0x00000066; 114 | pub const NV_ERR_TOO_MANY_PRIMARIES: u32 = 0x00000067; 115 | pub const NV_ERR_UVM_ADDRESS_IN_USE: u32 = 0x00000068; 116 | pub const NV_ERR_MAX_SESSION_LIMIT_REACHED: u32 = 0x00000069; 117 | pub const NV_ERR_LIB_RM_VERSION_MISMATCH: u32 = 0x0000006a; 118 | pub const NV_ERR_PRIV_SEC_VIOLATION: u32 = 0x0000006b; 119 | pub const NV_ERR_GPU_IN_DEBUG_MODE: u32 = 0x0000006c; 120 | pub const NV_ERR_FEATURE_NOT_ENABLED: u32 = 0x0000006d; 121 | pub const NV_ERR_RESOURCE_LOST: u32 = 0x0000006e; 122 | pub const NV_ERR_PMU_NOT_READY: u32 = 0x0000006f; 123 | pub const NV_ERR_FLCN_ERROR: u32 = 0x00000070; 124 | pub const NV_ERR_FATAL_ERROR: u32 = 0x00000071; 125 | pub const NV_ERR_MEMORY_ERROR: u32 = 0x00000072; 126 | pub const NV_ERR_INVALID_LICENSE: u32 = 0x00000073; 127 | pub const NV_ERR_NVLINK_INIT_ERROR: u32 = 0x00000074; 128 | pub const NV_ERR_NVLINK_MINION_ERROR: u32 = 0x00000075; 129 | pub const NV_ERR_NVLINK_CLOCK_ERROR: u32 = 0x00000076; 130 | pub const NV_ERR_NVLINK_TRAINING_ERROR: u32 = 0x00000077; 131 | pub const NV_ERR_NVLINK_CONFIGURATION_ERROR: u32 = 0x00000078; 132 | pub const NV_ERR_RISCV_ERROR: u32 = 0x00000079; 133 | pub const NV_ERR_FABRIC_MANAGER_NOT_PRESENT: u32 = 0x0000007a; 134 | pub const NV_WARN_HOT_SWITCH: u32 = 0x00010001; 135 | pub const NV_WARN_INCORRECT_PERFMON_DATA: u32 = 0x00010002; 136 | pub const NV_WARN_MISMATCHED_SLAVE: u32 = 0x00010003; 137 | pub const NV_WARN_MISMATCHED_TARGET: u32 = 0x00010004; 138 | pub const NV_WARN_MORE_PROCESSING_REQUIRED: u32 = 0x00010005; 139 | pub const NV_WARN_NOTHING_TO_DO: u32 = 0x00010006; 140 | pub const NV_WARN_NULL_OBJECT: u32 = 0x00010007; 141 | pub const NV_WARN_OUT_OF_RANGE: u32 = 0x00010008; 142 | -------------------------------------------------------------------------------- /src/nvidia/ioctl.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::c_ulong; 2 | 3 | /// Pulled from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/d8f3bcff924776518f1e63286537c3cf365289ac/kernel-open/common/inc/nv-ioctl-numbers.h 4 | pub const NV_IOCTL_MAGIC: c_ulong = b'F' as _; 5 | -------------------------------------------------------------------------------- /src/nvidia/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ctrl0000vgpu; 2 | pub mod ctrl0080gpu; 3 | pub mod ctrl2080bus; 4 | pub mod ctrl2080gpu; 5 | pub mod ctrl9096; 6 | pub mod ctrla081; 7 | pub mod ctrla082; 8 | pub mod error; 9 | pub mod ioctl; 10 | pub mod nvos; 11 | pub mod nvtypes; 12 | -------------------------------------------------------------------------------- /src/nvidia/nvos.rs: -------------------------------------------------------------------------------- 1 | ///! Sourced from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/d8f3bcff924776518f1e63286537c3cf365289ac/src/common/sdk/nvidia/inc/nvos.h 2 | use std::os::raw::{c_ulong, c_void}; 3 | 4 | use super::ioctl::NV_IOCTL_MAGIC; 5 | use super::nvtypes::NvHandle; 6 | use crate::ioctl::_IOWR; 7 | 8 | /// Value of the "request" argument used by `nvidia-vgpud` and `nvidia-vgpu-mgr` when calling 9 | /// ioctl to read the PCI device ID, type, and many other things from the driver. 10 | /// 11 | /// Pulled from https://github.com/NVIDIA/open-gpu-kernel-modules/blob/d8f3bcff924776518f1e63286537c3cf365289ac/src/nvidia/arch/nvalloc/unix/include/nv_escape.h 12 | /// and [`nvidia_ioctl`](https://github.com/NVIDIA/open-gpu-kernel-modules/blob/98553501593ef05bddcc438689ed1136f732d40a/kernel-open/nvidia/nv.c) 13 | /// and [`__NV_IOWR`](https://github.com/NVIDIA/open-gpu-kernel-modules/blob/98553501593ef05bddcc438689ed1136f732d40a/kernel-open/common/inc/nv.h) 14 | /// showing that `_IOWR` is used to derive the I/O control request codes. 15 | pub const NV_ESC_RM_CONTROL: c_ulong = _IOWR::(NV_IOCTL_MAGIC, 0x2a); 16 | 17 | /// When issuing ioctl with `NV_ESC_RM_CONTROL` then the `argp` argument is a pointer to a 18 | /// `NVOS54_PARAMETERS` structure like this. 19 | /// 20 | /// See [`NVOS54_PARAMETERS`](https://github.com/NVIDIA/open-gpu-kernel-modules/blob/d8f3bcff924776518f1e63286537c3cf365289ac/src/common/sdk/nvidia/inc/nvos.h) 21 | //#[derive(Debug)] 22 | #[repr(C)] 23 | pub struct Nvos54Parameters { 24 | /// Initialized prior to call. 25 | pub h_client: NvHandle, 26 | /// Initialized prior to call. 27 | pub h_object: NvHandle, 28 | /// Operation type, see comment below. 29 | pub cmd: u32, 30 | /// Set of flags for call. 31 | pub flags: u32, 32 | /// Pointer initialized prior to call. 33 | /// Pointee initialized to 0 prior to call. 34 | /// Pointee is written by ioctl call. 35 | pub params: *mut c_void, 36 | /// Size in bytes of the object referenced in `params`. 37 | pub params_size: u32, 38 | /// Written by ioctl call. See comment below. 39 | pub status: u32, 40 | } 41 | -------------------------------------------------------------------------------- /src/nvidia/nvtypes.rs: -------------------------------------------------------------------------------- 1 | pub type NvHandle = u32; 2 | -------------------------------------------------------------------------------- /src/to_bytes.rs: -------------------------------------------------------------------------------- 1 | pub trait ToBytes { 2 | type Bytes: Copy + AsRef<[u8]> + AsMut<[u8]> + IntoIterator + 'static; 3 | 4 | fn to_ne_bytes(self) -> Self::Bytes; 5 | } 6 | 7 | macro_rules! impl_to_bytes { 8 | ($ty:tt, $len:expr) => { 9 | impl ToBytes for $ty { 10 | type Bytes = [u8; $len]; 11 | 12 | fn to_ne_bytes(self) -> Self::Bytes { 13 | $ty::to_ne_bytes(self) 14 | } 15 | } 16 | }; 17 | } 18 | 19 | impl ToBytes for i8 { 20 | type Bytes = [u8; 1]; 21 | 22 | fn to_ne_bytes(self) -> Self::Bytes { 23 | [self as u8] 24 | } 25 | } 26 | 27 | impl_to_bytes!(i16, 2); 28 | impl_to_bytes!(i32, 4); 29 | impl_to_bytes!(i64, 8); 30 | #[cfg(target_pointer_width = "32")] 31 | impl_to_bytes!(isize, 4); 32 | #[cfg(target_pointer_width = "64")] 33 | impl_to_bytes!(isize, 8); 34 | 35 | impl ToBytes for u8 { 36 | type Bytes = [u8; 1]; 37 | 38 | fn to_ne_bytes(self) -> Self::Bytes { 39 | [self] 40 | } 41 | } 42 | 43 | impl_to_bytes!(u16, 2); 44 | impl_to_bytes!(u32, 4); 45 | impl_to_bytes!(u64, 8); 46 | #[cfg(target_pointer_width = "32")] 47 | impl_to_bytes!(usize, 4); 48 | #[cfg(target_pointer_width = "64")] 49 | impl_to_bytes!(usize, 8); 50 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | #[cfg(feature = "proxmox")] 5 | use crate::uuid::Uuid; 6 | 7 | #[derive(Clone, Copy)] 8 | #[repr(C, align(8))] 9 | pub struct AlignedU64(pub u64); 10 | 11 | impl fmt::Debug for AlignedU64 { 12 | #[inline] 13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 14 | fmt::Debug::fmt(&self.0, f) 15 | } 16 | } 17 | 18 | impl fmt::LowerHex for AlignedU64 { 19 | #[inline] 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | fmt::LowerHex::fmt(&self.0, f) 22 | } 23 | } 24 | 25 | pub fn from_c_str(value: &[u8]) -> Cow<'_, str> { 26 | let len = value.iter().position(|&c| c == 0).unwrap_or(value.len()); 27 | 28 | String::from_utf8_lossy(&value[..len]) 29 | } 30 | 31 | /// Extracts the VMID from the last segment of a mdev uuid 32 | /// 33 | /// For example, for this uuid 00000000-0000-0000-0000-000000000100 34 | /// it would extract the number 100 35 | /// 36 | /// All except the last segment must be zero 37 | #[cfg(feature = "proxmox")] 38 | pub fn uuid_to_vmid(uuid: Uuid) -> Option { 39 | // Following https://forum.proxmox.com/threads/automatically-assign-uuid-to-a-vgpu-instance.98994/#post-427480 40 | // 41 | // The format is `-0000-0000-0000-`. Ensure the parts that should be 42 | // zero are in fact zero. 43 | if uuid.1 != 0 || uuid.2 != 0 || uuid.3[0] != 0 || uuid.3[1] != 0 { 44 | return None; 45 | } 46 | 47 | // Format the last segment of the uuid 48 | let s = format!( 49 | "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", 50 | uuid.3[2], uuid.3[3], uuid.3[4], uuid.3[5], uuid.3[6], uuid.3[7] 51 | ); 52 | 53 | // Parse it as a normal decimal number to get the right vm id 54 | s.parse().ok() 55 | } 56 | -------------------------------------------------------------------------------- /src/uuid.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Clone, Copy)] 4 | #[repr(C)] 5 | pub struct Uuid(pub u32, pub u16, pub u16, pub [u8; 8]); 6 | 7 | impl fmt::Display for Uuid { 8 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 9 | write!( 10 | f, 11 | "{:08x}-{:04x}-{:04x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", 12 | self.0, 13 | self.1, 14 | self.2, 15 | self.3[0], 16 | self.3[1], 17 | self.3[2], 18 | self.3[3], 19 | self.3[4], 20 | self.3[5], 21 | self.3[6], 22 | self.3[7] 23 | ) 24 | } 25 | } 26 | --------------------------------------------------------------------------------