diff --git a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c index 1e39ccf99b..707788020b 100644 --- a/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c +++ b/local/recipes/gpu/amdgpu/source/amdgpu_redox_main.c @@ -517,8 +517,88 @@ int amdgpu_dc_get_connector_info(int idx, void *info) ffi_info->mm_width = desc->mm_width; ffi_info->mm_height = desc->mm_height; ffi_info->encoder_id = desc->encoder_id; - return 0; - } + return 0; +} + +int amdgpu_dc_flip_surface(int crtc_id, uint64_t fb_addr) +{ + u32 hubp_base; + + if (!g_mmio_base) { + pr_err("amdgpu_redox: flip_surface called before init\n"); + return -ENODEV; + } + + { + const struct asic_props *props = asic_props(); + if (!props || crtc_id >= props->max_crtcs) { + pr_err("amdgpu_redox: no register layout for ASIC family %d crtc %d\n", + g_asic_family, crtc_id); + return -EINVAL; + } + hubp_base = props->hubp_base[crtc_id]; + } + + if (amdgpu_dc_validate_mmio_access(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH) != 0 || + amdgpu_dc_validate_mmio_access(hubp_base, AMDGPU_DC_HUBP_FLIP_CONTROL) != 0) { + return -EINVAL; + } + + amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_LOW, (u32)(fb_addr & 0xFFFFFFFFULL)); + amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_ADDR_HIGH, (u32)((fb_addr >> 32) & 0xFFFFFFFFULL)); + amdgpu_dc_write_reg(hubp_base, AMDGPU_DC_HUBP_FLIP_CONTROL, 1); + + return 0; +} + +int amdgpu_dc_set_dpms(int crtc_id, int mode) +{ + u32 otg_base; + u32 otg_control; + + if (!g_mmio_base) { + pr_err("amdgpu_redox: dpms called before init\n"); + return -ENODEV; + } + + { + const struct asic_props *props = asic_props(); + if (!props || crtc_id >= props->max_crtcs) { + pr_err("amdgpu_redox: no register layout for ASIC family %d crtc %d\n", + g_asic_family, crtc_id); + return -EINVAL; + } + otg_base = props->otg_base[crtc_id]; + } + + if (amdgpu_dc_validate_mmio_access(otg_base, AMDGPU_DC_OTG_CONTROL) != 0) { + return -EINVAL; + } + + otg_control = amdgpu_dc_read_reg(otg_base, AMDGPU_DC_OTG_CONTROL); + + switch (mode) { + case DRM_MODE_DPMS_ON: + otg_control |= 0x01U; + printk("amdgpu_redox: DPMS ON crtc %d\n", crtc_id); + break; + case DRM_MODE_DPMS_OFF: + otg_control &= ~0x01U; + printk("amdgpu_redox: DPMS OFF crtc %d\n", crtc_id); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + printk("amdgpu_redox: DPMS mode %d on crtc %d (no hardware support)\n", + mode, crtc_id); + return 0; + } + + mb(); + amdgpu_dc_write_reg(otg_base, AMDGPU_DC_OTG_CONTROL, otg_control); + mb(); + + return 0; +} active_index++; } diff --git a/local/recipes/gpu/redox-drm/source/src/driver.rs b/local/recipes/gpu/redox-drm/source/src/driver.rs index 72b3dd80ba..a50b5a6d23 100644 --- a/local/recipes/gpu/redox-drm/source/src/driver.rs +++ b/local/recipes/gpu/redox-drm/source/src/driver.rs @@ -119,6 +119,10 @@ pub trait GpuDriver: Send + Sync { fn get_edid(&self, connector_id: u32) -> Vec; fn handle_irq(&self) -> Result>; + fn poll_hotplug(&self) -> Result> { + Ok(None) + } + fn redox_private_cs_submit( &self, _submit: &RedoxPrivateCsSubmit, diff --git a/local/recipes/gpu/redox-drm/source/src/drivers/amd/display.rs b/local/recipes/gpu/redox-drm/source/src/drivers/amd/display.rs index 912f18d01c..c21fe19ad4 100644 --- a/local/recipes/gpu/redox-drm/source/src/drivers/amd/display.rs +++ b/local/recipes/gpu/redox-drm/source/src/drivers/amd/display.rs @@ -37,6 +37,10 @@ unsafe extern "C" { fn ffi_amdgpu_dc_get_connector_info(idx: i32, info: *mut ConnectorInfoFFI) -> i32; #[link_name = "amdgpu_dc_set_crtc"] fn ffi_amdgpu_dc_set_crtc(crtc_id: i32, fb_addr: u64, width: u32, height: u32) -> i32; + #[link_name = "amdgpu_dc_flip_surface"] + fn ffi_amdgpu_dc_flip_surface(crtc_id: i32, fb_addr: u64) -> i32; + #[link_name = "amdgpu_dc_set_dpms"] + fn ffi_amdgpu_dc_set_dpms(crtc_id: i32, mode: i32) -> i32; #[link_name = "amdgpu_redox_cleanup"] fn ffi_amdgpu_redox_cleanup(); @@ -105,6 +109,14 @@ fn amdgpu_dc_get_connector_info(_idx: i32, _info: *mut ConnectorInfoFFI) -> i32 fn amdgpu_dc_set_crtc(_crtc_id: i32, _fb_addr: u64, _width: u32, _height: u32) -> i32 { 0 } +#[cfg(no_amdgpu_c)] +fn amdgpu_dc_flip_surface(_crtc_id: i32, _fb_addr: u64) -> i32 { + 0 +} +#[cfg(no_amdgpu_c)] +fn amdgpu_dc_set_dpms(_crtc_id: i32, _mode: i32) -> i32 { + 0 +} #[cfg(no_amdgpu_c)] fn amdgpu_dc_cleanup() { @@ -200,6 +212,14 @@ fn amdgpu_dc_get_connector_info(idx: i32, info: *mut ConnectorInfoFFI) -> i32 { fn amdgpu_dc_set_crtc(crtc_id: i32, fb_addr: u64, width: u32, height: u32) -> i32 { unsafe { ffi_amdgpu_dc_set_crtc(crtc_id, fb_addr, width, height) } } +#[cfg(not(no_amdgpu_c))] +fn amdgpu_dc_flip_surface(crtc_id: i32, fb_addr: u64) -> i32 { + unsafe { ffi_amdgpu_dc_flip_surface(crtc_id, fb_addr) } +} +#[cfg(not(no_amdgpu_c))] +fn amdgpu_dc_set_dpms(crtc_id: i32, mode: i32) -> i32 { + unsafe { ffi_amdgpu_dc_set_dpms(crtc_id, mode) } +} #[cfg(not(no_amdgpu_c))] fn amdgpu_dc_cleanup() { @@ -338,17 +358,31 @@ impl DisplayCore { )); } - const HUBP_FLIP_ADDR_LOW: usize = 0x5800; - const HUBP_FLIP_ADDR_HIGH: usize = 0x5804; + let rc = amdgpu_dc_flip_surface(crtc_id as i32, fb_addr); + if rc < 0 { + return Err(DriverError::Mmio(format!( + "amdgpu_dc_flip_surface failed for CRTC {} with status {}", + crtc_id, rc + ))); + } - let hubp_base = HUBP_FLIP_ADDR_LOW + (crtc_id as usize) * 0x400; - let hubp_high = HUBP_FLIP_ADDR_HIGH + (crtc_id as usize) * 0x400; + Ok(()) + } - self.write_reg(hubp_high, (fb_addr >> 32) as u32)?; - self.write_reg(hubp_base, fb_addr as u32)?; + pub fn set_dpms(&self, crtc_id: u32, mode: i32) -> Result<()> { + if !self.initialized { + return Err(DriverError::Initialization( + "display core must be initialized before DPMS".to_string(), + )); + } - let flip_control = 0x5834 + (crtc_id as usize) * 0x400; - self.write_reg(flip_control, 1)?; + let rc = amdgpu_dc_set_dpms(crtc_id as i32, mode); + if rc < 0 { + return Err(DriverError::Mmio(format!( + "amdgpu_dc_set_dpms failed for CRTC {} mode {} with status {}", + crtc_id, mode, rc + ))); + } Ok(()) } 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 04cb177227..e8f70d5a56 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 @@ -307,6 +307,31 @@ impl AmdDriver { self.write_mmio_reg(AMD_IH_RB_CNTL, ih_rb_cntl); } + fn detect_hotplug_poll(&self) -> Option { + let previous_connectors = self + .connectors + .lock() + .ok() + .map(|c| c.len()) + .unwrap_or(0); + + let current_connectors = self + .display + .detect_connectors() + .map(|c| c.len()) + .unwrap_or(0); + + if current_connectors != previous_connectors { + info!( + "redox-drm: hotplug poll detected connector count change: {} -> {}", + previous_connectors, current_connectors + ); + Some(1) + } else { + None + } + } + fn refresh_connectors(&self) -> Result<()> { let (connectors, encoders) = detect_display_topology(&self.display)?; @@ -414,7 +439,19 @@ impl GpuDriver for AmdDriver { } fn driver_date(&self) -> &str { - "2026-04-11" + "2026-05-01" + } + + fn cursor_set(&self, _crtc_id: u32, _fb_handle: u32, _hot_x: u32, _hot_y: u32) -> Result<()> { + Err(DriverError::Unsupported( + "AMD hardware cursor requires Display Core initialization (imported Linux DC tree not yet compiled; cursor plane registers need DC hw programming)", + )) + } + + fn cursor_move(&self, _crtc_id: u32, _x: i32, _y: i32) -> Result<()> { + Err(DriverError::Unsupported( + "AMD hardware cursor movement requires Display Core cursor plane programming", + )) } fn detect_connectors(&self) -> Vec { @@ -631,6 +668,59 @@ impl GpuDriver for AmdDriver { return Ok(None); } + let irq = self + .irq_handle + .lock() + .ok() + .and_then(|guard| guard.as_ref().map(|h| h.irq())); + + match self.process_irq()? { + Some(DriverEvent::Vblank { crtc_id, count }) => { + debug!( + "redox-drm: handled AMD vblank IRQ for {} CRTC {} count={} irq={:?}", + self.info.location, crtc_id, count, irq + ); + Ok(Some(DriverEvent::Vblank { crtc_id, count })) + } + Some(DriverEvent::Hotplug { connector_id }) => { + info!( + "redox-drm: handled AMD hotplug IRQ for {} connector {} irq={:?}", + self.info.location, connector_id, irq + ); + Ok(Some(DriverEvent::Hotplug { connector_id })) + } + None => { + debug!( + "redox-drm: handled AMD IRQ for {} with no decoded source irq={:?}", + self.info.location, irq + ); + Ok(None) + } + } + } + + fn poll_hotplug(&self) -> Result> { + use std::sync::atomic::Ordering; + + if self.irq_handle.lock().map_or(true, |guard| guard.is_none()) { + if let Some(connector_id) = self.detect_hotplug_poll() { + self.hotplug_pending.store(true, Ordering::SeqCst); + if let Err(e) = self.refresh_connectors() { + warn!("redox-drm: hotplug poll refresh failed: {}", e); + } + self.hotplug_pending.store(false, Ordering::SeqCst); + return Ok(Some(DriverEvent::Hotplug { connector_id })); + } + } + + Ok(None) + } + }; + + if !irq_event { + return Ok(None); + } + let irq = self .irq_handle .lock()