Initial commit

This commit is contained in:
Matt Bilker
2021-09-17 08:33:12 +00:00
commit 4a73f9376d
6 changed files with 545 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "vgpu_unlock-rs"
version = "0.1.0"
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2.102"
serde = { version = "1.0.130", features = ["derive"] }
toml = "0.5.8"

22
LICENSE Normal file
View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2021 Jonathan Johansson
Copyright (c) 2021 Matt Bilker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

49
src/format.rs Normal file
View File

@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
use std::fmt;
pub struct CStrFormat<'a>(pub &'a [u8]);
impl<'a> fmt::Debug for CStrFormat<'a> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<'a> fmt::Display for CStrFormat<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = crate::from_c_str(&self.0);
fmt::Debug::fmt(&s, f)
}
}
pub struct HexFormat<T>(pub T);
impl<T: fmt::LowerHex> fmt::Debug for HexFormat<T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<T: fmt::LowerHex> fmt::Display for HexFormat<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "0x{:x}", self.0)
}
}
pub struct StraightFormat<T>(pub T);
impl<T: fmt::Debug> fmt::Debug for StraightFormat<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl<T: fmt::Display> fmt::Display for StraightFormat<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}

406
src/lib.rs Normal file
View File

@@ -0,0 +1,406 @@
// SPDX-License-Identifier: MIT
//! Credit to community members for most of the work with notable contributions by:
//!
//! - DualCoder for the original [`vgpu_unlock`](https://github.com/DualCoder/vgpu_unlock)
//! - DualCoder, snowman, Felix, Elec for vGPU profile modification at runtime
use std::borrow::Cow;
use std::env;
use std::fmt;
use std::fs;
use std::io::Write;
use std::mem;
use std::os::raw::{c_int, c_ulong, c_void};
use std::os::unix::io::RawFd;
use std::path::PathBuf;
use std::str;
use libc::RTLD_NEXT;
use serde::Deserialize;
mod format;
mod log;
use crate::format::{CStrFormat, HexFormat, StraightFormat};
use crate::log::{error, info};
/// Value of the "request" argument used by `nvidia-vgpud` and `nvidia-vgpu-mgr` when calling
/// ioctl to read the PCI device ID and type (and possibly other things) from the GPU.
const REQ_QUERY_GPU: c_ulong = 0xc020462a;
/// `result` is a pointer to `uint64_t`.
const OP_READ_DEV_TYPE: u32 = 0x800289;
/// `result` is a pointer to `uint16_t[4]`, the second element (index 1) is the device ID, the
/// forth element (index 3) is the subsystem ID.
const OP_READ_PCI_ID: u32 = 0x20801801;
/// `result` is a pointer to `VgpuConfig`.
const OP_READ_VGPU_CFG: u32 = 0xa0820102;
/// `nvidia-vgpu-mgr` expects this value for a vGPU capable GPU.
const DEV_TYPE_VGPU_CAPABLE: u64 = 3;
/// When ioctl returns success (retval >= 0) but sets the status value of the arg structure to 3
/// then `nvidia-vgpud` will sleep for a bit (first 0.1s then 1s then 10s) then issue the same
/// ioctl call again until the status differs from 3. It will attempt this for up to 24h before
/// giving up.
const STATUS_OK: u32 = 0;
const STATUS_TRY_AGAIN: u32 = 3;
/// When issuing ioctl with REQ_QUERY_GPU then the `argp` argument is a pointer to a structure
/// like this
//#[derive(Debug)]
#[repr(C)]
struct Request {
/// Initialized prior to call.
unknown_1: u32,
/// Initialized prior to call.
unknown_2: u32,
/// Operation type, see comment below.
op_type: u32,
/// Pointer initialized prior to call.
/// Pointee initialized to 0 prior to call.
/// Pointee is written by ioctl call.
result: *mut c_void,
/// Set to 0x10 for READ_PCI_ID and set to 4 for
/// READ_DEV_TYPE prior to call.
unknown_4: u32,
/// Written by ioctl call. See comment below.
status: u32,
}
#[repr(C)]
struct VgpuConfig {
gpu_type: u32,
card_name: [u8; 32],
vgpu_type: [u8; 160],
features: [u8; 128],
max_instances: u32,
num_displays: u32,
display_width: u32,
display_height: u32,
max_pixels: u32,
frl_config: u32,
cuda_enabled: u32,
ecc_supported: u32,
mig_instance_size: u32,
multi_vgpu_supported: u32,
pci_id: u64,
pci_device_id: u64,
framebuffer: u64,
mappable_video_size: u64,
framebuffer_reservation: u64,
encoder_capacity: u64,
bar1_length: u64,
frl_enabled: u32,
blob: [u8; 256],
license_type: [u8; 1156],
}
#[derive(Deserialize)]
struct VgpuProfileOverride<'a> {
gpu_type: Option<u32>,
card_name: Option<&'a str>,
vgpu_type: Option<&'a str>,
features: Option<&'a str>,
max_instances: Option<u32>,
num_displays: Option<u32>,
display_width: Option<u32>,
display_height: Option<u32>,
max_pixels: Option<u32>,
frl_config: Option<u32>,
cuda_enabled: Option<u32>,
ecc_supported: Option<u32>,
mig_instance_size: Option<u32>,
multi_vgpu_supported: Option<u32>,
pci_id: Option<u64>,
pci_device_id: Option<u64>,
framebuffer: Option<u64>,
mappable_video_size: Option<u64>,
framebuffer_reservation: Option<u64>,
encoder_capacity: Option<u64>,
bar1_length: Option<u64>,
frl_enabled: Option<u32>,
license_type: Option<&'a str>,
}
impl fmt::Debug for VgpuConfig {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("VgpuConfig")
.field("gpu_type", &self.gpu_type)
.field("card_name", &CStrFormat(&self.card_name))
.field("vgpu_type", &CStrFormat(&self.vgpu_type))
.field("features", &CStrFormat(&self.features))
.field("max_instances", &self.max_instances)
.field("num_displays", &self.num_displays)
.field("display_width", &self.display_width)
.field("display_height", &self.display_height)
.field("max_pixels", &self.max_pixels)
.field("frl_config", &self.frl_config)
.field("cuda_enabled", &self.cuda_enabled)
.field("ecc_supported", &self.ecc_supported)
.field("mig_instance_size", &self.mig_instance_size)
.field("multi_vgpu_supported", &self.multi_vgpu_supported)
.field("pci_id", &HexFormat(self.pci_id))
.field("pci_device_id", &HexFormat(self.pci_device_id))
.field("framebuffer", &HexFormat(self.framebuffer))
.field("mappable_video_size", &HexFormat(self.mappable_video_size))
.field(
"framebuffer_reservation",
&HexFormat(self.framebuffer_reservation),
)
.field("encoder_capacity", &HexFormat(self.encoder_capacity))
.field("bar1_length", &HexFormat(self.bar1_length))
.field("blob", &StraightFormat(&self.blob))
.field("license_type", &CStrFormat(&self.license_type))
.finish()
}
}
#[no_mangle]
pub unsafe extern "C" fn ioctl(fd: RawFd, request: c_ulong, argp: *mut c_void) -> c_int {
static mut IOCTL_FN_PTR: Option<unsafe extern "C" fn(RawFd, c_ulong, ...) -> c_int> = None;
//log!("ioctl({}, {}, {:?})", fd, request, data);
let next_ioctl = match IOCTL_FN_PTR {
Some(func) => func,
None => {
let next_ioctl = mem::transmute(libc::dlsym(RTLD_NEXT, b"ioctl\0".as_ptr() as _));
IOCTL_FN_PTR = mem::transmute(next_ioctl);
next_ioctl
}
};
let ret = next_ioctl(fd, request, argp);
if request != REQ_QUERY_GPU {
// Not a call we care about.
return ret;
}
if ret < 0 {
// Call failed.
return ret;
}
let io_data = &mut *(argp as *mut Request);
if io_data.status == STATUS_TRY_AGAIN {
// Driver will try again.
return ret;
}
//log!("{:x?}", io_data);
match io_data.op_type {
OP_READ_PCI_ID => {
// Lookup address of the device and subsystem IDs.
let devid_ptr: *mut u16 = io_data.result.add(2).cast();
let subsysid_ptr: *mut u16 = io_data.result.add(6).cast();
let actual_devid = *devid_ptr;
let actual_subsysid = *subsysid_ptr;
let (spoofed_devid, spoofed_subsysid) = match actual_devid {
// Maxwell
0x1340..=0x13bd | 0x174d..=0x179c => {
// Tesla M10
(0x13bd, 0x1160)
}
// Maxwell 2.0
0x13c0..=0x1436 | 0x1617..=0x1667 | 0x17c2..=0x17fd => {
// Tesla M60
(0x13f2, actual_subsysid)
}
// Pascal
0x15f0 | 0x15f1 | 0x1b00..=0x1d56 | 0x1725..=0x172f => {
// Tesla P40
(0x1b38, actual_subsysid)
}
// GV100 Volta
//
// 0x1d81 = TITAN V
// 0x1dba = Quadro GV100 32GB
0x1d81 | 0x1dba => {
// Tesla V100 32GB PCIE
(0x1db6, actual_subsysid)
}
// Turing
0x1e02..=0x1ff9 | 0x2182..=0x21d1 => {
// Quadro RTX 6000
(0x1e30, 0x12ba)
}
// Ampere
0x2200..=0x2600 => {
// RTX A6000
(0x2230, actual_subsysid)
}
_ => (actual_devid, actual_subsysid),
};
*devid_ptr = spoofed_devid;
*subsysid_ptr = spoofed_subsysid;
}
OP_READ_DEV_TYPE => {
let dev_type_ptr: *mut u64 = io_data.result.cast();
// Set device type to vGPU capable.
*dev_type_ptr = DEV_TYPE_VGPU_CAPABLE;
}
OP_READ_VGPU_CFG => {
let config = &mut *(io_data.result as *mut VgpuConfig);
info!("{:#?}", config);
if !apply_profile_override(config) {
error!("Failed to apply profile override");
return -1;
}
}
_ => {}
}
if io_data.status != STATUS_OK {
// Things seems to work fine even if some operations that fail result in failed assertions.
// So here we change the status value for these cases to cleanup the logs for
// `nvidia-vgpu-mgr`.
if io_data.op_type == 0xa0820104 || io_data.op_type == 0x90960103 {
io_data.status = STATUS_OK;
} else {
error!("op_type: 0x{:x} failed.", io_data.op_type);
}
}
// Workaround for some Maxwell cards not supporting reading inforom.
if io_data.op_type == 0x2080014b && io_data.status == 0x56 {
io_data.status = 0x57;
}
ret
}
pub fn from_c_str<'a>(value: &'a [u8]) -> Cow<'a, str> {
let len = value.iter().position(|&c| c == 0).unwrap_or(value.len());
String::from_utf8_lossy(&value[..len])
}
fn apply_profile_override(config: &mut VgpuConfig) -> bool {
const DEFAULT_CONFIG_PATH: &'static str = "/etc/vgpu_unlock/profile_override.toml";
let config_path = match env::var_os("VGPU_UNLOCK_PROFILE_OVERRIDE_CONFIG_PATH") {
Some(path) => PathBuf::from(path),
None => PathBuf::from(DEFAULT_CONFIG_PATH),
};
let config_overrides = match fs::read_to_string(&config_path) {
Ok(data) => data,
Err(e) => {
error!("Failed to read '{}': {}", config_path.display(), e);
return false;
}
};
let config_override: VgpuProfileOverride = match toml::from_str(&config_overrides) {
Ok(config) => config,
Err(e) => {
error!("Failed to decode config: {}", e);
return false;
}
};
let gpu_type = config.gpu_type;
macro_rules! handle_copy_overrides {
($($field:ident),*$(,)?) => {
$(
if let Some(value) = config_override.$field {
info!(
"Patching nvidia-{}/{}: {} -> {}",
gpu_type,
stringify!($field),
config.$field,
value
);
config.$field = value;
}
)*
};
}
macro_rules! handle_str_overrides {
($($field:ident),*$(,)?) => {
$(
if let Some(value) = config_override.$field {
let value_bytes = value.as_bytes();
// Use `len - 1` to account for the required NULL terminator.
if value_bytes.len() > config.$field.len() - 1 {
error!(
"Patching nvidia-{}/{}: value '{}' is too long",
gpu_type,
stringify!($field),
value
);
return false;
} else {
info!(
"Patching nvidia-{}/{}: '{}' -> '{}'",
gpu_type,
stringify!($field),
from_c_str(&config.$field),
value
);
// Zero out the field first
config.$field.fill(0);
let _ = config.$field[..].as_mut().write_all(value_bytes);
}
}
)*
}
}
/*
* While the following could be done with two statements. I wanted the log statements to be in
* field order.
*/
handle_copy_overrides! {
gpu_type,
}
handle_str_overrides! {
card_name,
vgpu_type,
features,
}
handle_copy_overrides! {
max_instances,
num_displays,
display_width,
display_height,
max_pixels,
frl_config,
cuda_enabled,
ecc_supported,
mig_instance_size,
multi_vgpu_supported,
pci_id,
pci_device_id,
framebuffer,
mappable_video_size,
framebuffer_reservation,
encoder_capacity,
bar1_length,
frl_enabled,
}
handle_str_overrides! {
license_type,
}
true
}

54
src/log.rs Normal file
View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
use std::cell::RefCell;
use std::fmt;
use std::io::Write;
use libc::{c_int, LOG_ERR};
pub(crate) fn syslog(level: c_int, args: fmt::Arguments<'_>) {
thread_local!(static MSG_BUFFER: RefCell<Vec<u8>> = RefCell::new(Vec::with_capacity(512)));
MSG_BUFFER.with(|msg_buffer| {
let mut msg_buffer = msg_buffer.borrow_mut();
msg_buffer.clear();
match msg_buffer.write_fmt(args) {
Ok(_) => {
msg_buffer.push(b'\0');
unsafe { libc::syslog(level, b"%s\0".as_ptr() as _, msg_buffer.as_ptr()) };
}
Err(e) => {
msg_buffer.clear();
let _ = msg_buffer.write_all(b"Failed to format message: ");
if let Err(_) = write!(&mut msg_buffer, "{}", e) {
msg_buffer.clear();
let _ = msg_buffer.write_all(b"Failed to format message and error message");
}
msg_buffer.push(b'\0');
unsafe { libc::syslog(LOG_ERR, b"%s\0".as_ptr() as _, msg_buffer.as_ptr()) }
}
}
});
}
macro_rules! error {
($($arg:tt)+) => {
$crate::log::syslog(::libc::LOG_ERR, format_args!($($arg)+))
};
}
macro_rules! info {
($($arg:tt)+) => {
$crate::log::syslog(::libc::LOG_NOTICE, format_args!($($arg)+))
};
}
pub(crate) use error;
pub(crate) use info;