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).
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ pub trait GpuDriver: Send + Sync {
|
||||
_submit: &RedoxPrivateCsSubmit,
|
||||
) -> Result<RedoxPrivateCsSubmitResult> {
|
||||
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<RedoxPrivateCsWaitResult> {
|
||||
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<VirglCapsetInfo> {
|
||||
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<VirglCapset> {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Vec<u8>>,
|
||||
vram: Mutex<VramManager>,
|
||||
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<GemHandle> {
|
||||
let mut gem = self
|
||||
.gem
|
||||
fn gem_create(&self, size: u64, width: u32, height: u32) -> Result<GemHandle> {
|
||||
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
|
||||
);
|
||||
|
||||
@@ -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<u64> {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user