diff --git a/include/meson.build b/include/meson.build index 397d4b424d9..38a0e1cff14 100644 --- a/include/meson.build +++ b/include/meson.build @@ -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 diff --git a/meson.build b/meson.build index fc83a9869b0..bc2b29b1eee 100644 --- a/meson.build +++ b/meson.build @@ -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') diff --git a/meson.options b/meson.options index cf28ff85380..38aeeb423ed 100644 --- a/meson.options +++ b/meson.options @@ -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' +) diff --git a/src/magma/ffi/include/.clang-format b/src/magma/ffi/include/.clang-format new file mode 100644 index 00000000000..05120f01783 --- /dev/null +++ b/src/magma/ffi/include/.clang-format @@ -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 diff --git a/src/magma/ffi/include/magma.h b/src/magma/ffi/include/magma.h new file mode 100644 index 00000000000..5efa1503a17 --- /dev/null +++ b/src/magma/ffi/include/magma.h @@ -0,0 +1,63 @@ +/* + * Copyright 2025 Mesa3D authors + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#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 diff --git a/src/magma/ffi/include/magma_defines.h b/src/magma/ffi/include/magma_defines.h new file mode 100644 index 00000000000..abe9472e315 --- /dev/null +++ b/src/magma/ffi/include/magma_defines.h @@ -0,0 +1,63 @@ +/* + * Copyright 2025 Mesa3D authors + * SPDX-License-Identifier: MIT + */ + +#ifndef MAGMA_DEFINES_H +#define MAGMA_DEFINES_H + +#include + +#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 diff --git a/src/magma/ffi/lib.rs b/src/magma/ffi/lib.rs new file mode 100644 index 00000000000..583d1bd7638 --- /dev/null +++ b/src/magma/ffi/lib.rs @@ -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(result: MesaResult) -> 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) +} diff --git a/src/magma/ffi/meson.build b/src/magma/ffi/meson.build new file mode 100644 index 00000000000..b23c445a8e5 --- /dev/null +++ b/src/magma/ffi/meson.build @@ -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] +) diff --git a/src/magma/lib.rs b/src/magma/lib.rs new file mode 100644 index 00000000000..bbc31a13cf7 --- /dev/null +++ b/src/magma/lib.rs @@ -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; diff --git a/src/magma/magma.rs b/src/magma/magma.rs new file mode 100644 index 00000000000..5f7e5cb3756 --- /dev/null +++ b/src/magma/magma.rs @@ -0,0 +1,291 @@ +// Copyright 2025 Google +// SPDX-License-Identifier: MIT + +//! Magma: Rust implementation of Fuchsia's driver model. +//! +//! Design found at . + +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, + pci_info: MagmaPciInfo, + pci_bus_info: MagmaPciBusInfo, +} + +#[derive(Clone)] +pub struct MagmaDevice { + device: Arc, +} + +#[derive(Clone)] +pub struct MagmaContext { + _context: Arc, +} + +#[derive(Clone)] +pub struct MagmaBuffer { + buffer: Arc, +} + +pub fn magma_enumerate_devices() -> MagmaResult> { + 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, + pci_info: MagmaPciInfo, + pci_bus_info: MagmaPciBusInfo, + ) -> MagmaPhysicalDevice { + MagmaPhysicalDevice { + physical_device, + pci_info, + pci_bus_info, + } + } + + pub fn create_device(&self) -> MagmaResult { + 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, + resources: Vec, + wait_semaphores: Vec, + signal_semaphores: Vec, +} + +#[allow(dead_code)] +struct MagmaInlineCommandBuffer { + data: Vec, + wait_semaphores: Vec, + signal_semaphores: Vec, +} + +impl MagmaDevice { + pub fn get_memory_properties(&self) -> MagmaResult { + let mem_props = self.device.get_memory_properties()?; + Ok(mem_props) + } + + pub fn get_memory_budget(&self, heap_idx: u32) -> MagmaResult { + let budget = self.device.get_memory_budget(heap_idx)?; + Ok(budget) + } + + pub fn create_context(&self) -> MagmaResult { + let context = self.device.create_context(&self.device)?; + Ok(MagmaContext { _context: context }) + } + + pub fn create_buffer(&self, create_info: &MagmaCreateBufferInfo) -> MagmaResult { + 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 { + let buffer = self.device.import(&self.device, info)?; + Ok(MagmaBuffer { buffer }) + } +} + +impl MagmaBuffer { + pub fn map(&self) -> MagmaResult> { + let region = self.buffer.map(&self.buffer)?; + Ok(region) + } + + pub fn export(&self) -> MagmaResult { + 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 { + Err(MagmaError::Unimplemented) + } + + pub fn execute_immediate_commands( + _connection: &MagmaPhysicalDevice, + _wait_semaphores: Vec, + _signal_semaphore: Vec, + ) -> MagmaResult { + Err(MagmaError::Unimplemented) + } + + pub fn raw_handle() -> MagmaResult { + Err(MagmaError::Unimplemented) + } +} + +#[cfg(test)] +mod tests { + use crate::*; + + fn get_physical_device() -> Option { + 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 = 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(); + } +} diff --git a/src/magma/magma_defines.rs b/src/magma/magma_defines.rs new file mode 100644 index 00000000000..9f6e1ff4d6f --- /dev/null +++ b/src/magma/magma_defines.rs @@ -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 for MagmaError { + fn from(e: MesaError) -> MagmaError { + MagmaError::MesaError(e) + } +} + +pub type MagmaResult = std::result::Result; + +#[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, +} diff --git a/src/magma/magma_kumquat.rs b/src/magma/magma_kumquat.rs new file mode 100644 index 00000000000..fc2690c6ec0 --- /dev/null +++ b/src/magma/magma_kumquat.rs @@ -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 { + 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, + _pci_info: &MagmaPciInfo, + ) -> MesaResult> { + let _virtgpu = physical_device.as_virtgpu().unwrap(); + Err(MesaError::Unsupported) + } +} + +impl GenericDevice for MagmaKumquat { + fn get_memory_properties(&self) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn create_context(&self, _device: &Arc) -> MesaResult> { + Err(MesaError::Unsupported) + } + + fn create_buffer( + &self, + _device: &Arc, + _create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + Err(MesaError::Unsupported) + } + + fn import( + &self, + _device: &Arc, + _info: MagmaImportHandleInfo, + ) -> MesaResult> { + Err(MesaError::Unsupported) + } +} + +pub fn enumerate_devices() -> MesaResult> { + let pci_info: MagmaPciInfo = Default::default(); + let pci_bus_info: MagmaPciBusInfo = Default::default(); + let mut devices: Vec = 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) +} diff --git a/src/magma/meson.build b/src/magma/meson.build new file mode 100644 index 00000000000..ece52a61fdc --- /dev/null +++ b/src/magma/meson.build @@ -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] +) diff --git a/src/magma/sys/linux/amdgpu.rs b/src/magma/sys/linux/amdgpu.rs new file mode 100644 index 00000000000..22f01fa49ad --- /dev/null +++ b/src/magma/sys/linux/amdgpu.rs @@ -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, + mem_props: MagmaMemoryProperties, +} + +struct AmdGpuContext { + physical_device: Arc, + context_id: u32, +} + +struct AmdGpuBuffer { + physical_device: Arc, + gem_handle: u32, + size: usize, +} + +impl AmdGpu { + pub fn new(physical_device: Arc) -> MesaResult { + 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 { + Ok(self.mem_props.clone()) + } + + fn get_memory_budget(&self, heap_idx: u32) -> MesaResult { + 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) -> MesaResult> { + let ctx = AmdGpuContext::new(self.physical_device.clone(), 0)?; + Ok(Arc::new(ctx)) + } + + fn create_buffer( + &self, + _device: &Arc, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + let buf = AmdGpuBuffer::new(self.physical_device.clone(), create_info, &self.mem_props)?; + Ok(Arc::new(buf)) + } + + fn import( + &self, + _device: &Arc, + info: MagmaImportHandleInfo, + ) -> MesaResult> { + 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, _priority: i32) -> MesaResult { + 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, + create_info: &MagmaCreateBufferInfo, + mem_props: &MagmaMemoryProperties, + ) -> MesaResult { + 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, + gem_handle: u32, + size: usize, + ) -> MesaResult { + Ok(AmdGpuBuffer { + physical_device, + gem_handle, + size, + }) + } +} + +impl GenericBuffer for AmdGpuBuffer { + fn map(&self, _buffer: &Arc) -> MesaResult> { + 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 { + 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 {} diff --git a/src/magma/sys/linux/bindings/amdgpu_bindings.rs b/src/magma/sys/linux/bindings/amdgpu_bindings.rs new file mode 100644 index 00000000000..95f6f92cdd6 --- /dev/null +++ b/src/magma/sys/linux/bindings/amdgpu_bindings.rs @@ -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")); diff --git a/src/magma/sys/linux/bindings/drm_bindings.rs b/src/magma/sys/linux/bindings/drm_bindings.rs new file mode 100644 index 00000000000..4f49d7e11f4 --- /dev/null +++ b/src/magma/sys/linux/bindings/drm_bindings.rs @@ -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")); diff --git a/src/magma/sys/linux/bindings/i915_binding.rs b/src/magma/sys/linux/bindings/i915_binding.rs new file mode 100644 index 00000000000..4bba749ecb7 --- /dev/null +++ b/src/magma/sys/linux/bindings/i915_binding.rs @@ -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::*; diff --git a/src/magma/sys/linux/bindings/i915_bindings.rs b/src/magma/sys/linux/bindings/i915_bindings.rs new file mode 100644 index 00000000000..fc4ccc6403d --- /dev/null +++ b/src/magma/sys/linux/bindings/i915_bindings.rs @@ -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")); diff --git a/src/magma/sys/linux/bindings/mod.rs b/src/magma/sys/linux/bindings/mod.rs new file mode 100644 index 00000000000..f105117655e --- /dev/null +++ b/src/magma/sys/linux/bindings/mod.rs @@ -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; diff --git a/src/magma/sys/linux/bindings/msm_bindings.rs b/src/magma/sys/linux/bindings/msm_bindings.rs new file mode 100644 index 00000000000..e4446803245 --- /dev/null +++ b/src/magma/sys/linux/bindings/msm_bindings.rs @@ -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")); diff --git a/src/magma/sys/linux/bindings/xe_bindings.rs b/src/magma/sys/linux/bindings/xe_bindings.rs new file mode 100644 index 00000000000..ae4dd7edae3 --- /dev/null +++ b/src/magma/sys/linux/bindings/xe_bindings.rs @@ -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")); diff --git a/src/magma/sys/linux/common.rs b/src/magma/sys/linux/common.rs new file mode 100644 index 00000000000..694768a827e --- /dev/null +++ b/src/magma/sys/linux/common.rs @@ -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> { + None + } + + fn as_raw_descriptor(&self) -> RawDescriptor { + -1 + } + + fn cpu_map(&self, _offset: u64, _size: usize) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn export(&self, _gem_handle: u32) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn import(&self, _handle: MesaHandle) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn close(&self, _gem_handle: u32) {} +} + +impl GenericPhysicalDevice for LinuxPhysicalDevice { + fn create_device( + &self, + physical_device: &Arc, + pci_info: &MagmaPciInfo, + ) -> MesaResult> { + let device: Arc = 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 { + 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> { + 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::from_offset(&self.descriptor, offset.try_into()?, size) + } + + fn export(&self, gem_handle: u32) -> MesaResult { + 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 { + 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 { + 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> { + let mut devices: Vec = 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::()?; + pci_bus_info.bus = v[2].parse::()?; + pci_bus_info.device = v[3].parse::()?; + pci_bus_info.function = v[4].parse::()?; + } + } + + 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 {} diff --git a/src/magma/sys/linux/drm.rs b/src/magma/sys/linux/drm.rs new file mode 100644 index 00000000000..61eaf0df76b --- /dev/null +++ b/src/magma/sys/linux/drm.rs @@ -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 { + 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 = 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")) +} diff --git a/src/magma/sys/linux/flexible_array.rs b/src/magma/sys/linux/flexible_array.rs new file mode 100644 index 00000000000..66483090ca8 --- /dev/null +++ b/src/magma/sys/linux/flexible_array.rs @@ -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 +//! . A flexible array struct is of the form: +//! +//! ```ignore +//! #[repr(C)] +//! struct T { +//! some_data: u32, +//! nents: u32, +//! entries: __IncompleteArrayField, +//! } +//! ``` +//! 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` with a size in bytes at least as large as `size_in_bytes`. +fn vec_with_size_in_bytes(size_in_bytes: usize) -> Vec { + let rounded_size = size_in_bytes.div_ceil(size_of::()); + 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::()` 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` is created. Only the first element of `Vec` would actually +/// be used as a `Foo`. The remaining memory in the `Vec` is for `entries`, which must be +/// contiguous with `Foo`. This function is used to make the `Vec` with enough space for +/// `count` entries. +fn vec_with_array_field(count: usize) -> Vec { + let element_space = count * size_of::(); + let vec_size_bytes = size_of::() + 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 { + /// 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 { + entries: Vec, + phantom: PhantomData, + 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 FlexibleArrayWrapper +where + T: FlexibleArray + 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 { + let mut entries = vec_with_array_field::(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 { + let array_size = total_size - size_of::(); + let num_elements = array_size.div_ceil(size_of::()); + + FlexibleArrayWrapper::::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] + } +} diff --git a/src/magma/sys/linux/i915.rs b/src/magma/sys/linux/i915.rs new file mode 100644 index 00000000000..e172d37e6ec --- /dev/null +++ b/src/magma/sys/linux/i915.rs @@ -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( + physical_device: &Arc, + query_id: u64, +) -> MesaResult> +where + T: FlexibleArray + 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::::from_total_size(0)); + } + + let mut wrapper = FlexibleArrayWrapper::::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, +) -> MesaResult { + let query_mem_regions = i915_query::( + 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, + mem_props: MagmaMemoryProperties, +} + +struct I915Context { + physical_device: Arc, + context_id: u32, +} + +struct I915Buffer { + physical_device: Arc, + gem_handle: u32, + size: usize, +} + +impl I915 { + pub fn new(physical_device: Arc) -> MesaResult { + 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 { + Ok(self.mem_props.clone()) + } + + fn get_memory_budget(&self, heap_idx: u32) -> MesaResult { + 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) -> MesaResult> { + let ctx = I915Context::new(self.physical_device.clone())?; + Ok(Arc::new(ctx)) + } + + fn create_buffer( + &self, + _device: &Arc, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + let buf = I915Buffer::new(self.physical_device.clone(), create_info)?; + Ok(Arc::new(buf)) + } + + fn import( + &self, + _device: &Arc, + info: MagmaImportHandleInfo, + ) -> MesaResult> { + 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) -> MesaResult { + 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, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult { + 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, + gem_handle: u32, + size: usize, + ) -> MesaResult { + Ok(I915Buffer { + physical_device, + gem_handle, + size, + }) + } +} + +impl GenericBuffer for I915Buffer { + fn map(&self, _buffer: &Arc) -> MesaResult> { + 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 { + 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 {} diff --git a/src/magma/sys/linux/macros.rs b/src/magma/sys/linux/macros.rs new file mode 100644 index 00000000000..ed179058e58 --- /dev/null +++ b/src/magma/sys/linux/macros.rs @@ -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::::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::::new(data), + )?) + } + }; +} diff --git a/src/magma/sys/linux/mod.rs b/src/magma/sys/linux/mod.rs new file mode 100644 index 00000000000..9a0a561c3b7 --- /dev/null +++ b/src/magma/sys/linux/mod.rs @@ -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; diff --git a/src/magma/sys/linux/msm.rs b/src/magma/sys/linux/msm.rs new file mode 100644 index 00000000000..962c3f2ef8e --- /dev/null +++ b/src/magma/sys/linux/msm.rs @@ -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, + 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, + mem_props: MagmaMemoryProperties, +} + +struct MsmBuffer { + physical_device: Arc, + gem_handle: u32, + size: usize, +} + +impl Msm { + pub fn new(physical_device: Arc) -> Msm { + Msm { + physical_device, + mem_props: Default::default(), + } + } +} + +impl GenericDevice for Msm { + fn get_memory_properties(&self) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult { + Err(MesaError::Unsupported) + } + + fn create_context(&self, _device: &Arc) -> MesaResult> { + 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, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + let buf = MsmBuffer::new(self.physical_device.clone(), create_info, &self.mem_props)?; + Ok(Arc::new(buf)) + } + + fn import( + &self, + _device: &Arc, + info: MagmaImportHandleInfo, + ) -> MesaResult> { + 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, + create_info: &MagmaCreateBufferInfo, + _mem_props: &MagmaMemoryProperties, + ) -> MesaResult { + 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, + gem_handle: u32, + size: usize, + ) -> MesaResult { + Ok(MsmBuffer { + physical_device, + gem_handle, + size, + }) + } +} + +impl GenericBuffer for MsmBuffer { + fn map(&self, _buffer: &Arc) -> MesaResult> { + 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 { + 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 {} diff --git a/src/magma/sys/linux/xe.rs b/src/magma/sys/linux/xe.rs new file mode 100644 index 00000000000..214d788887f --- /dev/null +++ b/src/magma/sys/linux/xe.rs @@ -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, + _gtt_size: u64, + _mem_alignment: u64, + mem_props: MagmaMemoryProperties, + sysmem_instance: u16, + vram_instance: u16, +} + +struct XeBuffer { + physical_device: Arc, + gem_handle: u32, + size: usize, +} + +struct XeContext { + physical_device: Arc, + vm_id: u32, +} + +fn xe_device_query( + physical_device: &Arc, + query_id: u32, +) -> MesaResult> +where + T: FlexibleArray + 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::::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 { + 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) -> MesaResult { + let mut memory_info: XeMemoryInfo = Default::default(); + let query_mem_regions = xe_device_query::( + 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, + pci_info: &MagmaPciInfo, + ) -> MesaResult { + let _graphics_version = determine_graphics_version(pci_info.device_id)?; + let mut mem_props: MagmaMemoryProperties = Default::default(); + + let query_config = xe_device_query::( + &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 { + Ok(self.mem_props.clone()) + } + + fn get_memory_budget(&self, heap_idx: u32) -> MesaResult { + 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) -> MesaResult> { + let ctx = XeContext::new(self.physical_device.clone(), 0)?; + Ok(Arc::new(ctx)) + } + + fn create_buffer( + &self, + _device: &Arc, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + 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, + info: MagmaImportHandleInfo, + ) -> MesaResult> { + 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, _priority: i32) -> MesaResult { + 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, + create_info: &MagmaCreateBufferInfo, + mem_props: &MagmaMemoryProperties, + sysmem_instance: u16, + vram_instance: u16, + ) -> MesaResult { + 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, + gem_handle: u32, + size: usize, + ) -> MesaResult { + Ok(XeBuffer { + physical_device, + gem_handle, + size, + }) + } +} + +impl GenericBuffer for XeBuffer { + fn map(&self, _buffer: &Arc) -> MesaResult> { + 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 { + 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 {} diff --git a/src/magma/sys/mod.rs b/src/magma/sys/mod.rs new file mode 100644 index 00000000000..1f9a96b01c7 --- /dev/null +++ b/src/magma/sys/mod.rs @@ -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"); + } +} diff --git a/src/magma/sys/windows/amd.rs b/src/magma/sys/windows/amd.rs new file mode 100644 index 00000000000..c3a595ad07b --- /dev/null +++ b/src/magma/sys/windows/amd.rs @@ -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 { + Vec::from(AMD_CREATE_ALLOC_PDATA) + } + + fn allocationinfo2_pdata( + &self, + create_info: &MagmaCreateBufferInfo, + mem_props: &MagmaMemoryProperties, + ) -> Vec { + 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) + } +} diff --git a/src/magma/sys/windows/d3dkmt_common.rs b/src/magma/sys/windows/d3dkmt_common.rs new file mode 100644 index 00000000000..648a6639a00 --- /dev/null +++ b/src/magma/sys/windows/d3dkmt_common.rs @@ -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, + vendor_private_data: Box, + mem_props: MagmaMemoryProperties, +} + +pub struct WddmBuffer { + handle: D3dkmtHandle, + device: Arc, + size: u64, +} + +pub struct WddmContext { + handle: D3dkmtHandle, + _device: Arc, +} + +struct WddmMapping { + _buffer: Arc, + 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::() 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::() 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::() 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::() 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::() 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(®istry_info.AdapterString[0] as *const u16) }; + let chip_type_len = unsafe { wcslen(®istry_info.ChipType[0] as *const u16) }; + let adapter_name_slice: &[u16] = unsafe { + from_raw_parts( + ®istry_info.AdapterString[0] as *const _, + adapter_name_len, + ) + }; + let chip_type_slice: &[u16] = + unsafe { from_raw_parts(®istry_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, + pci_info: &MagmaPciInfo, + ) -> MesaResult> { + 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> { + 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, + vendor_private_data: Box, + ) -> MesaResult { + 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 { + Ok(self.mem_props.clone()) + } + + fn get_memory_budget(&self, heap_idx: u32) -> MesaResult { + 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::(), + 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) -> MesaResult> { + let ctx = WddmContext::new(device.clone())?; + Ok(Arc::new(ctx)) + } + + fn create_buffer( + &self, + device: &Arc, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult> { + let buf = WddmBuffer::new(device.clone(), create_info, &self.mem_props)?; + Ok(Arc::new(buf)) + } + + fn import( + &self, + device: &Arc, + info: MagmaImportHandleInfo, + ) -> MesaResult> { + 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) -> MesaResult { + // 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::(), + 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, + create_info: &MagmaCreateBufferInfo, + mem_props: &MagmaMemoryProperties, + ) -> MesaResult { + 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 = vendor_private_data.createallocation_pdata(); + let mut allocationinfo2: Vec = + vendor_private_data.allocationinfo2_pdata(create_info, mem_props); + + let size_create_allocation: usize = create_allocation.len() * std::mem::size_of::(); + let size_allocationinfo2: usize = allocationinfo2.len() * std::mem::size_of::(); + + 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::(), + 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::(), // 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, + handle: D3dkmtHandle, + size: u64, + ) -> MesaResult { + 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) -> MesaResult> { + 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 { + 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 {} diff --git a/src/magma/sys/windows/macros.rs b/src/magma/sys/windows/macros.rs new file mode 100644 index 00000000000..276a8f84915 --- /dev/null +++ b/src/magma/sys/windows/macros.rs @@ -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), + } + }}; +} diff --git a/src/magma/sys/windows/mod.rs b/src/magma/sys/windows/mod.rs new file mode 100644 index 00000000000..be86026e6b5 --- /dev/null +++ b/src/magma/sys/windows/mod.rs @@ -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; diff --git a/src/magma/sys/windows/wddm.rs b/src/magma/sys/windows/wddm.rs new file mode 100644 index 00000000000..91f0280caeb --- /dev/null +++ b/src/magma/sys/windows/wddm.rs @@ -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 { + Vec::new() + } + + fn allocationinfo2_pdata( + &self, + _create_info: &MagmaCreateBufferInfo, + _mem_props: &MagmaMemoryProperties, + ) -> Vec { + Vec::new() + } +} + +pub fn enumerate_devices() -> MesaResult> { + let mut devices: Vec = 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) +} diff --git a/src/magma/traits.rs b/src/magma/traits.rs new file mode 100644 index 00000000000..c66b9d37a61 --- /dev/null +++ b/src/magma/traits.rs @@ -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, + pci_info: &MagmaPciInfo, + ) -> MesaResult>; +} + +pub trait GenericDevice { + fn get_memory_properties(&self) -> MesaResult; + + fn get_memory_budget(&self, _heap_idx: u32) -> MesaResult; + + fn create_context(&self, device: &Arc) -> MesaResult>; + + fn create_buffer( + &self, + device: &Arc, + create_info: &MagmaCreateBufferInfo, + ) -> MesaResult>; + + fn import( + &self, + _device: &Arc, + _info: MagmaImportHandleInfo, + ) -> MesaResult>; +} + +pub trait GenericBuffer { + fn map(&self, buffer: &Arc) -> MesaResult>; + + fn export(&self) -> MesaResult; + + 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 {} diff --git a/src/meson.build b/src/meson.build index f27dae33631..4e177410560 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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 diff --git a/src/util/rust/error.rs b/src/util/rust/error.rs index c2111e071b0..f2a11a5ac1d 100644 --- a/src/util/rust/error.rs +++ b/src/util/rust/error.rs @@ -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 for MesaError { } } +impl From for MesaError { + fn from(e: ParseIntError) -> MesaError { + MesaError::ParseIntError(e) + } +} + impl From for MesaError { fn from(e: TryFromIntError) -> MesaError { MesaError::TryFromIntError(e) diff --git a/src/virtio/protocols/protocols/magma_virtio_protocol.rs b/src/virtio/protocols/protocols/magma_virtio_protocol.rs new file mode 100644 index 00000000000..2a4e6c58107 --- /dev/null +++ b/src/virtio/protocols/protocols/magma_virtio_protocol.rs @@ -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, Vec), + ResourceCreateBlob(kumquat_gpu_protocol_resource_create_blob), + SnapshotSave, + SnapshotRestore, + RespNumCapsets(u32), + RespCapsetInfo(kumquat_gpu_protocol_resp_capset_info), + RespCapset(Vec), + RespContextCreate(u32), + RespResourceCreate(kumquat_gpu_protocol_resp_resource_create, MesaHandle), + RespCmdSubmit3d(u64, MesaHandle), + RespOkSnapshot, +} + +pub enum KumquatGpuProtocolWrite { + Cmd(T), + CmdWithHandle(T, MesaHandle), + CmdWithData(T, Vec), +} diff --git a/subprojects/packagefiles/windows-sys-0.6-rs/meson.build b/subprojects/packagefiles/windows-sys-0.6-rs/meson.build index a38c1267850..391c24930b1 100644 --- a/subprojects/packagefiles/windows-sys-0.6-rs/meson.build +++ b/subprojects/packagefiles/windows-sys-0.6-rs/meson.build @@ -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"',