Merge branch 'magma-in-mesa' into 'main'

[RFC][WiP] Magma: Cross-platform system call interface [RFC][WiP]

See merge request mesa/mesa!33190
This commit is contained in:
Gurchetan Singh 2025-12-20 00:16:42 +00:00
commit ab4bb3a3ba
40 changed files with 4646 additions and 1 deletions

View file

@ -102,3 +102,12 @@ opencl_headers = files(
'CL/cl_version.h',
'CL/opencl.h',
)
if with_magma and host_machine.system() == 'linux'
drm_h = files('drm-uapi/drm.h')
amdgpu_drm_h = files('drm-uapi/amdgpu_drm.h')
msm_drm_h = files('drm-uapi/msm_drm.h')
virtgpu_drm_h = files('drm-uapi/virtgpu_drm.h')
xe_drm_h = files('drm-uapi/xe_drm.h')
i915_drm_h = files('drm-uapi/i915_drm.h')
endif

View file

@ -777,8 +777,10 @@ if with_gallium_rusticl
endif
endif
with_magma = get_option('magma')
with_virtgpu_kumquat = get_option('virtgpu_kumquat') and with_gfxstream_vk
if with_gallium_rusticl or with_nouveau_vk or with_tools.contains('etnaviv') or with_virtgpu_kumquat
pre_args += '-DHAVE_MAGMA=@0@'.format(with_magma.to_int())
if with_gallium_rusticl or with_nouveau_vk or with_tools.contains('etnaviv') or with_virtgpu_kumquat or with_magma
# rust.bindgen() does not pass `--rust-target` to bindgen until 1.7.0.
if meson.version().version_compare('< 1.7.0')
error('Mesa Rust support requires Meson 1.7.0 or newer')

View file

@ -863,3 +863,10 @@ option(
type : 'feature',
description : 'Use SPIRV-Tools for dumping SPIR-V for debugging purposes (required by CLC)'
)
option(
'magma',
type : 'boolean',
value : false,
description : 'Build Magma backends of Mesa3D'
)

View file

@ -0,0 +1,15 @@
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
BasedOnStyle: LLVM
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
BreakBeforeBraces: Linux
ColumnLimit: 100
IndentWidth: 4
TabWidth: 4
UseTab: Never
Cpp11BracedListStyle: false
IndentCaseLabels: false

View file

@ -0,0 +1,63 @@
/*
* Copyright 2025 Mesa3D authors
* SPDX-License-Identifier: MIT
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#ifndef MAGMA_H
#define MAGMA_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* Versioning
*/
#define MAGMA_VERSION_MAJOR 0
#define MAGMA_VERSION_MINOR 1
#define MAGMA_VERSION_PATCH 3
#define MAGMA_MAX_PHYSICAL_DEVICES 8
struct magma_physical_device;
struct magma_device;
struct magma_buffer;
typedef struct *magma_physical_device magma_physical_device_t;
typedef struct *magma_device magma_device_t;
typedef struct *magma_buffer magma_buffer_t;
/**
* Enumerates physical devices on the system.
*
* # Safety
* - `physical_devices` must be an array of type magma_physical_device_t with size `MAGMA_MAX_PHYSICAL_DEVICES`.
*/
int32_t
magma_enumerate_physical_devices(magma_physical_device_t physical_devices[MAGMA_MAX_PHYSICAL_DEVICES],
uint32_t *num_devices);
int32_t magma_create_device(const magma_physical_device_t physical_device, magma_device_t *device);
int32_t magma_physical_device_close(magma_physical_device_t *physical_devices);
int32_t magma_device_get_memory_properties(const magma_device_t device,
struct magma_memory_properties *mem_props);
int32_t magma_device_get_memory_budget(const magma_device_t device, const uint32_t heap_idx,
struct magma_heap_budget *budget);
int32_t magma_device_create_buffer(const magma_device_t device,
const struct magma_create_buffer_info *info,
magma_buffer_t *buffer);
int32_t magma_device_create_context(const magma_device_t device,
const struct magma_create_context_info *info,
magma_context_t *context);
int32_t magma_device_close(magma_physical_device_t *physical_devices);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,63 @@
/*
* Copyright 2025 Mesa3D authors
* SPDX-License-Identifier: MIT
*/
#ifndef MAGMA_DEFINES_H
#define MAGMA_DEFINES_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MAGMA_MAX_MEMORY_HEAPS 8
#define MAGMA_MAX_MEMORY_TYPES 16
struct magma_pci_info {
uint16_t vendor_id;
uint16_t device_id;
uint16_t subvendor_id;
uint16_t subdevice_id;
uint16_t revision_id;
};
struct magma_memory_heap {
uint64_t heap_size;
uint64_t heap_flags;
};
struct magma_memory_type {
uint32_t property_flags;
uint32_t heap_idx;
};
struct magma_memory_properties {
uint32_t memory_type_count;
uint32_t memory_heap_count;
struct magma_memory_type memory_types[MAGMA_MAX_MEMORY_TYPES];
struct magma_memory_heaps memory_heaps[MAGMA_MAX_MEMORY_HEAPS];
};
struct magma_heap_budget {
uint64_t budget;
uint64_t usage;
};
uint32_t MAGMA_BUFFER_FLAG_AMD_OA = 0x000000001;
uint32_t MAGMA_BUFFER_FLAG_AMD_GDS = 0x000000002;
struct magma_create_buffer_info {
uint32_t memory_type_idx;
uint32_t alignment;
uint32_t common_flags;
uint32_t vendor_flags;
uint32_t size;
};
uint16_t MAGMA_VENDOR_ID_INTEL = 0x8086;
uint16_t MAGMA_VENDOR_ID_AMD = 0x1002;
uint16_t MAGMA_VENDOR_ID_MALI = 0x13B5;
uint16_t MAGMA_VENDOR_ID_QCOM = 0x5413;
#endif

91
src/magma/ffi/lib.rs Normal file
View file

@ -0,0 +1,91 @@
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::boxed::Box;
use std::convert::TryInto;
use std::ffi::CStr;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::panic::catch_unwind;
use std::panic::AssertUnwindSafe;
use std::ptr::null_mut;
use std::slice::from_raw_parts_mut;
use std::sync::Mutex;
use libc::EINVAL;
use libc::ESRCH;
use log::error;
use mesa3d_magma::magma_enumerate_devices as enumerate_devices;
use mesa3d_magma::MagmaBuffer;
use mesa3d_magma::MagmaDevice;
use mesa3d_magma::MagmaPhysicalDevice;
use mesa3d_util::FromRawDescriptor;
use mesa3d_util::IntoRawDescriptor;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use mesa3d_util::OwnedDescriptor;
use mesa3d_util::RawDescriptor;
use mesa3d_util::DEFAULT_RAW_DESCRIPTOR;
const NO_ERROR: i32 = 0;
const MAGMA_MAX_PHYSICAL_DEVICES: u32 = 8;
fn return_result<T>(result: MesaResult<T>) -> i32 {
if let Err(e) = result {
error!("An error occurred: {}", e);
-EINVAL
} else {
NO_ERROR
}
}
macro_rules! return_on_error {
($result:expr) => {
match $result {
Ok(t) => t,
Err(e) => {
error!("An error occurred: {}", e);
return -EINVAL;
}
}
};
}
#[allow(non_camel_case_types)]
type magma_physical_device = MagmaPhysicalDevice;
#[allow(non_camel_case_types)]
type magma_device = MagmaDevice;
#[allow(non_camel_case_types)]
type magma_buffer = MagmaBuffer;
// The following structs (in define.rs) must be ABI-compatible with FFI header
// (magma.h).
#[no_mangle]
pub unsafe extern "C" fn magma_enumerate_physical_devices(
physical_devices: &mut *mut magma_physical_device,
num_devices: &mut u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = enumerate_devices();
let phys_devs = return_on_error!(result);
*num_devices = phys_devs.len().try_into().unwrap();
if *num_devices > MAGMA_MAX_PHYSICAL_DEVICES {
return -EINVAL;
}
let physical_devices =
from_raw_parts_mut(physical_devices, MAGMA_MAX_PHYSICAL_DEVICES as usize);
for (i, phys_dev) in phys_devs.into_iter().enumerate() {
physical_devices[i] = Box::into_raw(Box::new(phys_dev)) as _;
}
NO_ERROR
}))
.unwrap_or(-ESRCH)
}

11
src/magma/ffi/meson.build Normal file
View file

@ -0,0 +1,11 @@
# Copyright © 2025 Google
# SPDX-License-Identifier: MIT
libmesa_magma = static_library(
'mesa3d_magma_ffi',
'lib.rs',
gnu_symbol_visibility : 'hidden',
rust_abi : 'c',
link_with: [libmesa_magma, libmesa_rust_util],
dependencies: [dep_mesa3d_util, dep_log]
)

16
src/magma/lib.rs Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
mod magma;
mod magma_defines;
mod magma_kumquat;
mod sys;
mod traits;
pub use magma_defines::*;
pub use magma::magma_enumerate_devices;
pub use magma::MagmaBuffer;
pub use magma::MagmaContext;
pub use magma::MagmaDevice;
pub use magma::MagmaPhysicalDevice;

291
src/magma/magma.rs Normal file
View file

@ -0,0 +1,291 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
//! Magma: Rust implementation of Fuchsia's driver model.
//!
//! Design found at <https://fuchsia.dev/fuchsia-third_party/mesa3d/src/development/graphics/magma/concepts/design>.
use std::sync::Arc;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaHandle;
use mesa3d_util::OwnedDescriptor;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaError;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MagmaPciBusInfo;
use crate::magma_defines::MagmaPciInfo;
use crate::magma_defines::MagmaResult;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::PhysicalDevice;
use crate::magma_kumquat::enumerate_devices as magma_kumquat_enumerate_devices;
use crate::sys::platform::enumerate_devices as platform_enumerate_devices;
const VIRTGPU_KUMQUAT_ENABLED: &str = "VIRTGPU_KUMQUAT";
#[repr(C)]
#[derive(Clone)]
pub struct MagmaPhysicalDevice {
physical_device: Arc<dyn PhysicalDevice>,
pci_info: MagmaPciInfo,
pci_bus_info: MagmaPciBusInfo,
}
#[derive(Clone)]
pub struct MagmaDevice {
device: Arc<dyn Device>,
}
#[derive(Clone)]
pub struct MagmaContext {
_context: Arc<dyn Context>,
}
#[derive(Clone)]
pub struct MagmaBuffer {
buffer: Arc<dyn Buffer>,
}
pub fn magma_enumerate_devices() -> MagmaResult<Vec<MagmaPhysicalDevice>> {
let devices = match std::env::var(VIRTGPU_KUMQUAT_ENABLED) {
Ok(_) => magma_kumquat_enumerate_devices()?,
Err(_) => platform_enumerate_devices()?,
};
Ok(devices)
}
impl MagmaPhysicalDevice {
pub(crate) fn new(
physical_device: Arc<dyn PhysicalDevice>,
pci_info: MagmaPciInfo,
pci_bus_info: MagmaPciBusInfo,
) -> MagmaPhysicalDevice {
MagmaPhysicalDevice {
physical_device,
pci_info,
pci_bus_info,
}
}
pub fn create_device(&self) -> MagmaResult<MagmaDevice> {
let device = self
.physical_device
.create_device(&self.physical_device, &self.pci_info)?;
Ok(MagmaDevice { device })
}
}
#[allow(dead_code)]
pub struct MagmaSemaphore {
semaphore: OwnedDescriptor,
}
#[allow(dead_code)]
struct MagmaExecResource {
buffer: MagmaBuffer,
offset: u64,
length: u64,
}
#[allow(dead_code)]
struct MagmaExecCommandBuffer {
resource_idx: u32,
unused: u32,
start_offset: u64,
}
#[allow(dead_code)]
struct MagmaCommandDescriptor {
flags: u64,
command_buffers: Vec<MagmaExecCommandBuffer>,
resources: Vec<MagmaExecResource>,
wait_semaphores: Vec<MagmaSemaphore>,
signal_semaphores: Vec<MagmaSemaphore>,
}
#[allow(dead_code)]
struct MagmaInlineCommandBuffer {
data: Vec<u8>,
wait_semaphores: Vec<MagmaSemaphore>,
signal_semaphores: Vec<MagmaSemaphore>,
}
impl MagmaDevice {
pub fn get_memory_properties(&self) -> MagmaResult<MagmaMemoryProperties> {
let mem_props = self.device.get_memory_properties()?;
Ok(mem_props)
}
pub fn get_memory_budget(&self, heap_idx: u32) -> MagmaResult<MagmaHeapBudget> {
let budget = self.device.get_memory_budget(heap_idx)?;
Ok(budget)
}
pub fn create_context(&self) -> MagmaResult<MagmaContext> {
let context = self.device.create_context(&self.device)?;
Ok(MagmaContext { _context: context })
}
pub fn create_buffer(&self, create_info: &MagmaCreateBufferInfo) -> MagmaResult<MagmaBuffer> {
let buffer = self.device.create_buffer(&self.device, create_info)?;
Ok(MagmaBuffer { buffer })
}
// FIXME: we probably want to import with a memory type
pub fn import(&self, info: MagmaImportHandleInfo) -> MagmaResult<MagmaBuffer> {
let buffer = self.device.import(&self.device, info)?;
Ok(MagmaBuffer { buffer })
}
}
impl MagmaBuffer {
pub fn map(&self) -> MagmaResult<Arc<dyn MappedRegion>> {
let region = self.buffer.map(&self.buffer)?;
Ok(region)
}
pub fn export(&self) -> MagmaResult<MesaHandle> {
let handle = self.buffer.export()?;
Ok(handle)
}
pub fn invalidate(
&self,
sync_flags: u64,
ranges: &[MagmaMappedMemoryRange],
) -> MagmaResult<()> {
self.buffer.invalidate(sync_flags, ranges)?;
Ok(())
}
pub fn flush(&self, sync_flags: u64, ranges: &[MagmaMappedMemoryRange]) -> MagmaResult<()> {
self.buffer.flush(sync_flags, ranges)?;
Ok(())
}
}
impl MagmaContext {
pub fn execute_command(
_connection: &MagmaPhysicalDevice,
_command_descriptor: u64,
) -> MagmaResult<u64> {
Err(MagmaError::Unimplemented)
}
pub fn execute_immediate_commands(
_connection: &MagmaPhysicalDevice,
_wait_semaphores: Vec<MagmaSemaphore>,
_signal_semaphore: Vec<MagmaSemaphore>,
) -> MagmaResult<u64> {
Err(MagmaError::Unimplemented)
}
pub fn raw_handle() -> MagmaResult<u64> {
Err(MagmaError::Unimplemented)
}
}
#[cfg(test)]
mod tests {
use crate::*;
fn get_physical_device() -> Option<MagmaPhysicalDevice> {
let valid_vendor_ids: [u16; 4] = [
MAGMA_VENDOR_ID_INTEL,
MAGMA_VENDOR_ID_AMD,
MAGMA_VENDOR_ID_MALI,
MAGMA_VENDOR_ID_QCOM,
];
let physical_devices = magma_enumerate_devices().unwrap();
physical_devices
.into_iter()
.find(|device| valid_vendor_ids.contains(&device.pci_info.vendor_id))
}
#[test]
fn test_memory_properties() {
let physical_device = get_physical_device().unwrap();
let device = physical_device.create_device().unwrap();
let mem_props = device.get_memory_properties().unwrap();
assert_ne!(mem_props.memory_type_count, 0);
assert_ne!(mem_props.memory_heap_count, 0);
assert_ne!(mem_props.memory_type_count, 0,);
assert_ne!(mem_props.memory_heap_count, 0,);
println!("--- Retrieved Magma Memory Properties ---");
println!(" Total Memory Type Count: {}", mem_props.memory_type_count);
println!(" Total Memory Heap Count: {}", mem_props.memory_heap_count);
println!("--- Validating Memory Heaps ---");
for i in 0..mem_props.memory_heap_count as usize {
let heap = &mem_props.memory_heaps[i];
println!(" Heap {}:", i);
println!(" Size: {} bytes", heap.heap_size);
println!(" Flags: {:#x}", heap.heap_flags); // Print flags in hex for clarity
// Assertions for heaps
assert!(heap.heap_size > 0);
}
// Loop through and validate Memory Types
println!("--- Validating Memory Types ---");
for i in 0..mem_props.memory_type_count as usize {
let mem_type = &mem_props.memory_types[i];
println!(" Memory Type {}:", i);
println!(" Heap Index: {}", mem_type.heap_idx);
println!(" Property Flags: {:#x}", mem_type.property_flags); // Print flags in hex
// Assertions for memory types
assert!(mem_type.heap_idx < mem_props.memory_heap_count,);
// Assert that each memory type has at least one property flag set (not NONE)
assert_ne!(mem_type.property_flags, 0,);
}
println!("--- Magma Memory Properties Test Passed Successfully! ---");
}
#[test]
fn test_memory_allocation() {
let physical_device = get_physical_device().unwrap();
let device = physical_device.create_device().unwrap();
let mem_props = device.get_memory_properties().unwrap();
let mut chosen_memory_type_idx: Option<u32> = None;
for i in 0..mem_props.memory_type_count as usize {
let mem_type = &mem_props.memory_types[i];
if (mem_type.property_flags & MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT != 0)
&& (mem_type.property_flags & MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT != 0)
{
chosen_memory_type_idx = Some(i as u32);
break;
}
}
let memory_type_idx = chosen_memory_type_idx.unwrap();
let buffer_size: u64 = 4096;
let create_info = MagmaCreateBufferInfo {
memory_type_idx,
alignment: 4096,
common_flags: 0,
vendor_flags: 0,
size: buffer_size,
};
let buffer = device.create_buffer(&create_info).unwrap();
}
}

206
src/magma/magma_defines.rs Normal file
View file

@ -0,0 +1,206 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use mesa3d_util::MesaError;
use remain::sorted;
use thiserror::Error;
use zerocopy::FromBytes;
use zerocopy::IntoBytes;
/// An error type based on magma_common_defs.h
#[sorted]
#[derive(Error, Debug)]
pub enum MagmaError {
#[error("Access Denied")]
AccessDenied,
#[error("Bad State")]
BadState,
#[error("Connection Lost")]
ConnectionLost,
#[error("Context Killed")]
ContextKilled,
#[error("Internal Error")]
InternalError,
#[error("Invalid Arguments")]
InvalidArgs,
#[error("Memory Error")]
MemoryError,
#[error("A Mesa error was returned {0}")]
MesaError(MesaError),
#[error("Timed out")]
TimedOut,
#[error("Unimplemented")]
Unimplemented,
}
impl From<MesaError> for MagmaError {
fn from(e: MesaError) -> MagmaError {
MagmaError::MesaError(e)
}
}
pub type MagmaResult<T> = std::result::Result<T, MagmaError>;
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaPciInfo {
pub vendor_id: u16,
pub device_id: u16,
pub subvendor_id: u16,
pub subdevice_id: u16,
pub revision_id: u8,
pub padding: [u8; 7],
}
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaPciBusInfo {
pub domain: u16,
pub bus: u8,
pub device: u8,
pub function: u8,
pub padding: [u8; 7],
}
// Should be set in the case of VRAM only
pub const MAGMA_HEAP_DEVICE_LOCAL_BIT: u64 = 0x00000001;
pub const MAGMA_HEAP_CPU_VISIBLE_BIT: u64 = 0x00000010;
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaHeap {
pub heap_size: u64,
pub heap_flags: u64,
}
impl MagmaHeap {
pub fn is_device_local(&self) -> bool {
self.heap_flags & MAGMA_HEAP_DEVICE_LOCAL_BIT != 0
}
pub fn is_cpu_visible(&self) -> bool {
self.heap_flags & MAGMA_HEAP_CPU_VISIBLE_BIT != 0
}
}
pub const MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT: u32 = 0x00000001;
pub const MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT: u32 = 0x00000002;
pub const MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT: u32 = 0x00000004;
pub const MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT: u32 = 0x00000008;
pub const MAGMA_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT: u32 = 0x00000010;
pub const MAGMA_MEMORY_PROPERTY_PROTECTED_BIT: u32 = 0x00000020;
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaMemoryType {
pub property_flags: u32,
pub heap_idx: u32,
}
impl MagmaMemoryType {
pub fn is_device_local(&self) -> bool {
self.property_flags & MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT != 0
}
pub fn is_coherent(&self) -> bool {
self.property_flags & MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT != 0
}
pub fn is_cached(&self) -> bool {
self.property_flags & MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT != 0
}
pub fn is_protected(&self) -> bool {
self.property_flags & MAGMA_MEMORY_PROPERTY_PROTECTED_BIT != 0
}
}
pub const MAGMA_MAX_MEMORY_TYPES: usize = 32;
pub const MAGMA_MAX_MEMORY_HEAPS: usize = 16;
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaMemoryProperties {
pub memory_type_count: u32,
pub memory_heap_count: u32,
pub memory_types: [MagmaMemoryType; MAGMA_MAX_MEMORY_TYPES],
pub memory_heaps: [MagmaHeap; MAGMA_MAX_MEMORY_HEAPS],
}
impl MagmaMemoryProperties {
pub(crate) fn increment_heap_count(&mut self) {
self.memory_heap_count += 1;
}
pub(crate) fn add_heap(&mut self, heap_size: u64, heap_flags: u64) {
self.memory_heaps[self.memory_heap_count as usize].heap_size = heap_size;
self.memory_heaps[self.memory_heap_count as usize].heap_flags = heap_flags;
}
pub(crate) fn add_memory_type(&mut self, property_flags: u32) {
self.memory_types[self.memory_type_count as usize].property_flags = property_flags;
self.memory_types[self.memory_type_count as usize].heap_idx = self.memory_heap_count;
self.memory_type_count += 1;
}
pub(crate) fn get_memory_heap(&self, heap_idx: u32) -> &MagmaHeap {
&self.memory_heaps[heap_idx as usize]
}
pub(crate) fn get_memory_type(&self, memory_type_idx: u32) -> &MagmaMemoryType {
&self.memory_types[memory_type_idx as usize]
}
}
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaHeapBudget {
pub budget: u64,
pub usage: u64,
}
// Common allocation flags
// - MAGMA_BUFFER_FLAG_EXTERNAL: The buffer *may* be exported as an OS-specific handle
// - MAGMA_BUFFER_FLAG_SCANOUT: The buffer *may* be used by the scanout engine directly
pub const MAGMA_BUFFER_FLAG_EXTERNAL: u32 = 0x000000001;
pub const MAGMA_BUFFER_FLAG_SCANOUT: u32 = 0x000000002;
// Acceptable buffer vendor flags if the vendor is AMD:
// - MAGMA_BUFFER_FLAG_AMD_FLAG_OA: Ordered append, used by 3D/Compute engines
// - MAGMA_BUFFER_FLAG_AMD_FLAG_GDS: Global on-chip data storage. Used to share
// data across shader threads
pub const MAGMA_BUFFER_FLAG_AMD_OA: u32 = 0x000000001;
pub const MAGMA_BUFFER_FLAG_AMD_GDS: u32 = 0x000000002;
pub const MAGMA_SYNC_WHOLE_RANGE: u64 = 1 << 0;
pub const MAGMA_SYNC_RANGES: u64 = 1 << 1;
pub const MAGMA_SYNC_INVALIDATE_READ: u64 = 1 << 2;
pub const MAGMA_SYNC_INVALIDATE_WRITE: u64 = 1 << 3;
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaMappedMemoryRange {
pub offset: u64,
pub size: u64,
}
#[repr(C)]
#[derive(Clone, Default, Debug, IntoBytes, FromBytes)]
pub struct MagmaCreateBufferInfo {
pub memory_type_idx: u32,
pub alignment: u32,
pub common_flags: u32,
pub vendor_flags: u32,
pub size: u64,
}
// Same as PCI id
pub const MAGMA_VENDOR_ID_INTEL: u16 = 0x8086;
pub const MAGMA_VENDOR_ID_AMD: u16 = 0x1002;
pub const MAGMA_VENDOR_ID_MALI: u16 = 0x13B5;
pub const MAGMA_VENDOR_ID_QCOM: u16 = 0x5413;
use mesa3d_util::MesaHandle;
pub struct MagmaImportHandleInfo {
pub handle: MesaHandle,
pub size: u64,
pub memory_type_idx: u32,
}

103
src/magma/magma_kumquat.rs Normal file
View file

@ -0,0 +1,103 @@
// Copyright 2025 Android Open Source Project
// SPDX-License-Identifier: MIT
use std::sync::Arc;
use mesa3d_util::MesaError;
use mesa3d_util::MesaResult;
use virtgpu_kumquat::VirtGpuKumquat;
use crate::magma::MagmaPhysicalDevice;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MagmaPciBusInfo;
use crate::magma_defines::MagmaPciInfo;
use crate::sys::platform::PlatformPhysicalDevice;
use crate::traits::AsVirtGpu;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericDevice;
use crate::traits::GenericPhysicalDevice;
use crate::traits::PhysicalDevice;
pub struct MagmaKumquat {
virtgpu: VirtGpuKumquat,
}
impl MagmaKumquat {
pub fn new() -> MesaResult<MagmaKumquat> {
Ok(MagmaKumquat {
virtgpu: VirtGpuKumquat::new("/tmp/kumquat-gpu-0")?,
})
}
}
impl AsVirtGpu for MagmaKumquat {
fn as_virtgpu(&self) -> Option<&VirtGpuKumquat> {
Some(&self.virtgpu)
}
}
impl PlatformPhysicalDevice for MagmaKumquat {}
impl PhysicalDevice for MagmaKumquat {}
impl GenericPhysicalDevice for MagmaKumquat {
fn create_device(
&self,
physical_device: &Arc<dyn PhysicalDevice>,
_pci_info: &MagmaPciInfo,
) -> MesaResult<Arc<dyn Device>> {
let _virtgpu = physical_device.as_virtgpu().unwrap();
Err(MesaError::Unsupported)
}
}
impl GenericDevice for MagmaKumquat {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Err(MesaError::Unsupported)
}
fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
Err(MesaError::Unsupported)
}
fn create_context(&self, _device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
Err(MesaError::Unsupported)
}
fn create_buffer(
&self,
_device: &Arc<dyn Device>,
_create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
Err(MesaError::Unsupported)
}
fn import(
&self,
_device: &Arc<dyn Device>,
_info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
Err(MesaError::Unsupported)
}
}
pub fn enumerate_devices() -> MesaResult<Vec<MagmaPhysicalDevice>> {
let pci_info: MagmaPciInfo = Default::default();
let pci_bus_info: MagmaPciBusInfo = Default::default();
let mut devices: Vec<MagmaPhysicalDevice> = Vec::new();
let enc = MagmaKumquat::new()?;
// TODO): Get data from the server
devices.push(MagmaPhysicalDevice::new(
Arc::new(enc),
pci_info,
pci_bus_info,
));
Ok(devices)
}

169
src/magma/meson.build Normal file
View file

@ -0,0 +1,169 @@
# Copyright © 2024 Google
# SPDX-License-Identifier: MIT
magma_generated_libs = []
dep_log = dependency('log',
version: '>= 0.4.22',
fallback: ['log-0.4-rs', 'dep_log'],
required: true,
)
dep_windows_sys = null_dep
if host_machine.system() == 'linux' or host_machine.system() == 'android'
drm_bindgen = rust.bindgen(
input : drm_h,
output : 'mesa3d_magma_drm_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_.+',
'--allowlist-var', 'drm_.+',
'--allowlist-type', 'drm_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
],
)
amdgpu_bindgen = rust.bindgen(
input : amdgpu_drm_h,
output : 'mesa3d_magma_amdgpu_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_AMDGPU_.+',
'--allowlist-var', 'AMDGPU_.+',
'--allowlist-type', 'drm_amdgpu_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
],
)
msm_bindgen = rust.bindgen(
input : msm_drm_h,
output : 'mesa3d_magma_msm_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_MSM_.+',
'--allowlist-var', 'MSM_.+',
'--allowlist-type', 'drm_msm_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
],
)
virtgpu_bindgen = rust.bindgen(
input : virtgpu_drm_h,
output : 'mesa3d_magma_virtgpu_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_VIRTGPU_.+',
'--allowlist-var', 'VIRTGPU_.+',
'--allowlist-type', 'drm_virtgpu_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
]
)
xe_bindgen = rust.bindgen(
input : xe_drm_h,
output : 'mesa3d_magma_xe_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_XE.+',
'--allowlist-var', 'XE_.+',
'--allowlist-type', 'drm_xe_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
],
)
i915_bindgen = rust.bindgen(
input : i915_drm_h,
output : 'mesa3d_magma_i915_bindgen.rs',
include_directories : [inc_include],
args : [
'--with-derive-default',
'--allowlist-var', 'DRM_I915.+',
'--allowlist-var', 'I915_.+',
'--allowlist-type', 'drm_i915_.+',
'--no-prepend-enum-name',
'--no-doc-comments',
'--no-layout-tests'
],
)
drm_bindgen_gen = static_library(
'mesa3d_magma_drm_bindgen',
drm_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
amdgpu_bindgen_gen = static_library(
'mesa3d_magma_amdgpu_bindgen',
amdgpu_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
msm_bindgen_gen = static_library(
'mesa3d_magma_msm_bindgen',
msm_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
virtgpu_bindgen_gen = static_library(
'mesa3d_magma_virtgpu_bindgen',
virtgpu_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
i915_bindgen_gen = static_library(
'mesa3d_magma_i915_bindgen',
i915_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
xe_bindgen_gen = static_library(
'mesa3d_magma_xe_bindgen',
xe_bindgen,
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
)
magma_generated_libs += [drm_bindgen_gen, amdgpu_bindgen_gen,
msm_bindgen_gen, virtgpu_bindgen_gen,
i915_bindgen_gen, xe_bindgen_gen]
elif host_machine.system() == 'windows'
dep_windows_sys = dependency('windows-sys-rs',
version: '>= 0.61.1',
fallback: ['windows-sys-0.6-rs', 'dep_windows_sys'],
required: true,
)
endif
magma_args = []
magma_args += ['--cfg', 'avoid_cargo']
libmesa_magma = static_library(
'mesa3d_magma',
'lib.rs',
gnu_symbol_visibility : 'hidden',
rust_abi : 'rust',
rust_args: magma_args,
link_with: [magma_generated_libs, libmesa_rust_util, libmesa_protocols,
libvirtgpu_kumquat],
dependencies: [dep_mesa3d_util, dep_log, dep_windows_sys]
)

View file

@ -0,0 +1,438 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::os::fd::BorrowedFd;
use std::sync::Arc;
use log::error;
use mesa3d_util::log_status;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use crate::ioctl_readwrite;
use crate::ioctl_write_ptr;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MAGMA_BUFFER_FLAG_AMD_GDS;
use crate::magma_defines::MAGMA_BUFFER_FLAG_AMD_OA;
use crate::magma_defines::MAGMA_HEAP_CPU_VISIBLE_BIT;
use crate::magma_defines::MAGMA_HEAP_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
use crate::sys::linux::bindings::amdgpu_bindings::*;
use crate::sys::linux::bindings::drm_bindings::DRM_COMMAND_BASE;
use crate::sys::linux::bindings::drm_bindings::DRM_IOCTL_BASE;
use crate::sys::linux::PlatformDevice;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericBuffer;
use crate::traits::GenericDevice;
use crate::traits::PhysicalDevice;
ioctl_readwrite!(
drm_ioctl_amdgpu_ctx,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_AMDGPU_CTX,
drm_amdgpu_ctx
);
ioctl_write_ptr!(
drm_ioctl_amdgpu_info,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_AMDGPU_INFO,
drm_amdgpu_info
);
macro_rules! amdgpu_info_ioctl {
($(#[$attr:meta])* $name:ident, $nr:expr, $ty:ty) => (
$(#[$attr])*
pub unsafe fn $name(fd: BorrowedFd<'_>,
data: *mut $ty)
-> MesaResult<()> {
let mut info: drm_amdgpu_info = Default::default();
info.query = $nr;
info.return_size = ::std::mem::size_of::<$ty>() as u32;
info.return_pointer = data as __u64;
drm_ioctl_amdgpu_info(fd, &info)?;
Ok(())
}
)
}
amdgpu_info_ioctl!(
drm_ioctl_amdgpu_info_memory,
AMDGPU_INFO_MEMORY,
drm_amdgpu_memory_info
);
amdgpu_info_ioctl!(
drm_ioctl_amdgpu_info_vram_gtt,
AMDGPU_INFO_VRAM_GTT,
drm_amdgpu_info_vram_gtt
);
amdgpu_info_ioctl!(drm_ioctl_amdgpu_info_gtt_usage, AMDGPU_INFO_GTT_USAGE, u64);
amdgpu_info_ioctl!(
drm_ioctl_amdgpu_info_vram_usage,
AMDGPU_INFO_VRAM_USAGE,
u64
);
amdgpu_info_ioctl!(
drm_ioctl_amdgpu_info_vis_vram_usage,
AMDGPU_INFO_VIS_VRAM_USAGE,
u64
);
ioctl_readwrite!(
drm_ioctl_amdgpu_gem_create,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_AMDGPU_GEM_CREATE,
drm_amdgpu_gem_create
);
ioctl_readwrite!(
drm_ioctl_amdgpu_gem_mmap,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_AMDGPU_GEM_MMAP,
drm_amdgpu_gem_mmap
);
pub struct AmdGpu {
physical_device: Arc<dyn PhysicalDevice>,
mem_props: MagmaMemoryProperties,
}
struct AmdGpuContext {
physical_device: Arc<dyn PhysicalDevice>,
context_id: u32,
}
struct AmdGpuBuffer {
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
}
impl AmdGpu {
pub fn new(physical_device: Arc<dyn PhysicalDevice>) -> MesaResult<AmdGpu> {
let mut mem_props: MagmaMemoryProperties = Default::default();
let mut memory_info: drm_amdgpu_memory_info = Default::default();
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_memory_info struct
unsafe {
drm_ioctl_amdgpu_info_memory(physical_device.as_fd().unwrap(), &mut memory_info)?;
};
if memory_info.gtt.total_heap_size > 0 {
mem_props.add_heap(memory_info.gtt.total_heap_size, MAGMA_HEAP_CPU_VISIBLE_BIT);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT | MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT,
);
mem_props.increment_heap_count();
}
if memory_info.vram.total_heap_size > 0 {
mem_props.add_heap(
memory_info.vram.total_heap_size,
MAGMA_HEAP_DEVICE_LOCAL_BIT,
);
mem_props.add_memory_type(MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
mem_props.increment_heap_count();
}
if memory_info.cpu_accessible_vram.total_heap_size > 0 {
mem_props.add_heap(
memory_info.cpu_accessible_vram.total_heap_size,
MAGMA_HEAP_DEVICE_LOCAL_BIT | MAGMA_HEAP_CPU_VISIBLE_BIT,
);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
| MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.increment_heap_count();
}
Ok(AmdGpu {
physical_device,
mem_props,
})
}
}
impl GenericDevice for AmdGpu {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Ok(self.mem_props.clone())
}
fn get_memory_budget(&self, heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
if heap_idx >= self.mem_props.memory_heap_count {
return Err(MesaError::WithContext("Heap Index out of bounds"));
}
let mut vram_gtt: drm_amdgpu_info_vram_gtt = Default::default();
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_memory_info_vram_gtt struct
unsafe {
drm_ioctl_amdgpu_info_vram_gtt(self.physical_device.as_fd().unwrap(), &mut vram_gtt)?;
};
let budget: u64;
let mut usage: u64 = 0;
let heap = &self.mem_props.memory_heaps[heap_idx as usize];
if heap.is_device_local() && heap.is_cpu_visible() {
budget = vram_gtt.vram_cpu_accessible_size;
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - usage
unsafe {
drm_ioctl_amdgpu_info_vis_vram_usage(
self.physical_device.as_fd().unwrap(),
&mut usage,
)?;
};
} else if heap.is_device_local() {
budget = vram_gtt.vram_size;
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - usage
unsafe {
drm_ioctl_amdgpu_info_vram_usage(
self.physical_device.as_fd().unwrap(),
&mut usage,
)?;
};
} else if heap.is_cpu_visible() {
budget = vram_gtt.gtt_size;
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - usage
unsafe {
drm_ioctl_amdgpu_info_gtt_usage(self.physical_device.as_fd().unwrap(), &mut usage)?;
};
} else {
return Err(MesaError::Unsupported);
}
Ok(MagmaHeapBudget { budget, usage })
}
fn create_context(&self, _device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
let ctx = AmdGpuContext::new(self.physical_device.clone(), 0)?;
Ok(Arc::new(ctx))
}
fn create_buffer(
&self,
_device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let buf = AmdGpuBuffer::new(self.physical_device.clone(), create_info, &self.mem_props)?;
Ok(Arc::new(buf))
}
fn import(
&self,
_device: &Arc<dyn Device>,
info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let gem_handle = self.physical_device.import(info.handle)?;
let buf = AmdGpuBuffer::from_existing(
self.physical_device.clone(),
gem_handle,
info.size.try_into()?,
)?;
Ok(Arc::new(buf))
}
}
impl Device for AmdGpu {}
impl PlatformDevice for AmdGpu {}
impl AmdGpuContext {
fn new(physical_device: Arc<dyn PhysicalDevice>, _priority: i32) -> MesaResult<AmdGpuContext> {
let mut ctx_arg = drm_amdgpu_ctx::default();
ctx_arg.in_.op = AMDGPU_CTX_OP_ALLOC_CTX;
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_ctx struct
let context_id: u32 = unsafe {
drm_ioctl_amdgpu_ctx(physical_device.as_fd().unwrap(), &mut ctx_arg)?;
ctx_arg.out.alloc.ctx_id
};
Ok(AmdGpuContext {
physical_device,
context_id,
})
}
}
impl Drop for AmdGpuContext {
fn drop(&mut self) {
let mut ctx_arg = drm_amdgpu_ctx::default();
ctx_arg.in_.op = AMDGPU_CTX_OP_FREE_CTX;
ctx_arg.in_.ctx_id = self.context_id;
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_ctx struct
let result =
unsafe { drm_ioctl_amdgpu_ctx(self.physical_device.as_fd().unwrap(), &mut ctx_arg) };
log_status!(result);
}
}
impl Context for AmdGpuContext {}
impl AmdGpuBuffer {
fn new(
physical_device: Arc<dyn PhysicalDevice>,
create_info: &MagmaCreateBufferInfo,
mem_props: &MagmaMemoryProperties,
) -> MesaResult<AmdGpuBuffer> {
let mut gem_create_in: drm_amdgpu_gem_create_in = Default::default();
let mut gem_create: drm_amdgpu_gem_create = Default::default();
let memory_type = mem_props.get_memory_type(create_info.memory_type_idx);
gem_create_in.bo_size = create_info.size;
// FIXME: gpu_info.pte_fragment_size, alignment
// Need GPU topology crate
gem_create_in.alignment = create_info.alignment as u64;
// Goal: An explicit sync world + discardable world only.
gem_create_in.domain_flags |= AMDGPU_GEM_CREATE_EXPLICIT_SYNC as u64;
gem_create_in.domain_flags |= AMDGPU_GEM_CREATE_DISCARDABLE as u64;
if memory_type.is_coherent() {
gem_create_in.domain_flags |= AMDGPU_GEM_CREATE_CPU_GTT_USWC as u64;
} else {
gem_create_in.domain_flags |= AMDGPU_GEM_CREATE_NO_CPU_ACCESS as u64;
}
if memory_type.is_protected() {
gem_create_in.domain_flags |= AMDGPU_GEM_CREATE_ENCRYPTED as u64;
}
// Should these be "heaps" of zero size?
if create_info.vendor_flags & MAGMA_BUFFER_FLAG_AMD_OA != 0 {
gem_create_in.domains |= AMDGPU_GEM_DOMAIN_OA as u64
} else if create_info.vendor_flags & MAGMA_BUFFER_FLAG_AMD_GDS != 0 {
gem_create_in.domains |= AMDGPU_GEM_DOMAIN_GDS as u64;
} else if memory_type.is_device_local() {
gem_create_in.domains |= AMDGPU_GEM_DOMAIN_VRAM as u64;
} else {
gem_create_in.domains |= AMDGPU_GEM_DOMAIN_GTT as u64;
}
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_gem_create_args
let gem_handle = unsafe {
gem_create.in_ = gem_create_in;
drm_ioctl_amdgpu_gem_create(physical_device.as_fd().unwrap(), &mut gem_create)?;
gem_create.out.handle
};
Ok(AmdGpuBuffer {
physical_device,
gem_handle,
size: create_info.size.try_into()?,
})
}
fn from_existing(
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
) -> MesaResult<AmdGpuBuffer> {
Ok(AmdGpuBuffer {
physical_device,
gem_handle,
size,
})
}
}
impl GenericBuffer for AmdGpuBuffer {
fn map(&self, _buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>> {
let mut gem_mmap: drm_amdgpu_gem_mmap = Default::default();
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_gem_mmap
let offset = unsafe {
gem_mmap.in_.handle = self.gem_handle;
drm_ioctl_amdgpu_gem_mmap(self.physical_device.as_fd().unwrap(), &mut gem_mmap)?;
gem_mmap.out.addr_ptr
};
let mapping = self.physical_device.cpu_map(offset, self.size)?;
Ok(Arc::new(mapping))
}
fn export(&self) -> MesaResult<MesaHandle> {
self.physical_device.export(self.gem_handle)
}
fn invalidate(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
fn flush(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
}
impl Drop for AmdGpuBuffer {
fn drop(&mut self) {
// GEM close
}
}
impl Buffer for AmdGpuBuffer {}
unsafe impl Send for AmdGpu {}
unsafe impl Sync for AmdGpu {}
unsafe impl Send for AmdGpuContext {}
unsafe impl Sync for AmdGpuContext {}
unsafe impl Send for AmdGpuBuffer {}
unsafe impl Sync for AmdGpuBuffer {}

View file

@ -0,0 +1,14 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(unused_imports)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#[cfg(avoid_cargo)]
pub use mesa3d_magma_amdgpu_bindgen::*;
#[cfg(not(avoid_cargo))]
include!(concat!(env!("OUT_DIR"), "/mesa3d_magma_amdgpu_bindgen.rs"));

View file

@ -0,0 +1,13 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(non_upper_case_globals)]
#![allow(unused_imports)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#[cfg(avoid_cargo)]
pub use mesa3d_magma_drm_bindgen::*;
#[cfg(not(avoid_cargo))]
include!(concat!(env!("OUT_DIR"), "/mesa3d_magma_drm_bindgen.rs"));

View file

@ -0,0 +1,12 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#[cfg(not(use_meson))]
include!(concat!(env!("OUT_DIR"), "/i915_bindings.rs"));
#[cfg(use_meson)]
pub use i915_bindings::*;

View file

@ -0,0 +1,14 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#[cfg(avoid_cargo)]
pub use mesa3d_magma_i915_bindgen::*;
#[cfg(not(avoid_cargo))]
include!(concat!(env!("OUT_DIR"), "/mesa3d_magma_i915_bindgen.rs"));

View file

@ -0,0 +1,8 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
pub mod amdgpu_bindings;
pub mod drm_bindings;
pub mod i915_bindings;
pub mod msm_bindings;
pub mod xe_bindings;

View file

@ -0,0 +1,12 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#[cfg(avoid_cargo)]
pub use mesa3d_magma_msm_bindgen::*;
#[cfg(not(avoid_cargo))]
include!(concat!(env!("OUT_DIR"), "/mesa3d_magma_msm_bindgen.rs"));

View file

@ -0,0 +1,12 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(non_camel_case_types)]
#[cfg(avoid_cargo)]
pub use mesa3d_magma_xe_bindgen::*;
#[cfg(not(avoid_cargo))]
include!(concat!(env!("OUT_DIR"), "/mesa3d_magma_xe_bindgen.rs"));

View file

@ -0,0 +1,301 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Read;
use std::os::fd::AsFd;
use std::os::fd::BorrowedFd;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
use log::error;
use mesa3d_util::log_status;
use mesa3d_util::AsRawDescriptor;
use mesa3d_util::FromRawDescriptor;
use mesa3d_util::MemoryMapping;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use mesa3d_util::OwnedDescriptor;
use mesa3d_util::RawDescriptor;
use mesa3d_util::MESA_HANDLE_TYPE_MEM_DMABUF;
use rustix::fs::major;
use rustix::fs::minor;
use rustix::fs::open;
use rustix::fs::readlink;
use rustix::fs::stat;
use rustix::fs::Dir;
use rustix::fs::Mode;
use rustix::fs::OFlags;
use libc::O_CLOEXEC;
use libc::O_RDWR;
use crate::magma::MagmaPhysicalDevice;
use crate::magma_defines::MagmaPciBusInfo;
use crate::magma_defines::MagmaPciInfo;
use crate::magma_defines::MAGMA_VENDOR_ID_AMD;
use crate::magma_defines::MAGMA_VENDOR_ID_INTEL;
use crate::magma_defines::MAGMA_VENDOR_ID_QCOM;
use crate::sys::linux::bindings::drm_bindings::drm_gem_close;
use crate::sys::linux::bindings::drm_bindings::drm_prime_handle;
use crate::sys::linux::drm_ioctl_gem_close;
use crate::sys::linux::drm_ioctl_prime_fd_to_handle;
use crate::sys::linux::drm_ioctl_prime_handle_to_fd;
use crate::sys::linux::get_drm_device_name;
use crate::sys::linux::AmdGpu;
use crate::sys::linux::Msm;
use crate::sys::linux::Xe;
use crate::sys::linux::DRM_DIR_NAME;
use crate::sys::linux::DRM_RENDER_MINOR_NAME;
use crate::sys::linux::I915;
use crate::traits::AsVirtGpu;
use crate::traits::Device;
use crate::traits::GenericPhysicalDevice;
use crate::traits::PhysicalDevice;
const PCI_ATTRS: [&str; 5] = [
"revision",
"vendor",
"device",
"subsystem_vendor",
"subsystem_device",
];
#[derive(Debug)]
pub struct LinuxPhysicalDevice {
descriptor: OwnedDescriptor,
name: String,
}
#[allow(dead_code)]
pub trait PlatformPhysicalDevice {
fn as_fd(&self) -> Option<BorrowedFd<'_>> {
None
}
fn as_raw_descriptor(&self) -> RawDescriptor {
-1
}
fn cpu_map(&self, _offset: u64, _size: usize) -> MesaResult<MemoryMapping> {
Err(MesaError::Unsupported)
}
fn export(&self, _gem_handle: u32) -> MesaResult<MesaHandle> {
Err(MesaError::Unsupported)
}
fn import(&self, _handle: MesaHandle) -> MesaResult<u32> {
Err(MesaError::Unsupported)
}
fn close(&self, _gem_handle: u32) {}
}
impl GenericPhysicalDevice for LinuxPhysicalDevice {
fn create_device(
&self,
physical_device: &Arc<dyn PhysicalDevice>,
pci_info: &MagmaPciInfo,
) -> MesaResult<Arc<dyn Device>> {
let device: Arc<dyn Device> = match pci_info.vendor_id {
MAGMA_VENDOR_ID_AMD => Arc::new(AmdGpu::new(physical_device.clone())?),
MAGMA_VENDOR_ID_QCOM => Arc::new(Msm::new(physical_device.clone())),
MAGMA_VENDOR_ID_INTEL => {
if self.name == "xe" {
Arc::new(Xe::new(physical_device.clone(), pci_info)?)
} else {
Arc::new(I915::new(physical_device.clone())?)
}
}
_ => todo!(),
};
Ok(device)
}
}
pub trait PlatformDevice {}
impl LinuxPhysicalDevice {
pub fn new(device_node: PathBuf) -> MesaResult<LinuxPhysicalDevice> {
let descriptor: OwnedDescriptor = OpenOptions::new()
.read(true)
.write(true)
.open(device_node.clone())?
.into();
// TODO: confirm if necessary if everything has PCI-ID
let name = get_drm_device_name(&descriptor)?;
println!("the name is {}", name);
Ok(LinuxPhysicalDevice { descriptor, name })
}
}
impl PlatformPhysicalDevice for LinuxPhysicalDevice {
fn as_fd(&self) -> Option<BorrowedFd<'_>> {
Some(self.descriptor.as_fd())
}
fn as_raw_descriptor(&self) -> RawDescriptor {
self.descriptor.as_raw_descriptor()
}
fn cpu_map(&self, offset: u64, size: usize) -> MesaResult<MemoryMapping> {
MemoryMapping::from_offset(&self.descriptor, offset.try_into()?, size)
}
fn export(&self, gem_handle: u32) -> MesaResult<MesaHandle> {
let mut arg: drm_prime_handle = drm_prime_handle {
handle: gem_handle,
flags: (O_CLOEXEC | O_RDWR) as u32,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_prime_handle
let fd = unsafe {
drm_ioctl_prime_handle_to_fd(self.descriptor.as_fd(), &mut arg)?;
arg.fd
};
// SAFETY:
// `fd` is valid after a successful PRIME_HANDLE_TO_HANDLE syscall.
let descriptor = unsafe { OwnedDescriptor::from_raw_descriptor(fd) };
Ok(MesaHandle {
os_handle: descriptor,
handle_type: MESA_HANDLE_TYPE_MEM_DMABUF,
})
}
fn import(&self, handle: MesaHandle) -> MesaResult<u32> {
let mut arg: drm_prime_handle = drm_prime_handle {
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_prime_handle
let handle = unsafe {
arg.fd = handle.os_handle.as_raw_descriptor();
drm_ioctl_prime_fd_to_handle(self.descriptor.as_fd(), &mut arg)?;
arg.handle
};
Ok(handle)
}
fn close(&self, gem_handle: u32) {
let arg: drm_gem_close = drm_gem_close {
handle: gem_handle,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_gem_handle
let result = unsafe { drm_ioctl_gem_close(self.descriptor.as_fd(), &arg) };
log_status!(result);
}
}
impl AsVirtGpu for LinuxPhysicalDevice {}
impl PhysicalDevice for LinuxPhysicalDevice {}
// Helper function to parse hexadecimal string to u16
fn parse_hex_u16(s: &str) -> MesaResult<u16> {
let valid_str = s.trim().strip_prefix("0x").unwrap_or(s.trim());
Ok(u16::from_str_radix(valid_str, 16)?)
}
pub fn enumerate_devices() -> MesaResult<Vec<MagmaPhysicalDevice>> {
let mut devices: Vec<MagmaPhysicalDevice> = Vec::new();
let dir_fd = open(
DRM_DIR_NAME,
OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
Mode::empty(),
)?;
let dir = Dir::new(dir_fd)?;
for entry in dir.flatten() {
let filename = entry.file_name().to_str()?;
if filename.contains(DRM_RENDER_MINOR_NAME) {
let path = Path::new(DRM_DIR_NAME).join(filename);
let statbuf = stat(&path)?;
let maj = major(statbuf.st_rdev);
let min = minor(statbuf.st_rdev);
let pci_device_dir = format!("/sys/dev/char/{}:{}/device", maj, min);
let pci_subsystem_dir = format!("{}/subsystem", pci_device_dir);
let subsystem_path = Path::new(&pci_subsystem_dir);
let subsystem = readlink(subsystem_path, Vec::new())?;
// If not valid UTF-8, assume not PCI
let is_pci_subsystem = subsystem
.to_str()
.map(|s| s.contains("/pci"))
.unwrap_or(false);
if !is_pci_subsystem {
continue;
}
let mut pci_info: MagmaPciInfo = Default::default();
let mut pci_bus_info: MagmaPciBusInfo = Default::default();
for attr in PCI_ATTRS {
let attr_path = format!("{}/{}", pci_device_dir, attr);
let mut file = File::open(attr_path)?;
let mut hex_string = String::new();
file.read_to_string(&mut hex_string)?;
match attr {
"revision" => pci_info.revision_id = parse_hex_u16(&hex_string)?.try_into()?,
"vendor" => pci_info.vendor_id = parse_hex_u16(&hex_string)?,
"device" => pci_info.device_id = parse_hex_u16(&hex_string)?,
"subsystem_vendor" => pci_info.subvendor_id = parse_hex_u16(&hex_string)?,
"subsystem_device" => pci_info.subdevice_id = parse_hex_u16(&hex_string)?,
_ => unimplemented!(),
}
}
let uevent_path = format!("{}/uevent", pci_device_dir);
let text: String = fs::read_to_string(uevent_path)?;
for line in text.lines() {
if line.contains("PCI_SLOT_NAME") {
let v: Vec<&str> = line.split(&['=', ':', '.'][..]).collect();
pci_bus_info.domain = v[1].parse::<u16>()?;
pci_bus_info.bus = v[2].parse::<u8>()?;
pci_bus_info.device = v[3].parse::<u8>()?;
pci_bus_info.function = v[4].parse::<u8>()?;
}
}
devices.push(MagmaPhysicalDevice::new(
Arc::new(LinuxPhysicalDevice::new(path.to_path_buf())?),
pci_info,
pci_bus_info,
));
}
}
Ok(devices)
}
unsafe impl Send for LinuxPhysicalDevice {}
unsafe impl Sync for LinuxPhysicalDevice {}

View file

@ -0,0 +1,92 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::ffi::CString;
use std::os::fd::AsFd;
use std::os::raw::c_char;
use std::os::raw::c_uint;
use std::ptr::null_mut;
use mesa3d_util::MesaError;
use mesa3d_util::MesaResult;
use mesa3d_util::OwnedDescriptor;
use crate::ioctl_readwrite;
use crate::ioctl_write_ptr;
use crate::sys::linux::bindings::drm_bindings::__kernel_size_t;
use crate::sys::linux::bindings::drm_bindings::drm_gem_close;
use crate::sys::linux::bindings::drm_bindings::drm_prime_handle;
use crate::sys::linux::bindings::drm_bindings::drm_version;
use crate::sys::linux::bindings::drm_bindings::DRM_IOCTL_BASE;
pub const DRM_DIR_NAME: &str = "/dev/dri";
pub const DRM_RENDER_MINOR_NAME: &str = "renderD";
const DRM_IOCTL_VERSION: c_uint = 0x00;
ioctl_readwrite!(
drm_get_version,
DRM_IOCTL_BASE,
DRM_IOCTL_VERSION,
drm_version
);
ioctl_readwrite!(
drm_ioctl_prime_handle_to_fd,
DRM_IOCTL_BASE,
0x2d,
drm_prime_handle
);
ioctl_readwrite!(
drm_ioctl_prime_fd_to_handle,
DRM_IOCTL_BASE,
0x2e,
drm_prime_handle
);
ioctl_write_ptr!(drm_ioctl_gem_close, DRM_IOCTL_BASE, 0x09, drm_gem_close);
pub fn get_drm_device_name(descriptor: &OwnedDescriptor) -> MesaResult<String> {
let mut version = drm_version {
version_major: 0,
version_minor: 0,
version_patchlevel: 0,
name_len: 0,
name: null_mut(),
date_len: 0,
date: null_mut(),
desc_len: 0,
desc: null_mut(),
};
// SAFETY:
// Descriptor is valid and borrowed properly..
unsafe {
drm_get_version(descriptor.as_fd(), &mut version)?;
}
// Enough bytes to hold the device name and terminating null character.
let mut name_bytes: Vec<u8> = vec![0; (version.name_len + 1) as usize];
let mut version = drm_version {
version_major: 0,
version_minor: 0,
version_patchlevel: 0,
name_len: name_bytes.len() as __kernel_size_t,
name: name_bytes.as_mut_ptr() as *mut c_char,
date_len: 0,
date: null_mut(),
desc_len: 0,
desc: null_mut(),
};
// SAFETY:
// No more than name_len + 1 bytes will be written to name.
unsafe {
drm_get_version(descriptor.as_fd(), &mut version)?;
}
CString::new(&name_bytes[..(version.name_len as usize)])?
.into_string()
.map_err(|_| MesaError::WithContext("couldn't convert string"))
}

View file

@ -0,0 +1,162 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
//! A wrapper for structures that contain flexible arrays.
//!
//! The following code provides generic helpers for creating and accessing flexible array structs.
//! A complete definition of flexible array structs is found in the ISO 9899 specification
//! <http://www.iso-9899.info/n1570.html>. A flexible array struct is of the form:
//!
//! ```ignore
//! #[repr(C)]
//! struct T {
//! some_data: u32,
//! nents: u32,
//! entries: __IncompleteArrayField<S>,
//! }
//! ```
//! where:
//!
//! - `T` is the flexible array struct type
//! - `S` is the flexible array type
//! - `nents` is the flexible array length
//! - `entries` is the flexible array member
//!
//! These structures are used by the kernel API.
use std::marker::PhantomData;
use std::mem::size_of;
// Returns a `Vec<T>` with a size in bytes at least as large as `size_in_bytes`.
fn vec_with_size_in_bytes<T: Default>(size_in_bytes: usize) -> Vec<T> {
let rounded_size = size_in_bytes.div_ceil(size_of::<T>());
let mut v = Vec::with_capacity(rounded_size);
v.resize_with(rounded_size, T::default);
v
}
/// The kernel API has many structs that resemble the following `Foo` structure:
///
/// ```ignore
/// #[repr(C)]
/// struct Foo {
/// some_data: u32,
/// entries: __IncompleteArrayField<__u32>,
/// }
/// ```
///
/// In order to allocate such a structure, `size_of::<Foo>()` would be too small because it would
/// not include any space for `entries`. To make the allocation large enough while still being
/// aligned for `Foo`, a `Vec<Foo>` is created. Only the first element of `Vec<Foo>` would actually
/// be used as a `Foo`. The remaining memory in the `Vec<Foo>` is for `entries`, which must be
/// contiguous with `Foo`. This function is used to make the `Vec<Foo>` with enough space for
/// `count` entries.
fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
let element_space = count * size_of::<F>();
let vec_size_bytes = size_of::<T>() + element_space;
vec_with_size_in_bytes(vec_size_bytes)
}
/// A collection of methods that are required by the FlexibleArrayWrapper type.
///
/// When implemented for `T`, this trait allows the caller to set number of `S` entries and
/// retrieve a slice of `S` entries. Trait methods must only be called by the FlexibleArrayWrapper
/// type. Don't implement this trait directly, use the flexible_array! macro to avoid duplication.
pub trait FlexibleArray<S> {
/// Implementations must set flexible array length in the flexible array struct to the value
/// specified by `len`. Appropriate conversions (i.e, usize to u32) are allowed so long as
/// they don't overflow or underflow.
fn set_len(&mut self, len: usize);
/// Implementations must return the length of the flexible array member. Appropriate
/// conversions (i.e, usize to u32) are allowed so long as they don't overflow or underflow.
fn get_len(&self) -> usize;
/// Implementations must return a slice of flexible array member of length `len`.
/// # Safety
/// Do not use this function directly, as the FlexibleArrayWrapper will guarantee safety.
unsafe fn get_slice(&self, len: usize) -> &[S];
}
/// Always use this macro for implementing the FlexibleArray<`S`> trait for a given `T`. There
/// exists an 1:1 mapping of macro identifiers to the definitions in the FlexibleArray<`S`>
/// documentation, so refer to that for more information.
#[macro_export]
macro_rules! flexible_array_impl {
($T:ident, $S:ident, $nents:ident, $entries:ident) => {
impl FlexibleArray<$S> for $T {
fn set_len(&mut self, len: usize) {
self.$nents = ::std::convert::TryInto::try_into(len).unwrap();
}
fn get_len(&self) -> usize {
self.$nents as usize
}
unsafe fn get_slice(&self, len: usize) -> &[$S] {
self.$entries.as_slice(len)
}
}
};
}
pub struct FlexibleArrayWrapper<T, S> {
entries: Vec<T>,
phantom: PhantomData<S>,
allocated_len: usize,
}
/// Convenience wrapper for flexible array structs.
///
/// The FlexibleArray trait must be implemented for the flexible array struct before using this
/// wrapper.
impl<T, S> FlexibleArrayWrapper<T, S>
where
T: FlexibleArray<S> + Default,
{
/// Creates a new FlexibleArrayWrapper for the given flexible array struct type and flexible
/// array type. The flexible array length is set to `array_len`. vec_with_array_field is used
/// to make sure the resultant wrapper is appropriately sized.
pub fn from_array_len(array_len: usize) -> FlexibleArrayWrapper<T, S> {
let mut entries = vec_with_array_field::<T, S>(array_len);
entries[0].set_len(array_len);
FlexibleArrayWrapper {
entries,
phantom: PhantomData,
allocated_len: array_len,
}
}
/// Creates a new FlexibleArrayWrapper for the given flexible array struct type and flexible
/// array type. The flexible array length is inferred from `total_size`.
pub fn from_total_size(total_size: usize) -> FlexibleArrayWrapper<T, S> {
let array_size = total_size - size_of::<T>();
let num_elements = array_size.div_ceil(size_of::<S>());
FlexibleArrayWrapper::<T, S>::from_array_len(num_elements)
}
/// Mapping the unsized array to a slice is unsafe because the length isn't known. Using
/// the length we originally allocated with eliminates the possibility of overflow.
fn get_valid_len(&self) -> usize {
if self.entries[0].get_len() > self.allocated_len {
self.allocated_len
} else {
self.entries[0].get_len()
}
}
/// Returns a slice of the flexible array member, for inspecting. To modify, use
/// mut_entries_slice instead.
pub fn entries_slice(&self) -> &[S] {
let valid_length = self.get_valid_len();
// SAFETY:
// Safe because the length has been validated.
unsafe { self.entries[0].get_slice(valid_length) }
}
/// Get a mutable pointer so it can be passed to the kernel. Callers must not access the
/// flexible array member. Using this pointer is unsafe.
pub fn as_mut_ptr(&mut self) -> *mut T {
&mut self.entries[0]
}
}

488
src/magma/sys/linux/i915.rs Normal file
View file

@ -0,0 +1,488 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::sync::Arc;
use log::error;
use mesa3d_util::log_status;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use crate::flexible_array_impl;
use crate::ioctl_readwrite;
use crate::ioctl_write_ptr;
use crate::sys::linux::flexible_array::FlexibleArray;
use crate::sys::linux::flexible_array::FlexibleArrayWrapper;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MAGMA_HEAP_CPU_VISIBLE_BIT;
use crate::magma_defines::MAGMA_HEAP_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
use crate::sys::linux::bindings::drm_bindings::DRM_COMMAND_BASE;
use crate::sys::linux::bindings::drm_bindings::DRM_IOCTL_BASE;
use crate::sys::linux::bindings::i915_bindings::*;
use crate::sys::linux::PlatformDevice;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericBuffer;
use crate::traits::GenericDevice;
use crate::traits::PhysicalDevice;
ioctl_readwrite!(
drm_ioctl_i915_getparam,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_GETPARAM,
drm_i915_getparam
);
ioctl_readwrite!(
drm_ioctl_i915_query,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_QUERY,
drm_i915_query
);
ioctl_readwrite!(
drm_ioctl_i915_gem_create,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_GEM_CREATE,
drm_i915_gem_create
);
ioctl_readwrite!(
drm_ioctl_i915_gem_mmap_offset,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_GEM_MMAP_GTT,
drm_i915_gem_mmap_offset
);
ioctl_readwrite!(
drm_ioctl_i915_gem_context_create_ext,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_GEM_CONTEXT_CREATE,
drm_i915_gem_context_create_ext
);
ioctl_write_ptr!(
drm_ioctl_i915_gem_context_destroy,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_I915_GEM_CONTEXT_DESTROY,
drm_i915_gem_context_destroy
);
flexible_array_impl!(
drm_i915_query_memory_regions,
drm_i915_memory_region_info,
num_regions,
regions
);
fn i915_query<T, S>(
physical_device: &Arc<dyn PhysicalDevice>,
query_id: u64,
) -> MesaResult<FlexibleArrayWrapper<T, S>>
where
T: FlexibleArray<S> + Default,
{
let mut item = drm_i915_query_item {
query_id,
length: 0,
flags: 0,
data_ptr: 0,
};
let mut query = drm_i915_query {
num_items: 1,
flags: 0,
items_ptr: &mut item as *mut _ as u64,
};
// SAFETY: First call to get the size
unsafe {
drm_ioctl_i915_query(physical_device.as_fd().unwrap(), &mut query)?;
}
if item.length < 0 {
return Err(MesaError::from(std::io::Error::from_raw_os_error(
-item.length,
)));
}
let total_size = item.length as usize;
if total_size == 0 {
return Ok(FlexibleArrayWrapper::<T, S>::from_total_size(0));
}
let mut wrapper = FlexibleArrayWrapper::<T, S>::from_total_size(total_size);
item.data_ptr = wrapper.as_mut_ptr() as u64;
// SAFETY: Second call to get the data
unsafe {
drm_ioctl_i915_query(physical_device.as_fd().unwrap(), &mut query)?;
};
Ok(wrapper)
}
#[derive(Default)]
struct I915MemoryInfo {
sysmem_total: u64,
sysmem_free: u64,
vram_mappable_total: u64,
vram_mappable_free: u64,
vram_unmappable_total: u64,
vram_unmappable_free: u64,
}
fn i915_query_memory_regions(
physical_device: &Arc<dyn PhysicalDevice>,
) -> MesaResult<I915MemoryInfo> {
let query_mem_regions = i915_query::<drm_i915_query_memory_regions, drm_i915_memory_region_info>(
physical_device,
DRM_I915_QUERY_MEMORY_REGIONS as u64,
)?;
let regions = query_mem_regions.entries_slice();
let mut info = I915MemoryInfo::default();
for region in regions {
// SAFETY: Accessing a C union's fields is unsafe in Rust.
let (probed_cpu_visible_size, unallocated_cpu_visible_size) = unsafe {
(
region
.__bindgen_anon_1
.__bindgen_anon_1
.probed_cpu_visible_size,
region
.__bindgen_anon_1
.__bindgen_anon_1
.unallocated_cpu_visible_size,
)
};
match region.region.memory_class as u32 {
I915_MEMORY_CLASS_SYSTEM => {
info.sysmem_total = region.probed_size;
info.sysmem_free = region.unallocated_size;
}
I915_MEMORY_CLASS_DEVICE => {
if probed_cpu_visible_size > 0 {
info.vram_mappable_total = probed_cpu_visible_size;
info.vram_unmappable_total = region.probed_size - probed_cpu_visible_size;
if region.unallocated_size != u64::MAX {
info.vram_mappable_free = unallocated_cpu_visible_size;
info.vram_unmappable_free =
region.unallocated_size - unallocated_cpu_visible_size;
}
} else {
info.vram_mappable_total = region.probed_size;
info.vram_unmappable_total = 0;
if region.unallocated_size != u64::MAX {
info.vram_mappable_free = region.unallocated_size;
info.vram_unmappable_free = 0;
}
}
}
_ => {}
}
}
Ok(info)
}
pub struct I915 {
physical_device: Arc<dyn PhysicalDevice>,
mem_props: MagmaMemoryProperties,
}
struct I915Context {
physical_device: Arc<dyn PhysicalDevice>,
context_id: u32,
}
struct I915Buffer {
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
}
impl I915 {
pub fn new(physical_device: Arc<dyn PhysicalDevice>) -> MesaResult<I915> {
let mut val: i32 = 0;
let mut getparam = drm_i915_getparam {
param: I915_PARAM_HAS_ALIASING_PPGTT as i32,
value: &mut val as *mut _,
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_i915_getparam struct
unsafe {
drm_ioctl_i915_getparam(physical_device.as_fd().unwrap(), &mut getparam)?;
}
let mem_info = i915_query_memory_regions(&physical_device).unwrap_or_default();
let mut mem_props: MagmaMemoryProperties = Default::default();
if mem_info.sysmem_total > 0 {
mem_props.add_heap(mem_info.sysmem_total, MAGMA_HEAP_CPU_VISIBLE_BIT);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT,
);
mem_props.increment_heap_count();
}
if mem_info.vram_mappable_total > 0 {
mem_props.add_heap(
mem_info.vram_mappable_total,
MAGMA_HEAP_CPU_VISIBLE_BIT | MAGMA_HEAP_DEVICE_LOCAL_BIT,
);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.increment_heap_count();
}
if mem_info.vram_unmappable_total > 0 {
mem_props.add_heap(mem_info.vram_unmappable_total, MAGMA_HEAP_DEVICE_LOCAL_BIT);
mem_props.add_memory_type(MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
mem_props.increment_heap_count();
}
if mem_props.memory_heap_count == 0 {
// Fallback for older kernels
mem_props.add_heap(4 * 1024 * 1024 * 1024, MAGMA_HEAP_CPU_VISIBLE_BIT);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT,
);
mem_props.increment_heap_count();
}
Ok(I915 {
physical_device,
mem_props,
})
}
}
impl GenericDevice for I915 {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Ok(self.mem_props.clone())
}
fn get_memory_budget(&self, heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
if heap_idx >= self.mem_props.memory_heap_count {
return Err(MesaError::WithContext("Heap Index out of bounds"));
}
let mem_info = i915_query_memory_regions(&self.physical_device)?;
let heap = &self.mem_props.memory_heaps[heap_idx as usize];
let (budget, free) = if heap.is_cpu_visible() && !heap.is_device_local() {
(mem_info.sysmem_total, mem_info.sysmem_free)
} else if heap.is_cpu_visible() && heap.is_device_local() {
(mem_info.vram_mappable_total, mem_info.vram_mappable_free)
} else if !heap.is_cpu_visible() && heap.is_device_local() {
(
mem_info.vram_unmappable_total,
mem_info.vram_unmappable_free,
)
} else {
return Err(MesaError::Unsupported);
};
Ok(MagmaHeapBudget {
budget,
usage: budget - free,
})
}
fn create_context(&self, _device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
let ctx = I915Context::new(self.physical_device.clone())?;
Ok(Arc::new(ctx))
}
fn create_buffer(
&self,
_device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let buf = I915Buffer::new(self.physical_device.clone(), create_info)?;
Ok(Arc::new(buf))
}
fn import(
&self,
_device: &Arc<dyn Device>,
info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let gem_handle = self.physical_device.import(info.handle)?;
let buf = I915Buffer::from_existing(
self.physical_device.clone(),
gem_handle,
info.size.try_into()?,
)?;
Ok(Arc::new(buf))
}
}
impl Device for I915 {}
impl PlatformDevice for I915 {}
impl I915Context {
fn new(physical_device: Arc<dyn PhysicalDevice>) -> MesaResult<I915Context> {
let mut ctx_create = drm_i915_gem_context_create_ext::default();
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_i915_gem_context_create_ext struct
unsafe {
drm_ioctl_i915_gem_context_create_ext(
physical_device.as_fd().unwrap(),
&mut ctx_create,
)?;
};
Ok(I915Context {
physical_device,
context_id: ctx_create.ctx_id,
})
}
}
impl Drop for I915Context {
fn drop(&mut self) {
let ctx_destroy = drm_i915_gem_context_destroy {
ctx_id: self.context_id,
pad: 0,
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_i915_gem_context_destroy struct
let result = unsafe {
drm_ioctl_i915_gem_context_destroy(self.physical_device.as_fd().unwrap(), &ctx_destroy)
};
log_status!(result);
}
}
impl Context for I915Context {}
impl I915Buffer {
fn new(
physical_device: Arc<dyn PhysicalDevice>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<I915Buffer> {
let mut gem_create = drm_i915_gem_create {
size: create_info.size,
handle: 0,
pad: 0,
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_i915_gem_create struct
unsafe {
drm_ioctl_i915_gem_create(physical_device.as_fd().unwrap(), &mut gem_create)?;
};
Ok(I915Buffer {
physical_device,
gem_handle: gem_create.handle,
size: create_info.size.try_into()?,
})
}
fn from_existing(
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
) -> MesaResult<I915Buffer> {
Ok(I915Buffer {
physical_device,
gem_handle,
size,
})
}
}
impl GenericBuffer for I915Buffer {
fn map(&self, _buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>> {
let mut gem_mmap = drm_i915_gem_mmap_offset {
handle: self.gem_handle,
pad: 0,
offset: 0,
flags: I915_MMAP_OFFSET_WC as u64,
extensions: 0,
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_i915_gem_mmap_offset struct
let offset = unsafe {
drm_ioctl_i915_gem_mmap_offset(self.physical_device.as_fd().unwrap(), &mut gem_mmap)?;
gem_mmap.offset
};
let mapping = self.physical_device.cpu_map(offset, self.size)?;
Ok(Arc::new(mapping))
}
fn export(&self) -> MesaResult<MesaHandle> {
self.physical_device.export(self.gem_handle)
}
fn invalidate(
&self,
_sync_flags: u64,
_ranges: &[crate::magma_defines::MagmaMappedMemoryRange],
) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
fn flush(
&self,
_sync_flags: u64,
_ranges: &[crate::magma_defines::MagmaMappedMemoryRange],
) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
}
impl Drop for I915Buffer {
fn drop(&mut self) {
self.physical_device.close(self.gem_handle);
}
}
impl Buffer for I915Buffer {}
unsafe impl Send for I915 {}
unsafe impl Sync for I915 {}
unsafe impl Send for I915Context {}
unsafe impl Sync for I915Context {}
unsafe impl Send for I915Buffer {}
unsafe impl Sync for I915Buffer {}

View file

@ -0,0 +1,30 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#[macro_export]
macro_rules! ioctl_write_ptr {
($name:ident, $ioty:expr, $nr:expr, $ty:ty) => {
pub unsafe fn $name(fd: std::os::fd::BorrowedFd, data: &$ty) -> std::io::Result<()> {
const OPCODE: rustix::ioctl::Opcode =
rustix::ioctl::opcode::write::<$ty>($ioty as u8, $nr as u8);
Ok(rustix::ioctl::ioctl(
fd,
rustix::ioctl::Setter::<OPCODE, $ty>::new(*data),
)?)
}
};
}
#[macro_export]
macro_rules! ioctl_readwrite {
($name:ident, $ioty:expr, $nr:expr, $ty:ty) => {
pub unsafe fn $name(fd: std::os::fd::BorrowedFd, data: &mut $ty) -> std::io::Result<()> {
const OPCODE: rustix::ioctl::Opcode =
rustix::ioctl::opcode::read_write::<$ty>($ioty as u8, $nr as u8);
Ok(rustix::ioctl::ioctl(
fd,
rustix::ioctl::Updater::<OPCODE, $ty>::new(data),
)?)
}
};
}

View file

@ -0,0 +1,21 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
mod amdgpu;
mod bindings;
mod common;
mod drm;
pub mod flexible_array;
mod i915;
mod macros;
mod msm;
mod xe;
pub use amdgpu::AmdGpu;
pub use common::enumerate_devices;
pub use common::PlatformDevice;
pub use common::PlatformPhysicalDevice;
pub use drm::*;
pub use i915::I915;
pub use msm::Msm;
pub use xe::Xe;

269
src/magma/sys/linux/msm.rs Normal file
View file

@ -0,0 +1,269 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::sync::Arc;
use crate::ioctl_readwrite;
use crate::ioctl_write_ptr;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericBuffer;
use crate::traits::GenericDevice;
use crate::traits::PhysicalDevice;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::sys::linux::bindings::drm_bindings::DRM_COMMAND_BASE;
use crate::sys::linux::bindings::drm_bindings::DRM_IOCTL_BASE;
use crate::sys::linux::bindings::msm_bindings::*;
use crate::sys::linux::PlatformDevice;
ioctl_readwrite!(
drm_ioctl_msm_gem_new,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_GEM_NEW,
drm_msm_gem_new
);
ioctl_readwrite!(
drm_ioctl_msm_gem_info,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_GEM_INFO,
drm_msm_gem_info
);
ioctl_write_ptr!(
msm_gem_cpu_prep,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_GEM_CPU_PREP,
drm_msm_gem_cpu_prep
);
ioctl_write_ptr!(
msm_gem_cpu_fini,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_GEM_CPU_FINI,
drm_msm_gem_cpu_fini
);
ioctl_readwrite!(
msm_submitqueue_new,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_SUBMITQUEUE_NEW,
drm_msm_submitqueue
);
ioctl_write_ptr!(
msm_submitqueue_close,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_MSM_SUBMITQUEUE_CLOSE,
__u32
);
struct MsmContext {
physical_device: Arc<dyn PhysicalDevice>,
submit_queue_id: u32,
}
impl Drop for MsmContext {
fn drop(&mut self) {
// SAFETY: This is a valid file descriptor and a valid submitqueue id.
unsafe {
let _ =
msm_submitqueue_close(self.physical_device.as_fd().unwrap(), &self.submit_queue_id);
}
}
}
impl Context for MsmContext {}
pub struct Msm {
physical_device: Arc<dyn PhysicalDevice>,
mem_props: MagmaMemoryProperties,
}
struct MsmBuffer {
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
}
impl Msm {
pub fn new(physical_device: Arc<dyn PhysicalDevice>) -> Msm {
Msm {
physical_device,
mem_props: Default::default(),
}
}
}
impl GenericDevice for Msm {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Err(MesaError::Unsupported)
}
fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
Err(MesaError::Unsupported)
}
fn create_context(&self, _device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
let mut new_submit_queue = drm_msm_submitqueue {
flags: 0,
prio: 0,
..Default::default()
};
// SAFETY: This is a valid file descriptor.
unsafe {
msm_submitqueue_new(self.physical_device.as_fd().unwrap(), &mut new_submit_queue)?;
}
Ok(Arc::new(MsmContext {
physical_device: self.physical_device.clone(),
submit_queue_id: new_submit_queue.id,
}))
}
fn create_buffer(
&self,
_device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let buf = MsmBuffer::new(self.physical_device.clone(), create_info, &self.mem_props)?;
Ok(Arc::new(buf))
}
fn import(
&self,
_device: &Arc<dyn Device>,
info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let gem_handle = self.physical_device.import(info.handle)?;
let buf = MsmBuffer::from_existing(
self.physical_device.clone(),
gem_handle,
info.size.try_into()?,
)?;
Ok(Arc::new(buf))
}
}
impl PlatformDevice for Msm {}
impl Device for Msm {}
impl MsmBuffer {
fn new(
physical_device: Arc<dyn PhysicalDevice>,
create_info: &MagmaCreateBufferInfo,
_mem_props: &MagmaMemoryProperties,
) -> MesaResult<MsmBuffer> {
let mut gem_new = drm_msm_gem_new {
size: create_info.size,
flags: 0,
..Default::default()
};
// SAFETY: This is a well-formed ioctl conforming the driver specificiation.
unsafe {
drm_ioctl_msm_gem_new(physical_device.as_fd().unwrap(), &mut gem_new)?;
}
Ok(MsmBuffer {
physical_device,
gem_handle: gem_new.handle,
size: create_info.size.try_into()?,
})
}
fn from_existing(
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
) -> MesaResult<MsmBuffer> {
Ok(MsmBuffer {
physical_device,
gem_handle,
size,
})
}
}
impl GenericBuffer for MsmBuffer {
fn map(&self, _buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>> {
let mut gem_info: drm_msm_gem_info = drm_msm_gem_info {
handle: self.gem_handle,
info: MSM_INFO_GET_OFFSET,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_msm_gem_info
let offset = unsafe {
drm_ioctl_msm_gem_info(self.physical_device.as_fd().unwrap(), &mut gem_info)?;
gem_info.value
};
let mapping = self.physical_device.cpu_map(offset, self.size)?;
Ok(Arc::new(mapping))
}
fn export(&self) -> MesaResult<MesaHandle> {
self.physical_device.export(self.gem_handle)
}
fn invalidate(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
let prep = drm_msm_gem_cpu_prep {
handle: self.gem_handle,
op: MSM_PREP_READ | MSM_PREP_WRITE,
..Default::default()
};
// SAFETY: This is a valid file descriptor and a valid gem handle.
unsafe {
msm_gem_cpu_prep(self.physical_device.as_fd().unwrap(), &prep)?;
}
Ok(())
}
fn flush(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
let fini = drm_msm_gem_cpu_fini {
handle: self.gem_handle,
};
// SAFETY: This is a valid file descriptor and a valid gem handle.
unsafe {
msm_gem_cpu_fini(self.physical_device.as_fd().unwrap(), &fini)?;
}
Ok(())
}
}
impl Drop for MsmBuffer {
fn drop(&mut self) {
// GEM close
}
}
impl Buffer for MsmBuffer {}
unsafe impl Send for Msm {}
unsafe impl Sync for Msm {}
unsafe impl Send for MsmContext {}
unsafe impl Sync for MsmContext {}
unsafe impl Send for MsmBuffer {}
unsafe impl Sync for MsmBuffer {}

539
src/magma/sys/linux/xe.rs Normal file
View file

@ -0,0 +1,539 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::sync::Arc;
use log::error;
use mesa3d_util::log_status;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use crate::ioctl_readwrite;
use crate::ioctl_write_ptr;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericBuffer;
use crate::traits::GenericDevice;
use crate::traits::PhysicalDevice;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MagmaPciInfo;
use crate::magma_defines::MAGMA_HEAP_CPU_VISIBLE_BIT;
use crate::magma_defines::MAGMA_HEAP_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
use crate::flexible_array_impl;
use crate::sys::linux::bindings::drm_bindings::DRM_COMMAND_BASE;
use crate::sys::linux::bindings::drm_bindings::DRM_IOCTL_BASE;
use crate::sys::linux::bindings::xe_bindings::*;
use crate::sys::linux::flexible_array::FlexibleArray;
use crate::sys::linux::flexible_array::FlexibleArrayWrapper;
use crate::sys::linux::PlatformDevice;
// This information is also useful to the system side of a driver. Should be separated
// into it's own crate or module.
const GEN12_IDS: [u16; 50] = [
0x4c8a, 0x4c8b, 0x4c8c, 0x4c90, 0x4c9a, 0x4680, 0x4681, 0x4682, 0x4683, 0x4688, 0x4689, 0x4690,
0x4691, 0x4692, 0x4693, 0x4698, 0x4699, 0x4626, 0x4628, 0x462a, 0x46a0, 0x46a1, 0x46a2, 0x46a3,
0x46a6, 0x46a8, 0x46aa, 0x46b0, 0x46b1, 0x46b2, 0x46b3, 0x46c0, 0x46c1, 0x46c2, 0x46c3, 0x9A40,
0x9A49, 0x9A59, 0x9A60, 0x9A68, 0x9A70, 0x9A78, 0x9AC0, 0x9AC9, 0x9AD9, 0x9AF8, 0x4905, 0x4906,
0x4907, 0x4908,
];
const ADLP_IDS: [u16; 23] = [
0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6, 0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628, 0x46B0, 0x46B1,
0x46B2, 0x46B3, 0x46C0, 0x46C1, 0x46C2, 0x46C3, 0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4,
];
const RPLP_IDS: [u16; 10] = [
0xA720, 0xA721, 0xA7A0, 0xA7A1, 0xA7A8, 0xA7A9, 0xA7AA, 0xA7AB, 0xA7AC, 0xA7AD,
];
const MTL_IDS: [u16; 5] = [0x7D40, 0x7D60, 0x7D45, 0x7D55, 0x7DD5];
const LNL_IDS: [u16; 3] = [0x6420, 0x64A0, 0x64B0];
const PTL_IDS: [u16; 8] = [
0xB080, 0xB081, 0xB082, 0xB083, 0xB08F, 0xB090, 0xB0A0, 0xB0B0,
];
ioctl_readwrite!(
drm_ioctl_xe_device_query,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_DEVICE_QUERY,
drm_xe_device_query
);
ioctl_readwrite!(
drm_ioctl_xe_gem_create,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_GEM_CREATE,
drm_xe_gem_create
);
ioctl_readwrite!(
drm_ioctl_xe_gem_mmap_offset,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_GEM_MMAP_OFFSET,
drm_xe_gem_mmap_offset
);
ioctl_readwrite!(
drm_ioctl_xe_vm_create,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_VM_CREATE,
drm_xe_vm_create
);
ioctl_write_ptr!(
drm_ioctl_xe_vm_destroy,
DRM_IOCTL_BASE,
DRM_COMMAND_BASE + DRM_XE_VM_DESTROY,
drm_xe_vm_destroy
);
flexible_array_impl!(drm_xe_query_config, __u64, num_params, info);
flexible_array_impl!(
drm_xe_query_mem_regions,
drm_xe_mem_region,
num_mem_regions,
mem_regions
);
pub struct Xe {
physical_device: Arc<dyn PhysicalDevice>,
_gtt_size: u64,
_mem_alignment: u64,
mem_props: MagmaMemoryProperties,
sysmem_instance: u16,
vram_instance: u16,
}
struct XeBuffer {
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
}
struct XeContext {
physical_device: Arc<dyn PhysicalDevice>,
vm_id: u32,
}
fn xe_device_query<T, S>(
physical_device: &Arc<dyn PhysicalDevice>,
query_id: u32,
) -> MesaResult<FlexibleArrayWrapper<T, S>>
where
T: FlexibleArray<S> + Default,
{
let mut device_query: drm_xe_device_query = drm_xe_device_query {
query: query_id,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_xe_device_query
unsafe {
drm_ioctl_xe_device_query(physical_device.as_fd().unwrap(), &mut device_query)?;
};
let total_size = device_query.size;
let mut wrapper = FlexibleArrayWrapper::<T, S>::from_total_size(total_size as usize);
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_xe_device_query
// - drm_xe_device_query.data: we trust the FlexibleArrayWrapper to hold enough space
unsafe {
device_query.data = wrapper.as_mut_ptr() as __u64;
drm_ioctl_xe_device_query(physical_device.as_fd().unwrap(), &mut device_query)?;
};
Ok(wrapper)
}
/// Determines and sets the graphics version of the Intel device based on its ID.
fn determine_graphics_version(pci_device_id: u16) -> MesaResult<u32> {
let mut graphics_version = 0;
if ADLP_IDS.contains(&pci_device_id) {
graphics_version = 12;
}
if RPLP_IDS.contains(&pci_device_id) {
graphics_version = 12;
}
if MTL_IDS.contains(&pci_device_id) {
graphics_version = 12;
}
if LNL_IDS.contains(&pci_device_id) {
graphics_version = 20;
}
if PTL_IDS.contains(&pci_device_id) {
graphics_version = 20;
}
if GEN12_IDS.contains(&pci_device_id) {
graphics_version = 12;
}
if graphics_version != 0 {
Ok(graphics_version)
} else {
Err(MesaError::WithContext("missing intel pci-id"))
}
}
#[derive(Default)]
struct XeMemoryInfo {
vram_size: u64,
vram_used: u64,
sysmem_size: u64,
sysmem_used: u64,
vram_cpu_visible_size: u64,
vram_cpu_visible_used: u64,
sysmem_instance: u16,
vram_instance: u16,
}
fn xe_query_memory_regions(physical_device: &Arc<dyn PhysicalDevice>) -> MesaResult<XeMemoryInfo> {
let mut memory_info: XeMemoryInfo = Default::default();
let query_mem_regions = xe_device_query::<drm_xe_query_mem_regions, drm_xe_mem_region>(
physical_device,
DRM_XE_DEVICE_QUERY_MEM_REGIONS,
)?;
let mem_regions = query_mem_regions.entries_slice();
for region in mem_regions {
match region.mem_class as u32 {
DRM_XE_MEM_REGION_CLASS_SYSMEM => {
if memory_info.sysmem_size != 0 {
return Err(MesaError::WithContext("sysmem_size should not be set"));
}
// this should really use sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) for the
// host-visible heap. rustix has get_page_size(), but not get_num_pages..
memory_info.sysmem_size = region.total_size;
memory_info.sysmem_used = region.used;
memory_info.sysmem_instance = region.instance;
}
DRM_XE_MEM_REGION_CLASS_VRAM => {
if memory_info.vram_size != 0 || memory_info.vram_cpu_visible_size != 0 {
return Err(MesaError::WithContext("one vram value should be zero"));
}
memory_info.vram_cpu_visible_size = region.cpu_visible_size;
memory_info.vram_size = region.total_size - region.cpu_visible_size;
memory_info.vram_cpu_visible_used = region.cpu_visible_used;
memory_info.vram_used = region.used - region.cpu_visible_used;
memory_info.vram_instance = region.instance;
}
_ => return Err(MesaError::Unsupported),
}
}
Ok(memory_info)
}
impl Xe {
pub fn new(
physical_device: Arc<dyn PhysicalDevice>,
pci_info: &MagmaPciInfo,
) -> MesaResult<Xe> {
let _graphics_version = determine_graphics_version(pci_info.device_id)?;
let mut mem_props: MagmaMemoryProperties = Default::default();
let query_config = xe_device_query::<drm_xe_query_config, __u64>(
&physical_device,
DRM_XE_DEVICE_QUERY_CONFIG,
)?;
let config = query_config.entries_slice();
let _config_len = config.len();
let gtt_size = 1u64 << config[DRM_XE_QUERY_CONFIG_VA_BITS as usize];
let mem_alignment = config[DRM_XE_QUERY_CONFIG_MIN_ALIGNMENT as usize];
let memory_info = xe_query_memory_regions(&physical_device)?;
if memory_info.sysmem_size != 0 {
// Non-LLC case ignored.
mem_props.add_heap(memory_info.sysmem_size, MAGMA_HEAP_CPU_VISIBLE_BIT);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
| MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.increment_heap_count();
}
if memory_info.vram_cpu_visible_size != 0 {
mem_props.add_heap(
memory_info.vram_cpu_visible_size,
MAGMA_HEAP_CPU_VISIBLE_BIT | MAGMA_HEAP_DEVICE_LOCAL_BIT,
);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT
| MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.increment_heap_count();
}
if memory_info.vram_size != 0 {
mem_props.add_heap(memory_info.vram_size, MAGMA_HEAP_DEVICE_LOCAL_BIT);
mem_props.add_memory_type(MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
mem_props.increment_heap_count();
}
Ok(Xe {
physical_device,
_gtt_size: gtt_size,
_mem_alignment: mem_alignment,
mem_props,
sysmem_instance: memory_info.sysmem_instance,
vram_instance: memory_info.vram_instance,
})
}
}
impl GenericDevice for Xe {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Ok(self.mem_props.clone())
}
fn get_memory_budget(&self, heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
if heap_idx >= self.mem_props.memory_heap_count {
return Err(MesaError::WithContext("Heap Index out of bounds"));
}
let memory_info = xe_query_memory_regions(&self.physical_device)?;
let heap = &self.mem_props.memory_heaps[heap_idx as usize];
let (budget, usage) = if heap.is_device_local() && heap.is_cpu_visible() {
(
memory_info.vram_cpu_visible_size,
memory_info.vram_cpu_visible_used,
)
} else if heap.is_device_local() {
(memory_info.vram_size, memory_info.vram_used)
} else if heap.is_cpu_visible() {
(memory_info.sysmem_size, memory_info.sysmem_used)
} else {
return Err(MesaError::Unsupported);
};
Ok(MagmaHeapBudget { budget, usage })
}
fn create_context(&self, _device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
let ctx = XeContext::new(self.physical_device.clone(), 0)?;
Ok(Arc::new(ctx))
}
fn create_buffer(
&self,
_device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let buf = XeBuffer::new(
self.physical_device.clone(),
create_info,
&self.mem_props,
self.sysmem_instance,
self.vram_instance,
)?;
Ok(Arc::new(buf))
}
fn import(
&self,
_device: &Arc<dyn Device>,
info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let gem_handle = self.physical_device.import(info.handle)?;
let buf = XeBuffer::from_existing(
self.physical_device.clone(),
gem_handle,
info.size.try_into()?,
)?;
Ok(Arc::new(buf))
}
}
impl PlatformDevice for Xe {}
impl Device for Xe {}
impl XeContext {
fn new(physical_device: Arc<dyn PhysicalDevice>, _priority: i32) -> MesaResult<XeContext> {
let mut vm_create = drm_xe_vm_create {
flags: DRM_XE_VM_CREATE_FLAG_SCRATCH_PAGE,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_xe_vm_create struct
unsafe {
drm_ioctl_xe_vm_create(physical_device.as_fd().unwrap(), &mut vm_create)?;
};
Ok(XeContext {
physical_device,
vm_id: vm_create.vm_id,
})
}
}
impl Drop for XeContext {
fn drop(&mut self) {
let destroy = drm_xe_vm_destroy {
vm_id: self.vm_id,
..Default::default()
};
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_xe_vm_destroy struct
let result =
unsafe { drm_ioctl_xe_vm_destroy(self.physical_device.as_fd().unwrap(), &destroy) };
log_status!(result);
}
}
impl Context for XeContext {}
impl XeBuffer {
fn new(
physical_device: Arc<dyn PhysicalDevice>,
create_info: &MagmaCreateBufferInfo,
mem_props: &MagmaMemoryProperties,
sysmem_instance: u16,
vram_instance: u16,
) -> MesaResult<XeBuffer> {
let mut gem_create: drm_xe_gem_create = Default::default();
let mut pxp_ext: drm_xe_ext_set_property = Default::default();
gem_create.size = create_info.size;
let memory_type = mem_props.get_memory_type(create_info.memory_type_idx);
let memory_heap = mem_props.get_memory_heap(memory_type.heap_idx);
if memory_type.is_cached() {
gem_create.cpu_caching = DRM_XE_GEM_CPU_CACHING_WB as u16;
} else {
gem_create.cpu_caching = DRM_XE_GEM_CPU_CACHING_WC as u16;
}
if memory_heap.is_cpu_visible() && memory_heap.is_device_local() {
gem_create.flags |= DRM_XE_GEM_CREATE_FLAG_NEEDS_VISIBLE_VRAM;
gem_create.placement |= 1 << sysmem_instance;
gem_create.placement |= 1 << vram_instance;
} else if memory_heap.is_device_local() {
gem_create.placement |= 1 << vram_instance;
} else if memory_heap.is_cpu_visible() {
gem_create.placement |= 1 << sysmem_instance;
}
if memory_type.is_protected() {
pxp_ext.base.name = DRM_XE_GEM_CREATE_EXTENSION_SET_PROPERTY;
pxp_ext.property = DRM_XE_GEM_CREATE_SET_PROPERTY_PXP_TYPE;
pxp_ext.value = DRM_XE_PXP_TYPE_HWDRM as u64;
gem_create.extensions = &pxp_ext as *const drm_xe_ext_set_property as u64;
}
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_amdgpu_gem_create_args
unsafe {
drm_ioctl_xe_gem_create(physical_device.as_fd().unwrap(), &mut gem_create)?;
};
Ok(XeBuffer {
physical_device,
gem_handle: gem_create.handle,
size: create_info.size.try_into()?,
})
}
fn from_existing(
physical_device: Arc<dyn PhysicalDevice>,
gem_handle: u32,
size: usize,
) -> MesaResult<XeBuffer> {
Ok(XeBuffer {
physical_device,
gem_handle,
size,
})
}
}
impl GenericBuffer for XeBuffer {
fn map(&self, _buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>> {
let mut xe_offset: drm_xe_gem_mmap_offset = Default::default();
// SAFETY:
// Valid arguments are supplied for the following arguments:
// - Underlying descriptor
// - drm_xe_gem_mmap_offset
let offset = unsafe {
xe_offset.handle = self.gem_handle;
drm_ioctl_xe_gem_mmap_offset(self.physical_device.as_fd().unwrap(), &mut xe_offset)?;
xe_offset.offset
};
let mapping = self.physical_device.cpu_map(offset, self.size)?;
Ok(Arc::new(mapping))
}
fn export(&self) -> MesaResult<MesaHandle> {
self.physical_device.export(self.gem_handle)
}
fn invalidate(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
fn flush(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
Err(MesaError::Unsupported)
}
}
impl Drop for XeBuffer {
fn drop(&mut self) {
self.physical_device.close(self.gem_handle)
}
}
impl Buffer for XeBuffer {}
unsafe impl Send for Xe {}
unsafe impl Sync for Xe {}
unsafe impl Send for XeContext {}
unsafe impl Sync for XeContext {}
unsafe impl Send for XeBuffer {}
unsafe impl Sync for XeBuffer {}

18
src/magma/sys/mod.rs Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#[cfg(any(target_os = "android", target_os = "linux"))]
pub mod linux;
#[cfg(target_os = "windows")]
pub mod windows;
cfg_if::cfg_if! {
if #[cfg(any(target_os = "android", target_os = "linux"))] {
pub use linux as platform;
} else if #[cfg(windows)] {
pub use windows as platform;
} else {
compile_error!("Unsupported platform");
}
}

View file

@ -0,0 +1,106 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
// Taken from gfxstrand@'s WDDM branch, which seems reversed engineered.
//
// https://gitlab.freedesktop.org/gfxstrand/mesa/-/tree/radv/wddm2?ref_type=heads
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaMemoryProperties;
use crate::sys::windows::VendorPrivateData;
static AMD_CREATE_ALLOC_PDATA: [u32; 15] = [
0x00000000, 0x00000000, 0x00000080, 0x00000420, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
];
static AMD_ALLOC_PDATA: [u32; 206] = [
0x000002f8, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x0000036f, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
0x000002f8, 0x0000000d, 0x00000080, 0x00000000, 0xa0002008, 0x00270000, 0x00010000, 0x00001805,
0x00000004, 0x00000004, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x01080000, 0x00270000, 0x00000000, 0x00000000, 0x00000000, 0x00270000, 0x00000000,
0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00400000, 0x00000001, 0x00000001, 0x00000001,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000001e4, 0x00000000, 0x00000000, 0x00000008,
0x00000000, 0x00000120, 0x00020000, 0x00000020, 0x00000001, 0x00000000, 0x00270000, 0x00000001,
0x00270000, 0x00000001, 0x00000001, 0x00270000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00270000, 0x00000001, 0x00000001, 0x00270000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000114, 0x00000000, 0x00000000, 0x00000000, 0x00270000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
];
pub struct Amd(pub ());
impl VendorPrivateData for Amd {
fn createallocation_pdata(&self) -> Vec<u32> {
Vec::from(AMD_CREATE_ALLOC_PDATA)
}
fn allocationinfo2_pdata(
&self,
create_info: &MagmaCreateBufferInfo,
mem_props: &MagmaMemoryProperties,
) -> Vec<u32> {
let mut alloc_pdata = AMD_ALLOC_PDATA;
let memory_type = mem_props.get_memory_type(create_info.memory_type_idx);
// FIXME: gpu_info.pte_fragment_size, alignment
// Need GPU topology crate
let size: u32 = 0;
let phys_size: u32 = 0;
let phys_alignment: u32 = 0;
alloc_pdata[21] = phys_size;
alloc_pdata[22] = phys_alignment;
alloc_pdata[42] = phys_size;
alloc_pdata[46] = phys_size;
alloc_pdata[70] = size;
alloc_pdata[72] = phys_size;
alloc_pdata[75] = phys_size;
alloc_pdata[92] = size;
alloc_pdata[95] = phys_size;
alloc_pdata[141] = phys_size;
// Some sort of flags field
alloc_pdata[20] = 0x00002000;
if memory_type.is_coherent() {
alloc_pdata[20] |= 0xa0000000;
alloc_pdata[41] |= 0x00080000;
} else {
alloc_pdata[20] |= 0x80000000;
}
// Write-back cached
if memory_type.is_device_local() {
alloc_pdata[20] |= 0x00000005;
alloc_pdata[24] = 0x00030101;
alloc_pdata[25] = 0x00000001;
} else if memory_type.is_coherent() && !memory_type.is_cached() {
// RADEON_FLAG_GTT_WC
alloc_pdata[20] |= 0x00000004;
alloc_pdata[24] = 3;
alloc_pdata[25] = 3;
} else {
alloc_pdata[20] |= 0x00000008;
alloc_pdata[24] = 4;
alloc_pdata[25] = 4;
}
Vec::from(alloc_pdata)
}
}

View file

@ -0,0 +1,673 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use std::os::raw::c_void;
use std::slice::from_raw_parts;
use std::sync::Arc;
use libc::wcslen;
use log::error;
use mesa3d_util::IntoRawDescriptor;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaError;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaMapping;
use mesa3d_util::MesaResult;
use crate::check_ntstatus;
use crate::log_ntstatus;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MagmaPciBusInfo;
use crate::magma_defines::MagmaPciInfo;
use crate::magma_defines::MAGMA_HEAP_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT;
use crate::magma_defines::MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
use crate::magma_defines::MAGMA_SYNC_RANGES;
use crate::magma_defines::MAGMA_SYNC_WHOLE_RANGE;
use crate::magma_defines::MAGMA_VENDOR_ID_AMD;
use crate::sys::windows::Amd;
use crate::sys::windows::VendorPrivateData;
use crate::traits::AsVirtGpu;
use crate::traits::Buffer;
use crate::traits::Context;
use crate::traits::Device;
use crate::traits::GenericBuffer;
use crate::traits::GenericDevice;
use crate::traits::GenericPhysicalDevice;
use crate::traits::PhysicalDevice;
use windows_sys::Wdk::Graphics::Direct3D::*;
use windows_sys::Win32::Foundation::LUID;
type D3dkmtHandle = u32;
pub struct WddmAdapter {
handle: D3dkmtHandle,
_luid: LUID,
segment_group_size: D3DKMT_SEGMENTGROUPSIZEINFO,
_hw_sch_enabled: bool,
_hw_sch_supported: bool,
adapter_name: String,
chip_type: String,
}
pub struct WddmDevice {
handle: D3dkmtHandle,
adapter: Arc<dyn PhysicalDevice>,
vendor_private_data: Box<dyn VendorPrivateData>,
mem_props: MagmaMemoryProperties,
}
pub struct WddmBuffer {
handle: D3dkmtHandle,
device: Arc<dyn Device>,
size: u64,
}
pub struct WddmContext {
handle: D3dkmtHandle,
_device: Arc<dyn Device>,
}
struct WddmMapping {
_buffer: Arc<dyn Buffer>,
pdata: *mut c_void,
size: usize,
}
pub trait WindowsDevice {
fn as_wddm_handle(&self) -> D3dkmtHandle {
0
}
fn vendor_private_data(&self) -> Option<&dyn VendorPrivateData> {
None
}
}
pub trait WindowsPhysicalDevice {
fn as_wddm_handle(&self) -> D3dkmtHandle {
0
}
fn segment_group_size(&self) -> D3DKMT_SEGMENTGROUPSIZEINFO {
Default::default()
}
}
impl WddmAdapter {
pub fn new(handle: D3dkmtHandle, luid: LUID) -> WddmAdapter {
WddmAdapter {
handle,
_luid: luid,
segment_group_size: Default::default(),
_hw_sch_enabled: Default::default(),
_hw_sch_supported: Default::default(),
adapter_name: Default::default(),
chip_type: Default::default(),
}
}
pub fn initialize(&mut self) -> MesaResult<(MagmaPciInfo, MagmaPciBusInfo)> {
let mut pci_info: MagmaPciInfo = Default::default();
let mut pci_bus_info: MagmaPciBusInfo = Default::default();
let mut query_device_ids: D3DKMT_QUERY_DEVICE_IDS = Default::default();
let mut adapter_address: D3DKMT_ADAPTERADDRESS = Default::default();
let mut adapter_info = D3DKMT_QUERYADAPTERINFO {
hAdapter: self.handle,
Type: KMTQAITYPE_PHYSICALADAPTERDEVICEIDS,
pPrivateDriverData: &mut query_device_ids as *mut D3DKMT_QUERY_DEVICE_IDS
as *mut c_void,
PrivateDriverDataSize: std::mem::size_of::<D3DKMT_QUERY_DEVICE_IDS>() as u32,
};
// SAFETY:
// - `adapter_info` is stack-allocated and properly typed.
// - `pPrivateDriverData` and `PrivateDriverDataSize` are both correct for the
// KMTQAITYPE_PHYSICALADAPTERDEVICEIDS operation
check_ntstatus!(unsafe {
D3DKMTQueryAdapterInfo(&mut adapter_info as *mut D3DKMT_QUERYADAPTERINFO)
})?;
adapter_info.Type = KMTQAITYPE_ADAPTERADDRESS;
adapter_info.pPrivateDriverData =
&mut adapter_address as *mut D3DKMT_ADAPTERADDRESS as *mut c_void;
adapter_info.PrivateDriverDataSize = std::mem::size_of::<D3DKMT_ADAPTERADDRESS>() as u32;
// SAFETY:
// - `adapter_info` is stack-allocated and properly typed.
// - `pPrivateDriverData` and `PrivateDriverDataSize` are both correct for the
// KMTQAITYPE_ADAPTERADDRESS operation
check_ntstatus!(unsafe {
D3DKMTQueryAdapterInfo(&mut adapter_info as *mut D3DKMT_QUERYADAPTERINFO)
})?;
let mut wddm_caps: D3DKMT_WDDM_2_7_CAPS = Default::default();
adapter_info.Type = KMTQAITYPE_WDDM_2_7_CAPS;
adapter_info.pPrivateDriverData =
&mut wddm_caps as *mut D3DKMT_WDDM_2_7_CAPS as *mut c_void;
adapter_info.PrivateDriverDataSize = std::mem::size_of::<D3DKMT_WDDM_2_7_CAPS>() as u32;
// SAFETY:
// - `adapter_info` is stack-allocated and properly typed.
// - `pPrivateDriverData` and `PrivateDriverDataSize` are both correct for the
// KMTQAITYPE_WDDM_2_7_CAPS operation
check_ntstatus!(unsafe {
D3DKMTQueryAdapterInfo(&mut adapter_info as *mut D3DKMT_QUERYADAPTERINFO)
})?;
adapter_info.Type = KMTQAITYPE_GETSEGMENTGROUPSIZE;
adapter_info.pPrivateDriverData =
&mut self.segment_group_size as *mut D3DKMT_SEGMENTGROUPSIZEINFO as *mut c_void;
adapter_info.PrivateDriverDataSize =
std::mem::size_of::<D3DKMT_SEGMENTGROUPSIZEINFO>() as u32;
// SAFETY:
// - `adapter_info` is stack-allocated and properly typed.
// - `pPrivateDriverData` and `PrivateDriverDataSize` are both correct for the
// KMTQAITYPE_GETSEGMENTGROUPSIZE operation
check_ntstatus!(unsafe {
D3DKMTQueryAdapterInfo(&mut adapter_info as *mut D3DKMT_QUERYADAPTERINFO)
})?;
let mut registry_info: D3DKMT_ADAPTERREGISTRYINFO = Default::default();
adapter_info.Type = KMTQAITYPE_ADAPTERREGISTRYINFO_RENDER;
adapter_info.pPrivateDriverData =
&mut registry_info as *mut D3DKMT_ADAPTERREGISTRYINFO as *mut c_void;
adapter_info.PrivateDriverDataSize =
std::mem::size_of::<D3DKMT_ADAPTERREGISTRYINFO>() as u32;
// SAFETY:
// - `adapter_info` is stack-allocated and properly typed.
// - `pPrivateDriverData` and `PrivateDriverDataSize` are both correct for the
// KMTQAITYPE_ADAPTERREGISTERYINFO operation
check_ntstatus!(unsafe {
D3DKMTQueryAdapterInfo(&mut adapter_info as *mut D3DKMT_QUERYADAPTERINFO)
})?;
// SAFETY:
// - `registry_info` has been successfully retrieved and contains well-formed UTF-16 data.
// - WCHAR/wchar_t are 16-bits on Windows.
let adapter_name_len = unsafe { wcslen(&registry_info.AdapterString[0] as *const u16) };
let chip_type_len = unsafe { wcslen(&registry_info.ChipType[0] as *const u16) };
let adapter_name_slice: &[u16] = unsafe {
from_raw_parts(
&registry_info.AdapterString[0] as *const _,
adapter_name_len,
)
};
let chip_type_slice: &[u16] =
unsafe { from_raw_parts(&registry_info.ChipType[0] as *const _, chip_type_len) };
self.adapter_name = String::from_utf16(adapter_name_slice)
.map_err(|_| MesaError::WithContext("invalid utf-16 data"))?;
self.chip_type = String::from_utf16(chip_type_slice)
.map_err(|_| MesaError::WithContext("invalid utf-16 data"))?;
let device_ids = query_device_ids.DeviceIds;
pci_info.revision_id = device_ids.RevisionID.try_into()?;
pci_info.vendor_id = device_ids.VendorID.try_into()?;
pci_info.device_id = device_ids.DeviceID.try_into()?;
pci_info.subvendor_id = device_ids.SubVendorID.try_into()?;
pci_info.subdevice_id = device_ids.SubSystemID.try_into()?;
pci_bus_info.domain = 0;
pci_bus_info.bus = adapter_address.BusNumber.try_into()?;
pci_bus_info.device = adapter_address.DeviceNumber.try_into()?;
pci_bus_info.function = adapter_address.FunctionNumber.try_into()?;
Ok((pci_info, pci_bus_info))
}
}
impl GenericPhysicalDevice for WddmAdapter {
fn create_device(
&self,
physical_device: &Arc<dyn PhysicalDevice>,
pci_info: &MagmaPciInfo,
) -> MesaResult<Arc<dyn Device>> {
let vendor_private_data = match pci_info.vendor_id {
MAGMA_VENDOR_ID_AMD => Box::new(Amd(())),
_ => todo!(),
};
let device = WddmDevice::new(physical_device.clone(), vendor_private_data)?;
Ok(Arc::new(device))
}
}
impl WindowsPhysicalDevice for WddmAdapter {
fn as_wddm_handle(&self) -> D3dkmtHandle {
self.handle
}
fn segment_group_size(&self) -> D3DKMT_SEGMENTGROUPSIZEINFO {
self.segment_group_size
}
}
impl AsVirtGpu for WddmAdapter {}
impl PhysicalDevice for WddmAdapter {}
impl Drop for WddmAdapter {
fn drop(&mut self) {
let mut close = D3DKMT_CLOSEADAPTER {
hAdapter: self.handle,
};
// SAFETY: Safe since we own the adapter handle
log_ntstatus!(unsafe { D3DKMTCloseAdapter(&mut close as *mut D3DKMT_CLOSEADAPTER) });
}
}
pub fn enumerate_adapters() -> MesaResult<Vec<(WddmAdapter, MagmaPciInfo, MagmaPciBusInfo)>> {
let mut enum_adapters = D3DKMT_ENUMADAPTERS2::default();
// SAFETY:
// - `enum_adapters` is stack-allocated and properly typed.
// - D3DKMTEnumAdapters2 does not modify any other memory.
check_ntstatus!(unsafe {
D3DKMTEnumAdapters2(&mut enum_adapters as *mut D3DKMT_ENUMADAPTERS2)
})?;
// First call gets enum_adapters.NumAdapters, second call gets the actual data.
let mut adapter_slice = vec![D3DKMT_ADAPTERINFO::default(); enum_adapters.NumAdapters as usize];
enum_adapters.pAdapters = adapter_slice.as_mut_ptr();
// SAFETY:
// - `enum_adapters` is stack-allocated and properly typed.
// - D3DKMTEnumAdapters2 does not modify any other memory.
check_ntstatus!(unsafe {
D3DKMTEnumAdapters2(&mut enum_adapters as *mut D3DKMT_ENUMADAPTERS2)
})?;
// Should not return a larger value of NumAdapters than it returned on the first call.
assert!((enum_adapters.NumAdapters as usize) <= adapter_slice.len());
let mut adapters = Vec::with_capacity(enum_adapters.NumAdapters as usize);
for adapter in &mut adapter_slice[..(enum_adapters.NumAdapters as usize)] {
let mut adapter = WddmAdapter::new(adapter.hAdapter, adapter.AdapterLuid);
let (pci_info, pci_bus_info) = adapter.initialize()?;
adapters.push((adapter, pci_info, pci_bus_info));
}
Ok(adapters)
}
impl WddmDevice {
pub fn new(
adapter: Arc<dyn PhysicalDevice>,
vendor_private_data: Box<dyn VendorPrivateData>,
) -> MesaResult<WddmDevice> {
let mut mem_props: MagmaMemoryProperties = Default::default();
let mut arg = D3DKMT_CREATEDEVICE {
Flags: Default::default(),
Anonymous: D3DKMT_CREATEDEVICE_0 {
hAdapter: adapter.as_wddm_handle(),
},
..Default::default()
};
// Safe because mutable arg is allocated locally on the stack and we trust the D3DKMT API
// not to modify any other memory.
check_ntstatus!(unsafe { D3DKMTCreateDevice(&mut arg as *mut D3DKMT_CREATEDEVICE) })?;
let segment_group_size = adapter.segment_group_size();
if segment_group_size.NonLocalMemory > 0 {
mem_props.add_heap(segment_group_size.NonLocalMemory, 0);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT | MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
);
mem_props.add_memory_type(
MAGMA_MEMORY_PROPERTY_HOST_COHERENT_BIT
| MAGMA_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| MAGMA_MEMORY_PROPERTY_HOST_CACHED_BIT,
);
mem_props.increment_heap_count();
}
if segment_group_size.LocalMemory > 0 {
mem_props.add_heap(segment_group_size.LocalMemory, MAGMA_HEAP_DEVICE_LOCAL_BIT);
mem_props.add_memory_type(MAGMA_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
mem_props.increment_heap_count();
}
Ok(WddmDevice {
handle: arg.hDevice,
adapter,
vendor_private_data,
mem_props,
})
}
}
impl GenericDevice for WddmDevice {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties> {
Ok(self.mem_props.clone())
}
fn get_memory_budget(&self, heap_idx: u32) -> MesaResult<MagmaHeapBudget> {
if heap_idx >= self.mem_props.memory_heap_count {
return Err(MesaError::WithContext("Heap Index out of bounds"));
}
let mut segment_group = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL;
if self.mem_props.get_memory_heap(heap_idx).is_device_local() {
segment_group = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL;
}
let mut arg = D3DKMT_QUERYVIDEOMEMORYINFO {
hProcess: std::ptr::null_mut::<c_void>(),
hAdapter: self.adapter.as_wddm_handle(),
MemorySegmentGroup: segment_group,
Budget: 0, // output
CurrentUsage: 0, // output
CurrentReservation: 0, // output
AvailableForReservation: 0, // output
PhysicalAdapterIndex: 0,
};
check_ntstatus!(unsafe {
D3DKMTQueryVideoMemoryInfo(&mut arg as *mut D3DKMT_QUERYVIDEOMEMORYINFO)
})?;
Ok(MagmaHeapBudget {
budget: arg.Budget,
usage: arg.CurrentUsage,
})
}
fn create_context(&self, device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>> {
let ctx = WddmContext::new(device.clone())?;
Ok(Arc::new(ctx))
}
fn create_buffer(
&self,
device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let buf = WddmBuffer::new(device.clone(), create_info, &self.mem_props)?;
Ok(Arc::new(buf))
}
fn import(
&self,
device: &Arc<dyn Device>,
info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>> {
let mut open_alloc_info: D3DDDI_OPENALLOCATIONINFO2 = Default::default();
let mut arg = D3DKMT_OPENRESOURCEFROMNTHANDLE {
hDevice: self.handle,
hNtHandle: info.handle.os_handle.into_raw_descriptor(),
NumAllocations: 1,
pOpenAllocationInfo2: &mut open_alloc_info as *mut _,
PrivateRuntimeDataSize: 0,
pPrivateRuntimeData: std::ptr::null_mut(),
hResource: 0, // output
KeyedMutexPrivateRuntimeDataSize: 0,
pKeyedMutexPrivateRuntimeData: std::ptr::null_mut(),
ResourcePrivateDriverDataSize: 0,
pResourcePrivateDriverData: std::ptr::null_mut(),
TotalPrivateDriverDataBufferSize: 0,
pTotalPrivateDriverDataBuffer: std::ptr::null_mut(),
hKeyedMutex: 0,
hSyncObject: 0,
};
check_ntstatus!(unsafe { D3DKMTOpenResourceFromNtHandle(&mut arg) })?;
let buf =
WddmBuffer::from_existing(device.clone(), open_alloc_info.hAllocation, info.size)?;
Ok(Arc::new(buf))
}
}
impl Drop for WddmDevice {
fn drop(&mut self) {
let arg = D3DKMT_DESTROYDEVICE {
hDevice: self.handle,
};
// Safe because const arg is allocated locally on the stack and we trust the D3DKMT API
// not to modify any other memory.
log_ntstatus!(unsafe { D3DKMTDestroyDevice(&arg as *const D3DKMT_DESTROYDEVICE) })
}
}
impl WindowsDevice for WddmDevice {
fn as_wddm_handle(&self) -> D3dkmtHandle {
self.handle
}
fn vendor_private_data(&self) -> Option<&dyn VendorPrivateData> {
Some(&*self.vendor_private_data)
}
}
impl Device for WddmDevice {}
impl WddmContext {
pub fn new(device: Arc<dyn Device>) -> MesaResult<WddmContext> {
// TODO: Fill in NodeOrdinal, EngineAffinity, pPrivateDriverData
let mut arg = D3DKMT_CREATECONTEXTVIRTUAL {
hDevice: device.as_wddm_handle(),
NodeOrdinal: Default::default(),
EngineAffinity: Default::default(),
Flags: D3DDDI_CREATECONTEXTFLAGS {
Anonymous: D3DDDI_CREATECONTEXTFLAGS_0 {
Value: Default::default(),
},
},
pPrivateDriverData: std::ptr::null_mut::<c_void>(),
PrivateDriverDataSize: Default::default(),
ClientHint: D3DKMT_CLIENTHINT_VULKAN,
hContext: 0, // return value
};
check_ntstatus!(unsafe {
D3DKMTCreateContextVirtual(&mut arg as *mut D3DKMT_CREATECONTEXTVIRTUAL)
})?;
Ok(WddmContext {
handle: arg.hContext,
_device: device,
})
}
}
impl Drop for WddmContext {
fn drop(&mut self) {
// Safe because const arg is allocated locally on the stack and we trust the D3DKMT API
// not to modify any other memory.
log_ntstatus!(unsafe {
D3DKMTDestroyContext(&D3DKMT_DESTROYCONTEXT {
hContext: self.handle,
} as *const D3DKMT_DESTROYCONTEXT)
})
}
}
impl Context for WddmContext {}
impl WddmBuffer {
pub fn new(
device: Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
mem_props: &MagmaMemoryProperties,
) -> MesaResult<WddmBuffer> {
let vendor_private_data = device.vendor_private_data().unwrap();
let flags: D3DKMT_CREATEALLOCATIONFLAGS = Default::default();
// flags.set_NonSecure(1);
// flags.set_CreateWriteCombined(1);
// type annotations important for following calculation
let mut create_allocation: Vec<u32> = vendor_private_data.createallocation_pdata();
let mut allocationinfo2: Vec<u32> =
vendor_private_data.allocationinfo2_pdata(create_info, mem_props);
let size_create_allocation: usize = create_allocation.len() * std::mem::size_of::<u32>();
let size_allocationinfo2: usize = allocationinfo2.len() * std::mem::size_of::<u32>();
let mut alloc_info: D3DDDI_ALLOCATIONINFO2 = D3DDDI_ALLOCATIONINFO2 {
pPrivateDriverData: allocationinfo2.as_mut_ptr() as *mut c_void,
PrivateDriverDataSize: size_allocationinfo2.try_into()?,
..Default::default()
};
let mut arg = D3DKMT_CREATEALLOCATION {
hDevice: device.as_wddm_handle(),
hResource: Default::default(),
hGlobalShare: 0,
pPrivateRuntimeData: std::ptr::null_mut::<c_void>(),
PrivateRuntimeDataSize: 0,
PrivateDriverDataSize: size_create_allocation.try_into()?,
NumAllocations: 1,
Anonymous1: D3DKMT_CREATEALLOCATION_0 {
pPrivateDriverData: create_allocation.as_mut_ptr() as *mut c_void,
},
Anonymous2: D3DKMT_CREATEALLOCATION_1 {
pAllocationInfo2: &mut alloc_info as *mut D3DDDI_ALLOCATIONINFO2,
},
Flags: flags,
hPrivateRuntimeResourceHandle: std::ptr::null_mut::<c_void>(), // output of D3DKMTCreateAllocation
};
check_ntstatus!(unsafe {
D3DKMTCreateAllocation2(&mut arg as *mut D3DKMT_CREATEALLOCATION)
})?;
Ok(WddmBuffer {
handle: alloc_info.hAllocation,
device,
size: create_info.size,
})
}
pub fn from_existing(
device: Arc<dyn Device>,
handle: D3dkmtHandle,
size: u64,
) -> MesaResult<WddmBuffer> {
Ok(WddmBuffer {
handle,
device,
size,
})
}
}
unsafe impl Send for WddmMapping {}
unsafe impl Sync for WddmMapping {}
unsafe impl MappedRegion for WddmMapping {
fn as_ptr(&self) -> *mut u8 {
self.pdata as *mut u8
}
fn size(&self) -> usize {
self.size
}
fn as_mesa_mapping(&self) -> MesaMapping {
MesaMapping {
ptr: self.pdata as u64,
size: self.size as u64,
}
}
}
impl GenericBuffer for WddmBuffer {
fn map(&self, buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>> {
let mut arg = D3DKMT_LOCK2 {
hDevice: self.device.as_wddm_handle(),
hAllocation: self.handle,
..Default::default()
};
check_ntstatus!(unsafe { D3DKMTLock2(&mut arg as *mut D3DKMT_LOCK2) })?;
Ok(Arc::new(WddmMapping {
_buffer: buffer.clone(),
pdata: arg.pData,
size: self.size.try_into()?,
}))
}
fn export(&self) -> MesaResult<MesaHandle> {
Err(MesaError::Unsupported)
}
fn invalidate(&self, sync_flags: u64, ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
let mut arg = D3DKMT_INVALIDATECACHE {
hDevice: self.device.as_wddm_handle(),
hAllocation: self.handle,
..Default::default()
};
if (sync_flags & MAGMA_SYNC_WHOLE_RANGE) != 0 {
arg.Offset = 0;
arg.Length = self.size.try_into()?;
check_ntstatus!(unsafe {
D3DKMTInvalidateCache(&mut arg as *mut D3DKMT_INVALIDATECACHE)
})?;
} else if (sync_flags & MAGMA_SYNC_RANGES) != 0 {
for r in ranges {
arg.Offset = r.offset.try_into()?;
arg.Length = r.size.try_into()?;
check_ntstatus!(unsafe {
D3DKMTInvalidateCache(&mut arg as *mut D3DKMT_INVALIDATECACHE)
})?;
}
}
Ok(())
}
fn flush(&self, _sync_flags: u64, _ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()> {
Ok(())
}
}
impl Drop for WddmBuffer {
fn drop(&mut self) {
// Safe because const arg is allocated locally on the stack and we trust the D3DKMT API
// not to modify any other memory.
let arg = D3DKMT_DESTROYALLOCATION2 {
hDevice: self.device.as_wddm_handle(),
hResource: Default::default(),
phAllocationList: &self.handle as *const D3dkmtHandle,
AllocationCount: 1,
Flags: D3DDDICB_DESTROYALLOCATION2FLAGS {
Anonymous: D3DDDICB_DESTROYALLOCATION2FLAGS_0 {
Value: Default::default(),
},
},
};
log_ntstatus!(unsafe { D3DKMTDestroyAllocation2(&arg as *const D3DKMT_DESTROYALLOCATION2) })
}
}
impl Buffer for WddmBuffer {}
unsafe impl Send for WddmDevice {}
unsafe impl Sync for WddmDevice {}
unsafe impl Send for WddmContext {}
unsafe impl Sync for WddmContext {}
unsafe impl Send for WddmBuffer {}
unsafe impl Sync for WddmBuffer {}

View file

@ -0,0 +1,25 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
#[macro_export]
macro_rules! check_ntstatus {
($x: expr) => {{
match $x {
windows_sys::Win32::Foundation::STATUS_SUCCESS => Ok(()),
e => {
let error = rustix::io::Errno::from_raw_os_error(e);
Err(MesaError::RustixError(error))
}
}
}};
}
#[macro_export]
macro_rules! log_ntstatus {
($x: expr) => {{
match $x {
windows_sys::Win32::Foundation::STATUS_SUCCESS => (),
e => error!("logging error status: {:#X}", e),
}
}};
}

View file

@ -0,0 +1,13 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
mod amd;
mod d3dkmt_common;
mod macros;
mod wddm;
pub use amd::Amd;
pub use d3dkmt_common::WindowsDevice as PlatformDevice;
pub use d3dkmt_common::WindowsPhysicalDevice as PlatformPhysicalDevice;
pub use wddm::enumerate_devices;
pub use wddm::VendorPrivateData;

View file

@ -0,0 +1,39 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use mesa3d_util::MesaResult;
use std::sync::Arc;
use crate::magma::MagmaPhysicalDevice;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaMemoryProperties;
use crate::sys::windows::d3dkmt_common;
pub trait VendorPrivateData {
fn createallocation_pdata(&self) -> Vec<u32> {
Vec::new()
}
fn allocationinfo2_pdata(
&self,
_create_info: &MagmaCreateBufferInfo,
_mem_props: &MagmaMemoryProperties,
) -> Vec<u32> {
Vec::new()
}
}
pub fn enumerate_devices() -> MesaResult<Vec<MagmaPhysicalDevice>> {
let mut devices: Vec<MagmaPhysicalDevice> = Vec::new();
let adapters = d3dkmt_common::enumerate_adapters()?;
for (adapter, pci_info, pci_bus_info) in adapters {
devices.push(MagmaPhysicalDevice::new(
Arc::new(adapter),
pci_info,
pci_bus_info,
));
}
Ok(devices)
}

67
src/magma/traits.rs Normal file
View file

@ -0,0 +1,67 @@
// Copyright 2025 Android Open Source Project
// SPDX-License-Identifier: MIT
use std::sync::Arc;
use mesa3d_util::MappedRegion;
use mesa3d_util::MesaHandle;
use mesa3d_util::MesaResult;
use virtgpu_kumquat::VirtGpuKumquat;
use crate::magma_defines::MagmaCreateBufferInfo;
use crate::magma_defines::MagmaHeapBudget;
use crate::magma_defines::MagmaImportHandleInfo;
use crate::magma_defines::MagmaMappedMemoryRange;
use crate::magma_defines::MagmaMemoryProperties;
use crate::magma_defines::MagmaPciInfo;
use crate::sys::platform::PlatformDevice;
use crate::sys::platform::PlatformPhysicalDevice;
pub trait AsVirtGpu {
fn as_virtgpu(&self) -> Option<&VirtGpuKumquat> {
None
}
}
pub trait GenericPhysicalDevice {
fn create_device(
&self,
physical_device: &Arc<dyn PhysicalDevice>,
pci_info: &MagmaPciInfo,
) -> MesaResult<Arc<dyn Device>>;
}
pub trait GenericDevice {
fn get_memory_properties(&self) -> MesaResult<MagmaMemoryProperties>;
fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult<MagmaHeapBudget>;
fn create_context(&self, device: &Arc<dyn Device>) -> MesaResult<Arc<dyn Context>>;
fn create_buffer(
&self,
device: &Arc<dyn Device>,
create_info: &MagmaCreateBufferInfo,
) -> MesaResult<Arc<dyn Buffer>>;
fn import(
&self,
_device: &Arc<dyn Device>,
_info: MagmaImportHandleInfo,
) -> MesaResult<Arc<dyn Buffer>>;
}
pub trait GenericBuffer {
fn map(&self, buffer: &Arc<dyn Buffer>) -> MesaResult<Arc<dyn MappedRegion>>;
fn export(&self) -> MesaResult<MesaHandle>;
fn invalidate(&self, sync_flags: u64, ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()>;
fn flush(&self, sync_flags: u64, ranges: &[MagmaMappedMemoryRange]) -> MesaResult<()>;
}
pub trait PhysicalDevice: PlatformPhysicalDevice + AsVirtGpu + GenericPhysicalDevice {}
pub trait Device: GenericDevice + PlatformDevice {}
pub trait Context {}
pub trait Buffer: GenericBuffer {}

View file

@ -111,6 +111,13 @@ if with_gfxstream_vk
endif
subdir('gfxstream')
endif
if with_magma
subdir('util/rust')
subdir('virtio/protocols')
subdir('virtio/virtgpu_kumquat')
subdir('magma')
subdir('magma/ffi')
endif
if with_gallium_asahi or with_asahi_vk or with_tools.contains('asahi')
subdir('asahi')
endif

View file

@ -3,6 +3,7 @@
use std::ffi::NulError;
use std::io::Error as IoError;
use std::num::ParseIntError;
use std::num::TryFromIntError;
use std::str::Utf8Error;
@ -23,6 +24,9 @@ pub enum MesaError {
/// Nul crate error.
#[error("Nul Error occurred {0}")]
NulError(NulError),
/// An attempted integer parsing failed.
#[error("int parsing failed: {0}")]
ParseIntError(ParseIntError),
/// Rustix crate error.
#[error("The errno is {0}")]
RustixError(RustixError),
@ -58,6 +62,12 @@ impl From<IoError> for MesaError {
}
}
impl From<ParseIntError> for MesaError {
fn from(e: ParseIntError) -> MesaError {
MesaError::ParseIntError(e)
}
}
impl From<TryFromIntError> for MesaError {
fn from(e: TryFromIntError) -> MesaError {
MesaError::TryFromIntError(e)

View file

@ -0,0 +1,213 @@
// Copyright 2025 Google
// SPDX-License-Identifier: MIT
use mesa3d_util::MesaHandle;
use zerocopy::FromBytes;
use zerocopy::Immutable;
use zerocopy::IntoBytes;
#[repr(C)]
pub struct DeviceId {
pub device_uuid: [u8; 16],
pub driver_uuid: [u8; 16],
}
/// Memory index and physical device id of the associated VkDeviceMemory.
#[derive(
Copy,
Clone,
Debug,
Default,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
FromBytes,
IntoBytes,
Immutable,
)]
#[repr(C)]
pub struct VulkanInfo {
pub memory_idx: u32,
pub device_id: DeviceId,
}
pub const MAGMA_VIRTIO_GET_CAPABILITIES: u32 = 0x100;
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct magma_virtio_ctrl_hdr {
pub type_: u32,
pub payload: u32,
}
/* KUMQUAT_GPU_PROTOCOL_TRANSFER_TO_HOST_3D, KUMQUAT_GPU_PROTOCOL_TRANSFER_FROM_HOST_3D */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_transfer_host_3d {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub box_: kumquat_gpu_protocol_box,
pub offset: u64,
pub level: u32,
pub stride: u32,
pub layer_stride: u32,
pub ctx_id: u32,
pub resource_id: u32,
pub padding: u32,
}
/* KUMQUAT_GPU_PROTOCOL_RESOURCE_CREATE_3D */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_resource_create_3d {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub target: u32,
pub format: u32,
pub bind: u32,
pub width: u32,
pub height: u32,
pub depth: u32,
pub array_size: u32,
pub last_level: u32,
pub nr_samples: u32,
pub flags: u32,
pub size: u32,
pub stride: u32,
pub ctx_id: u32,
}
#[derive(Clone, Debug, Copy, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_ctx_create {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub nlen: u32,
pub context_init: u32,
pub debug_name: [u8; 64],
}
impl Default for kumquat_gpu_protocol_ctx_create {
fn default() -> Self {
// SAFETY: All zero pattern is safe for this particular struct
unsafe { ::std::mem::zeroed() }
}
}
/* KUMQUAT_GPU_PROTOCOL_CTX_ATTACH_RESOURCE, KUMQUAT_GPU_PROTOCOL_CTX_DETACH_RESOURCE */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_ctx_resource {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub ctx_id: u32,
pub resource_id: u32,
}
/* KUMQUAT_GPU_PROTOCOL_SUBMIT_3D */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_cmd_submit {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub ctx_id: u32,
pub pad: u32,
pub size: u32,
// The in-fence IDs are prepended to the cmd_buf and memory layout
// of the KUMQUAT_GPU_PROTOCOL_SUBMIT_3D buffer looks like this:
// _________________
// | CMD_SUBMIT_3D |
// -----------------
// | header |
// | in-fence IDs |
// | cmd_buf |
// -----------------
//
// This makes in-fence IDs naturally aligned to the sizeof(u64) inside
// of the virtio buffer.
pub num_in_fences: u32,
pub flags: u32,
pub ring_idx: u8,
pub padding: [u8; 3],
}
/* KUMQUAT_GPU_PROTOCOL_RESP_CAPSET_INFO */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_resp_capset_info {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub capset_id: u32,
pub version: u32,
pub size: u32,
pub padding: u32,
}
/* KUMQUAT_GPU_PROTOCOL_GET_CAPSET */
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_get_capset {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub capset_id: u32,
pub capset_version: u32,
}
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_resource_create_blob {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub ctx_id: u32,
pub blob_mem: u32,
pub blob_flags: u32,
pub padding: u32,
pub blob_id: u64,
pub size: u64,
}
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_resp_resource_create {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub resource_id: u32,
pub handle_type: u32,
pub vulkan_info: VulkanInfo,
}
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable)]
#[repr(C)]
pub struct kumquat_gpu_protocol_resp_cmd_submit_3d {
pub hdr: kumquat_gpu_protocol_ctrl_hdr,
pub fence_id: u64,
pub handle_type: u32,
pub padding: u32,
}
/// A virtio gpu command and associated metadata specific to each command.
#[derive(Debug)]
pub enum KumquatGpuProtocol {
OkNoData,
GetNumCapsets,
GetCapsetInfo(u32),
GetCapset(kumquat_gpu_protocol_get_capset),
CtxCreate(kumquat_gpu_protocol_ctx_create),
CtxDestroy(u32),
CtxAttachResource(kumquat_gpu_protocol_ctx_resource),
CtxDetachResource(kumquat_gpu_protocol_ctx_resource),
ResourceCreate3d(kumquat_gpu_protocol_resource_create_3d),
TransferToHost3d(kumquat_gpu_protocol_transfer_host_3d, MesaHandle),
TransferFromHost3d(kumquat_gpu_protocol_transfer_host_3d, MesaHandle),
CmdSubmit3d(kumquat_gpu_protocol_cmd_submit, Vec<u8>, Vec<u64>),
ResourceCreateBlob(kumquat_gpu_protocol_resource_create_blob),
SnapshotSave,
SnapshotRestore,
RespNumCapsets(u32),
RespCapsetInfo(kumquat_gpu_protocol_resp_capset_info),
RespCapset(Vec<u8>),
RespContextCreate(u32),
RespResourceCreate(kumquat_gpu_protocol_resp_resource_create, MesaHandle),
RespCmdSubmit3d(u64, MesaHandle),
RespOkSnapshot,
}
pub enum KumquatGpuProtocolWrite<T: IntoBytes + FromBytes + Immutable> {
Cmd(T),
CmdWithHandle(T, MesaHandle),
CmdWithData(T, Vec<u8>),
}

View file

@ -11,6 +11,9 @@ project(
windows_link = subproject('windows-link-0.2-rs').get_variable('lib')
windows_sys_args = [
'--cfg', 'feature="Wdk"',
'--cfg', 'feature="Wdk_Graphics"',
'--cfg', 'feature="Wdk_Graphics_Direct3D"',
'--cfg', 'feature="Win32"',
'--cfg', 'feature="Win32_Networking"',
'--cfg', 'feature="Win32_Networking_WinSock"',