amdgpu: flip_surface per-family offsets, DPMS, cursor, hotplug polling

- Move flip_surface from Rust hardcoded registers (HUBP 0x5800, Navi23-only)
  to C side amdgpu_dc_flip_surface() using asic_props per-family HUBP offsets.

- Add amdgpu_dc_set_dpms() for DPMS ON/OFF via OTG_CONTROL register.
  Uses per-family OTG base offsets. DPMS standby/suspend return noop.

- Override cursor_set/cursor_move on AmdDriver with honest error messages
  documenting Display Core dependency.

- Add poll_hotplug() to GpuDriver trait. AmdDriver overrides with connector
  count change detection when IRQ handle is unavailable.

- Remove hardcoded HUBP_FLIP_ADDR_* constants from Rust display.rs.
This commit is contained in:
2026-06-01 05:44:57 +03:00
parent 24584eb3c6
commit c5bd162aea
4 changed files with 219 additions and 11 deletions
@@ -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++;
}
@@ -119,6 +119,10 @@ pub trait GpuDriver: Send + Sync {
fn get_edid(&self, connector_id: u32) -> Vec<u8>;
fn handle_irq(&self) -> Result<Option<DriverEvent>>;
fn poll_hotplug(&self) -> Result<Option<DriverEvent>> {
Ok(None)
}
fn redox_private_cs_submit(
&self,
_submit: &RedoxPrivateCsSubmit,
@@ -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(())
}
@@ -307,6 +307,31 @@ impl AmdDriver {
self.write_mmio_reg(AMD_IH_RB_CNTL, ih_rb_cntl);
}
fn detect_hotplug_poll(&self) -> Option<u32> {
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<ConnectorInfo> {
@@ -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<Option<DriverEvent>> {
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()