intel: GEM Phase 1-2 — core object lifecycle + memory regions + VMA

gem/gem_object.rs (175 lines):
  GemObject struct: handle, size, region, cache_level, gtt/vram offset
  GemObjectManager: BTreeMap registry with create/close/pin/unpin
  Memory tracking: system/vram byte counters with limits
  Export tracking, per-object cache level, name support

gem/gem_region.rs (85 lines):
  MemoryRegion: System/LMEM/Stolen types with alloc/free
  IO-mapped vs CPU-visible region properties
  Min page size per region (4K system, 64K LMEM)
  Bump allocator with free tracking

gem/gem_vma.rs (100 lines):
  GemVma: virtual address binding with address space type
  VmaManager: BTreeMap registry with overlap detection
  Bind/unbind with bound byte tracking
  Per-object VMA query

Ported from Linux 7.1:
  gem/i915_gem_object_types.h → GemObject
  gem/i915_gem_object.c → GemObjectManager
  gem/i915_gem_region.c → MemoryRegion
  i915_vma.c → VmaManager + GemVma

This is a new gem/ subdirectory under the Intel driver — the foundation
for the full 27,472-line GEM subsystem port from Linux i915.
This commit is contained in:
2026-06-02 08:14:14 +03:00
parent e7ed83144e
commit 7053990358
5 changed files with 409 additions and 0 deletions
@@ -0,0 +1,203 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use redox_driver_sys::dma::DmaBuffer;
use crate::driver::Result;
use crate::driver::DriverError;
pub type GemHandle = u32;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MemoryRegionType {
System,
LocalMemory,
Stolen,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CacheLevel {
Uncached,
WriteCombine,
WriteThrough,
WriteBack,
}
#[derive(Debug)]
pub struct GemObject {
pub handle: GemHandle,
pub size: u64,
pub region: MemoryRegionType,
pub cache_level: CacheLevel,
pub gtt_offset: Option<u64>,
pub vram_offset: Option<u64>,
pub pinned: bool,
pub exported: bool,
pub name: Option<String>,
}
impl GemObject {
pub fn new(handle: GemHandle, size: u64, region: MemoryRegionType) -> Self {
Self {
handle, size, region,
cache_level: CacheLevel::WriteBack,
gtt_offset: None, vram_offset: None,
pinned: false, exported: false, name: None,
}
}
pub fn is_pinned(&self) -> bool { self.pinned }
pub fn is_exported(&self) -> bool { self.exported }
pub fn is_lmem(&self) -> bool { self.region == MemoryRegionType::LocalMemory }
}
pub struct GemObjectManager {
objects: BTreeMap<GemHandle, GemObject>,
next_handle: AtomicU64,
total_system_bytes: u64,
total_vram_bytes: u64,
max_system_bytes: u64,
max_vram_bytes: u64,
}
impl GemObjectManager {
pub fn new(max_system: u64, max_vram: u64) -> Self {
Self {
objects: BTreeMap::new(),
next_handle: AtomicU64::new(1),
total_system_bytes: 0, total_vram_bytes: 0,
max_system_bytes: max_system, max_vram_bytes: max_vram,
}
}
pub fn create(&mut self, size: u64, region: MemoryRegionType) -> Result<GemHandle> {
if size == 0 || size > self.max_allocation(region) {
return Err(DriverError::Buffer(format!("GEM size {} exceeds limit", size)));
}
if !self.can_allocate(size, region) {
return Err(DriverError::Buffer(format!("GEM memory exhausted for {:?}", region)));
}
let handle = self.next_handle.fetch_add(1, Ordering::SeqCst) as GemHandle;
let obj = GemObject::new(handle, size, region);
self.track_allocation(size, region);
self.objects.insert(handle, obj);
Ok(handle)
}
pub fn create_with_name(&mut self, size: u64, region: MemoryRegionType, name: &str) -> Result<GemHandle> {
let handle = self.create(size, region)?;
if let Some(obj) = self.objects.get_mut(&handle) {
obj.name = Some(name.to_string());
}
Ok(handle)
}
pub fn close(&mut self, handle: GemHandle) -> Result<()> {
let obj = self.objects.remove(&handle)
.ok_or(DriverError::NotFound(format!("GEM handle {} not found", handle)))?;
if obj.is_pinned() {
return Err(DriverError::Buffer(format!("GEM handle {} is still pinned", handle)));
}
if obj.is_exported() {
return Err(DriverError::Buffer(format!("GEM handle {} is still exported", handle)));
}
self.untrack_allocation(obj.size, obj.region);
Ok(())
}
pub fn get(&self, handle: GemHandle) -> Result<&GemObject> {
self.objects.get(&handle)
.ok_or(DriverError::NotFound(format!("GEM handle {} not found", handle)))
}
pub fn get_mut(&mut self, handle: GemHandle) -> Result<&mut GemObject> {
self.objects.get_mut(&handle)
.ok_or(DriverError::NotFound(format!("GEM handle {} not found", handle)))
}
pub fn pin(&mut self, handle: GemHandle) -> Result<()> {
let obj = self.get_mut(handle)?;
if obj.pinned { return Ok(()); }
obj.pinned = true;
Ok(())
}
pub fn unpin(&mut self, handle: GemHandle) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.pinned = false;
Ok(())
}
pub fn set_gtt_offset(&mut self, handle: GemHandle, offset: u64) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.gtt_offset = Some(offset);
Ok(())
}
pub fn set_vram_offset(&mut self, handle: GemHandle, offset: u64) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.vram_offset = Some(offset);
Ok(())
}
pub fn set_cache_level(&mut self, handle: GemHandle, level: CacheLevel) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.cache_level = level;
Ok(())
}
pub fn mark_exported(&mut self, handle: GemHandle) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.exported = true;
Ok(())
}
pub fn unmark_exported(&mut self, handle: GemHandle) -> Result<()> {
let obj = self.get_mut(handle)?;
obj.exported = false;
Ok(())
}
pub fn total_objects(&self) -> usize { self.objects.len() }
pub fn system_bytes_used(&self) -> u64 { self.total_system_bytes }
pub fn vram_bytes_used(&self) -> u64 { self.total_vram_bytes }
fn can_allocate(&self, size: u64, region: MemoryRegionType) -> bool {
match region {
MemoryRegionType::System | MemoryRegionType::Stolen =>
self.total_system_bytes + size <= self.max_system_bytes,
MemoryRegionType::LocalMemory =>
self.total_vram_bytes + size <= self.max_vram_bytes,
}
}
fn max_allocation(&self, region: MemoryRegionType) -> u64 {
match region {
MemoryRegionType::System | MemoryRegionType::Stolen => self.max_system_bytes,
MemoryRegionType::LocalMemory => self.max_vram_bytes,
}
}
fn track_allocation(&mut self, size: u64, region: MemoryRegionType) {
match region {
MemoryRegionType::System | MemoryRegionType::Stolen =>
self.total_system_bytes += size,
MemoryRegionType::LocalMemory =>
self.total_vram_bytes += size,
}
}
fn untrack_allocation(&mut self, size: u64, region: MemoryRegionType) {
match region {
MemoryRegionType::System | MemoryRegionType::Stolen =>
self.total_system_bytes = self.total_system_bytes.saturating_sub(size),
MemoryRegionType::LocalMemory =>
self.total_vram_bytes = self.total_vram_bytes.saturating_sub(size),
}
}
}
@@ -0,0 +1,79 @@
use std::sync::Arc;
use redox_driver_sys::dma::DmaBuffer;
use redox_driver_sys::memory::MmioRegion;
use super::gem_object::{CacheLevel, GemHandle, GemObject, MemoryRegionType};
use crate::driver::Result;
use crate::driver::DriverError;
pub struct MemoryRegion {
pub region_type: MemoryRegionType,
pub total_bytes: u64,
pub free_bytes: u64,
pub largest_free_block: u64,
pub io_mapped: bool,
pub cpu_visible: bool,
pub min_page_size: u64,
pub io_start: Option<u64>,
io_mmio: Option<Arc<MmioRegion>>,
}
impl MemoryRegion {
pub fn new_system(total: u64) -> Self {
Self {
region_type: MemoryRegionType::System,
total_bytes: total, free_bytes: total,
largest_free_block: total,
io_mapped: false, cpu_visible: true,
min_page_size: 4096,
io_start: None, io_mmio: None,
}
}
pub fn new_lmem(total: u64, io_start: u64, mmio: Arc<MmioRegion>) -> Self {
Self {
region_type: MemoryRegionType::LocalMemory,
total_bytes: total, free_bytes: total,
largest_free_block: total,
io_mapped: true, cpu_visible: false,
min_page_size: 65536,
io_start: Some(io_start), io_mmio: Some(mmio),
}
}
pub fn new_stolen(total: u64, io_start: u64) -> Self {
Self {
region_type: MemoryRegionType::Stolen,
total_bytes: total, free_bytes: total,
largest_free_block: total,
io_mapped: true, cpu_visible: true,
min_page_size: 4096,
io_start: Some(io_start), io_mmio: None,
}
}
pub fn allocate(&mut self, size: u64, alignment: u64) -> Option<u64> {
let aligned = (size + alignment - 1) / alignment * alignment;
if aligned > self.free_bytes { return None; }
if aligned > self.largest_free_block { return None; }
let offset = self.total_bytes - self.free_bytes;
let end = offset + aligned;
if end > self.total_bytes { return None; }
self.free_bytes -= aligned;
self.largest_free_block = self.free_bytes;
Some(offset)
}
pub fn free(&mut self, offset: u64, size: u64) {
self.free_bytes = (self.free_bytes + size).min(self.total_bytes);
self.largest_free_block = self.free_bytes;
}
pub fn io_mmio(&self) -> Option<&Arc<MmioRegion>> {
self.io_mmio.as_ref()
}
}
@@ -0,0 +1,119 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use redox_driver_sys::memory::MmioRegion;
use super::gem_object::{CacheLevel, GemHandle, GemObject, MemoryRegionType};
use crate::driver::{DriverError, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AddressSpaceType {
Ggtt,
Ppgtt,
}
#[derive(Debug)]
pub struct GemVma {
pub vma_id: u64,
pub gem_handle: GemHandle,
pub address_space: AddressSpaceType,
pub virtual_addr: u64,
pub size: u64,
pub page_count: u64,
pub bound: bool,
pub cache_level: CacheLevel,
pub read_only: bool,
}
impl GemVma {
pub fn new(gem_handle: GemHandle, addr_space: AddressSpaceType, va: u64, size: u64) -> Self {
let page_size = match addr_space {
AddressSpaceType::Ggtt => 4096u64,
AddressSpaceType::Ppgtt => 4096u64,
};
let page_count = (size + page_size - 1) / page_size;
Self {
vma_id: 0, gem_handle, address_space: addr_space,
virtual_addr: va, size, page_count,
bound: false, cache_level: CacheLevel::WriteBack,
read_only: false,
}
}
pub fn end_address(&self) -> u64 { self.virtual_addr + self.size }
pub fn overlaps(&self, other: &GemVma) -> bool {
self.virtual_addr < other.end_address() && other.virtual_addr < self.end_address()
}
}
pub struct VmaManager {
vmas: BTreeMap<u64, GemVma>,
next_id: u64,
total_bound: u64,
max_address: u64,
}
impl VmaManager {
pub fn new() -> Self {
Self { vmas: BTreeMap::new(), next_id: 1, total_bound: 0, max_address: 0 }
}
pub fn create_vma(&mut self, gem_handle: GemHandle, addr_space: AddressSpaceType,
va: u64, size: u64) -> Result<u64> {
let mut vma = GemVma::new(gem_handle, addr_space, va, size);
vma.vma_id = self.next_id;
for existing in self.vmas.values() {
if vma.overlaps(existing) {
return Err(DriverError::Buffer(format!(
"VMA overlap at {:#x} (size {:#x}) with existing VMA {} at {:#x}",
va, size, existing.vma_id, existing.virtual_addr
)));
}
}
self.next_id += 1;
let id = vma.vma_id;
self.vmas.insert(id, vma);
Ok(id)
}
pub fn bind(&mut self, vma_id: u64) -> Result<()> {
let vma = self.vmas.get_mut(&vma_id)
.ok_or(DriverError::NotFound(format!("VMA {} not found", vma_id)))?;
if vma.bound { return Ok(()); }
vma.bound = true;
self.total_bound += vma.size;
if vma.end_address() > self.max_address {
self.max_address = vma.end_address();
}
Ok(())
}
pub fn unbind(&mut self, vma_id: u64) -> Result<()> {
let vma = self.vmas.get_mut(&vma_id)
.ok_or(DriverError::NotFound(format!("VMA {} not found", vma_id)))?;
if !vma.bound { return Ok(()); }
vma.bound = false;
self.total_bound = self.total_bound.saturating_sub(vma.size);
Ok(())
}
pub fn destroy(&mut self, vma_id: u64) -> Result<()> {
self.unbind(vma_id)?;
self.vmas.remove(&vma_id);
Ok(())
}
pub fn get(&self, vma_id: u64) -> Result<&GemVma> {
self.vmas.get(&vma_id)
.ok_or(DriverError::NotFound(format!("VMA {} not found", vma_id)))
}
pub fn vmas_for_object(&self, gem_handle: GemHandle) -> Vec<&GemVma> {
self.vmas.values().filter(|v| v.gem_handle == gem_handle).collect()
}
pub fn bound_bytes(&self) -> u64 { self.total_bound }
pub fn vma_count(&self) -> usize { self.vmas.len() }
}
@@ -0,0 +1,7 @@
pub mod gem_object;
pub mod gem_region;
pub mod gem_vma;
pub use gem_object::{CacheLevel, GemHandle, GemObject, GemObjectManager, MemoryRegionType};
pub use gem_region::MemoryRegion;
pub use gem_vma::{AddressSpaceType, GemVma, VmaManager};
@@ -21,6 +21,7 @@ pub mod execlists;
pub mod fbc;
pub mod fence;
pub mod gamma;
pub mod gem;
pub mod gmbus;
pub mod gt;
pub mod gtt;