From 61f758a881554be8d97e092806cbf864773d09e9 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Sun, 31 May 2026 22:59:46 +0300 Subject: [PATCH] amdgpu: VRAM-backed GEM allocator, CS ioctl docs, firmware stub - Add VramManager (vram.rs): bump-allocator with free-list coalescing for BAR2 VRAM aperture. gem_create auto-selects VRAM for scanout buffers (width>0 && height>0) with fallback to system RAM on exhaustion. gem_close frees VRAM when gpu_addr is within BAR2 range. ensure_gem_gpu_mapping detects VRAM-backed buffers and skips GTT mapping. - Add amdgpu_dc_upload_firmware() stub documenting DMUB firmware upload sequence prerequisites (requires Linux DC tree compilation). - Replace generic 'unavailable' CS ioctl/virgl error messages with specific messages documenting what component is needed (amdgpu core driver, Mesa radeonsi/ iris cross-compilation, CS ioctl backend). --- .../gpu/amdgpu/source/amdgpu_redox_main.c | 19 ++ .../gpu/redox-drm/source/src/driver.rs | 63 +++--- .../redox-drm/source/src/drivers/amd/mod.rs | 105 +++++++-- .../redox-drm/source/src/drivers/amd/vram.rs | 199 ++++++++++++++++++ 4 files changed, 331 insertions(+), 55 deletions(-) create mode 100644 local/recipes/gpu/redox-drm/source/src/drivers/amd/vram.rs diff --git a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c index 1b2553649e..1e39ccf99b 100644 --- a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c +++ b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c @@ -371,6 +371,25 @@ int amdgpu_dc_init(void *mmio_base, size_t mmio_size) return ret; } +int amdgpu_dc_upload_firmware(void) +{ + const struct asic_props *props = asic_props(); + + printk("amdgpu_redox: firmware upload stub — DMUB upload requires imported DC tree\n"); + + if (!props) { + pr_err("amdgpu_redox: cannot determine DMUB firmware target without ASIC identification\n"); + return -ENODEV; + } + + printk("amdgpu_redox: DMUB firmware upload not yet implemented for %s.\n", props->name); + printk("amdgpu_redox: Required: compile display/dmub/ from imported Linux DC tree,\n"); + printk("amdgpu_redox: then call dmub_srv_create() / dmub_srv_hw_init() / dmub_srv_cmd_submit().\n"); + printk("amdgpu_redox: Until DC tree is compiled, DMUB-assisted features (PSR, ABM, etc.) are unavailable.\n"); + + return 0; +} + /* Initialize AMD GPU hardware for display */ int amdgpu_redox_init(void *mmio_base, size_t mmio_size, uint64_t fb_phys, size_t fb_size) { diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index cc98faf36e..72b3dd80ba 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -124,7 +124,7 @@ pub trait GpuDriver: Send + Sync { _submit: &RedoxPrivateCsSubmit, ) -> Result { Err(DriverError::Unsupported( - "private command submission is unavailable on this backend", + "GPU command submission requires amdgpu core driver (imported Linux tree not yet compiled; see local/recipes/gpu/amdgpu-source/) and a per-generation ring buffer backend", )) } @@ -133,7 +133,7 @@ pub trait GpuDriver: Send + Sync { _wait: &RedoxPrivateCsWait, ) -> Result { Err(DriverError::Unsupported( - "private command completion waits are unavailable on this backend", + "GPU fence/completion wait requires amdgpu core driver (imported Linux tree not yet compiled)", )) } @@ -142,15 +142,19 @@ pub trait GpuDriver: Send + Sync { } fn virgl_get_capset_info(&self, _capset_index: u32) -> Result { - Err(DriverError::Unsupported("virgl capset info")) + Err(DriverError::Unsupported( + "virgl 3D requires Mesa virgl driver (swrast backend only; gallium-drivers=swrast,virgl not yet fully compiled for Redox target)", + )) } fn virgl_get_capset(&self, _capset_id: u32, _capset_version: u32) -> Result { - Err(DriverError::Unsupported("virgl capset")) + Err(DriverError::Unsupported("virgl capset requires Mesa virgl cross-compilation")) } fn virgl_ctx_create(&self, _ctx_id: u32, _debug_name: &str, _context_init: u32) -> Result<()> { - Err(DriverError::Unsupported("virgl context creation")) + Err(DriverError::Unsupported( + "virgl GL context creation requires Mesa virgl driver + redox-drm CS ioctl backend", + )) } fn virgl_ctx_destroy(&self, _ctx_id: u32) -> Result<()> { @@ -172,56 +176,39 @@ pub trait GpuDriver: Send + Sync { _flags: u32, _ctx_id: u32, ) -> Result<()> { - Err(DriverError::Unsupported("virgl 3D resource creation")) + Err(DriverError::Unsupported( + "virgl 3D resource creation requires Mesa radeonsi/iris cross-compilation for hardware, or llvmpipe for software rendering", + )) } fn virgl_submit_3d(&self, _ctx_id: u32, _command_data: &[u8]) -> Result<()> { - Err(DriverError::Unsupported("virgl submit 3D")) + Err(DriverError::Unsupported( + "virgl command submission requires compiled Mesa gallium driver and CS ioctl backend", + )) } fn virgl_transfer_to_host_3d( &self, - _ctx_id: u32, - _resource_id: u32, - _x: u32, - _y: u32, - _z: u32, - _w: u32, - _h: u32, - _d: u32, - _offset: u64, - _level: u32, - _stride: u32, - _layer_stride: u32, + _ctx_id: u32, _resource_id: u32, _x: u32, _y: u32, _z: u32, + _w: u32, _h: u32, _d: u32, _offset: u64, _level: u32, + _stride: u32, _layer_stride: u32, ) -> Result<()> { - Err(DriverError::Unsupported("virgl transfer to host")) + Err(DriverError::Unsupported("virgl transfer-to-host requires Mesa virgl cross-compilation")) } fn virgl_transfer_from_host_3d( &self, - _ctx_id: u32, - _resource_id: u32, - _x: u32, - _y: u32, - _z: u32, - _w: u32, - _h: u32, - _d: u32, - _offset: u64, - _level: u32, - _stride: u32, - _layer_stride: u32, + _ctx_id: u32, _resource_id: u32, _x: u32, _y: u32, _z: u32, + _w: u32, _h: u32, _d: u32, _offset: u64, _level: u32, + _stride: u32, _layer_stride: u32, ) -> Result<()> { - Err(DriverError::Unsupported("virgl transfer from host")) + Err(DriverError::Unsupported("virgl transfer-from-host requires Mesa virgl cross-compilation")) } fn virgl_resource_attach_backing( - &self, - _resource_id: u32, - _phys_addr: u64, - _length: u32, + &self, _resource_id: u32, _phys_addr: u64, _length: u32, ) -> Result<()> { - Err(DriverError::Unsupported("virgl resource attach backing")) + Err(DriverError::Unsupported("virgl resource attach backing requires Mesa virgl + redox-drm GEM integration")) } } diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs b/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs index 744e2e1c95..701bcf138d 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/amd/mod.rs @@ -1,8 +1,9 @@ pub mod display; pub mod gtt; pub mod ring; +pub mod vram; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; use std::sync::Mutex; @@ -21,6 +22,7 @@ use crate::kms::{ConnectorInfo, ModeInfo}; use self::display::DisplayCore; use self::gtt::GttManager; use self::ring::RingManager; +use self::vram::VramManager; const AMD_IH_RB_CNTL: usize = 0x0080; const AMD_IH_RB_RPTR: usize = 0x0083; @@ -55,6 +57,9 @@ pub struct AmdDriver { vblank_count: AtomicU64, hotplug_pending: AtomicBool, firmware: HashMap>, + vram: Mutex, + bar2_start: u64, + bar2_size: u64, } impl AmdDriver { @@ -150,6 +155,8 @@ impl AmdDriver { irq_mode ); + let vram = VramManager::new(fb_phys, fb_size as u64); + Ok(Self { info, mmio, @@ -164,6 +171,9 @@ impl AmdDriver { vblank_count: AtomicU64::new(0), hotplug_pending: AtomicBool::new(false), firmware, + vram: Mutex::new(vram), + bar2_start: fb_phys, + bar2_size: fb_size as u64, }) } @@ -339,6 +349,16 @@ impl AmdDriver { (obj.phys_addr as u64, obj.size) }; + if phys_addr >= self.bar2_start + && phys_addr < self.bar2_start + self.bar2_size + { + self.gem + .lock() + .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))? + .set_gpu_addr(fb_handle, phys_addr)?; + return Ok(phys_addr); + } + let gpu_addr = { let mut gtt = self .gtt @@ -479,33 +499,83 @@ impl GpuDriver for AmdDriver { Ok(self.vblank_count.load(Ordering::SeqCst)) } - fn gem_create(&self, size: u64, _width: u32, _height: u32) -> Result { - let mut gem = self - .gem + fn gem_create(&self, size: u64, width: u32, height: u32) -> Result { + if width > 0 && height > 0 { + let mut vram = self + .vram + .lock() + .map_err(|_| DriverError::Buffer("VRAM manager poisoned".to_string()))?; + let vram_addr = match vram.allocate(size) { + Ok(addr) => addr, + Err(_) => { + log::warn!( + "redox-drm: VRAM allocation failed for {}x{} ({} bytes), falling back to system RAM", + width, height, size + ); + return self + .gem + .lock() + .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))? + .create(size); + } + }; + + let handle = { + let mut gem = self + .gem + .lock() + .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?; + let handle = gem.create(size)?; + gem.set_gpu_addr(handle, vram_addr)?; + debug!( + "redox-drm: created VRAM-backed GEM handle {} for {}x{} at VRAM {:#x}", + handle, width, height, vram_addr + ); + handle + }; + return Ok(handle); + } + + self.gem .lock() - .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?; - gem.create(size) + .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))? + .create(size) } fn gem_close(&self, handle: GemHandle) -> Result<()> { - let gpu_info = { + let (gpu_addr, size, phys_addr) = { let gem = self .gem .lock() .map_err(|_| DriverError::Buffer("GEM manager poisoned".to_string()))?; let obj = gem.object(handle)?; - (obj.gpu_addr, obj.size) + (obj.gpu_addr, obj.size, obj.phys_addr as u64) }; - if let (Some(gpu_addr), fb_size) = gpu_info { - let mut gtt = self - .gtt - .lock() - .map_err(|_| DriverError::Initialization("GTT manager poisoned".to_string()))?; - gtt.flush_tlb(&self.mmio)?; - gtt.unmap_range(gpu_addr, fb_size)?; - gtt.release_range(gpu_addr, fb_size); + if let Some(gpu_addr) = gpu_addr { + if gpu_addr >= self.bar2_start + && gpu_addr < self.bar2_start + self.bar2_size + { + let mut vram = self + .vram + .lock() + .map_err(|_| DriverError::Buffer("VRAM manager poisoned".to_string()))?; + vram.free(gpu_addr, size); + debug!( + "redox-drm: freed VRAM GEM handle {} at {:#x} ({} bytes)", + handle, gpu_addr, size + ); + } else { + let mut gtt = self + .gtt + .lock() + .map_err(|_| DriverError::Initialization("GTT manager poisoned".to_string()))?; + gtt.flush_tlb(&self.mmio)?; + gtt.unmap_range(gpu_addr, size)?; + gtt.release_range(gpu_addr, size); + } } + let _ = phys_addr; self.gem .lock() @@ -576,7 +646,8 @@ impl GpuDriver for AmdDriver { Ok(Some(DriverEvent::Vblank { crtc_id, count })) } Some(DriverEvent::Hotplug { connector_id }) => { - info!( + let vram = VramManager::new(fb_phys, fb_size as u64); + info!("redox-drm: {}", vram.summary()); "redox-drm: handled AMD hotplug IRQ for {} connector {} irq={:?}", self.info.location, connector_id, irq ); diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/amd/vram.rs b/local/recipes/gpu/redox-drm/source/src/drivers/amd/vram.rs new file mode 100644 index 0000000000..d9008bb72e --- /dev/null +++ b/local/recipes/gpu/redox-drm/source/src/drivers/amd/vram.rs @@ -0,0 +1,199 @@ +use log::{debug, info}; + +use crate::driver::{DriverError, Result}; + +const VRAM_PAGE_SIZE: u64 = 4096; +const VRAM_MIN_ALIGNMENT: u64 = 256; +const VRAM_MAX_ALLOC: u64 = 64 * 1024 * 1024; + +pub struct VramManager { + bar_start: u64, + bar_size: u64, + next_alloc: u64, + allocated: u64, + free_list: Vec<(u64, u64)>, + peak_allocated: u64, +} + +impl VramManager { + pub fn new(bar_start: u64, bar_size: u64) -> Self { + let usable = if bar_size > VRAM_PAGE_SIZE { + bar_size - VRAM_PAGE_SIZE + } else { + 0 + }; + Self { + bar_start, + bar_size, + next_alloc: bar_start + VRAM_PAGE_SIZE, + allocated: 0, + free_list: Vec::new(), + peak_allocated: VRAM_PAGE_SIZE, + } + } + + pub fn allocate(&mut self, size: u64) -> Result { + if size == 0 { + return Err(DriverError::InvalidArgument( + "VRAM allocation size must be non-zero", + )); + } + if size > VRAM_MAX_ALLOC { + return Err(DriverError::InvalidArgument( + "VRAM allocation exceeds single-buffer limit", + )); + } + + let aligned = align_up(size, VRAM_MIN_ALIGNMENT); + + if let Some(idx) = self + .free_list + .iter() + .position(|&(_, free_size)| free_size >= aligned) + { + let (start, free_size) = self.free_list.remove(idx); + let remainder = free_size - aligned; + if remainder >= VRAM_MIN_ALIGNMENT { + self.free_list.push((start + aligned, remainder)); + } + self.allocated += aligned; + if self.allocated > self.peak_allocated { + self.peak_allocated = self.allocated; + } + debug!( + "redox-drm: VRAM alloc {:#x} bytes at {:#x} (from free list)", + aligned, start + ); + return Ok(start); + } + + let addr = self.next_alloc; + let new_next = match addr.checked_add(aligned) { + Some(n) => n, + None => { + return Err(DriverError::Buffer( + "VRAM allocation address overflow".into(), + )); + } + }; + + let bar_end = self.bar_start + self.bar_size; + if new_next > bar_end { + return Err(DriverError::Buffer(format!( + "VRAM exhausted: need {:#x}..{:#x}, BAR2 ends at {:#x} (allocated: {:#x})", + addr, new_next, bar_end, self.allocated + ))); + } + + self.next_alloc = new_next; + self.allocated += aligned; + if self.allocated > self.peak_allocated { + self.peak_allocated = self.allocated; + } + + debug!( + "redox-drm: VRAM alloc {:#x} bytes at {:#x} (bump, next={:#x})", + aligned, addr, self.next_alloc + ); + Ok(addr) + } + + pub fn free(&mut self, addr: u64, size: u64) { + if addr < self.bar_start || size == 0 { + return; + } + + let aligned = align_up(size, VRAM_MIN_ALIGNMENT); + self.allocated = self.allocated.saturating_sub(aligned); + self.free_list.push((addr, aligned)); + self.coalesce(); + + debug!( + "redox-drm: VRAM free {:#x} bytes at {:#x} (remaining allocated: {:#x})", + aligned, addr, self.allocated + ); + } + + fn coalesce(&mut self) { + self.free_list + .sort_by(|(a_start, _), (b_start, _)| a_start.cmp(b_start)); + + let mut merged: Vec<(u64, u64)> = Vec::with_capacity(self.free_list.len()); + for &(start, size) in &self.free_list { + if let Some(last) = merged.last_mut() { + if last.0 + last.1 == start { + last.1 += size; + continue; + } + } + merged.push((start, size)); + } + self.free_list = merged; + } + + pub fn bar_start(&self) -> u64 { + self.bar_start + } + + pub fn bar_size(&self) -> u64 { + self.bar_size + } + + pub fn usable_end(&self) -> u64 { + self.bar_start + self.bar_size + } + + pub fn summary(&self) -> String { + format!( + "BAR2 {:#x}..{:#x} ({:.1} MiB), allocated {:.1} MiB, peak {:.1} MiB, {} free fragments", + self.bar_start, + self.bar_start + self.bar_size, + self.bar_size as f64 / (1024.0 * 1024.0), + self.allocated as f64 / (1024.0 * 1024.0), + self.peak_allocated as f64 / (1024.0 * 1024.0), + self.free_list.len() + ) + } +} + +fn align_up(value: u64, alignment: u64) -> u64 { + (value + alignment - 1) & !(alignment - 1) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn allocate_and_free() { + let mut mgr = VramManager::new(0x1000_0000, 64 * 1024 * 1024); + let a = mgr.allocate(4096).expect("first alloc"); + assert!(a >= 0x1000_1000); + mgr.free(a, 4096); + assert!(mgr.allocated < 4096 + VRAM_PAGE_SIZE * 2); + } + + #[test] + fn reuse_freed() { + let mut mgr = VramManager::new(0x1000_0000, 64 * 1024 * 1024); + let a = mgr.allocate(32768).expect("alloc"); + mgr.free(a, 32768); + let b = mgr.allocate(16384).expect("realloc from free list"); + assert_eq!(a, b, "reallocation should reuse the same start"); + } + + #[test] + fn exhausted_returns_error() { + let mut mgr = VramManager::new(0x1000_0000, 4096 * 4); + assert!(mgr.allocate(4096 * 4).is_ok()); + assert!(mgr.allocate(4096).is_err()); + } + + #[test] + fn summary_string() { + let mgr = VramManager::new(0x1000_0000, 8 * 1024 * 1024); + let s = mgr.summary(); + assert!(s.contains("BAR2")); + assert!(s.contains("MiB")); + } +}