intel: execlists — GPU execution list submission (Phase 4)

Add execlists.rs implementing GPU context submission via execlist ports.

- ExeclistPort: manages 2-slot ELSP (Execlist Submission Port)
  at RING_ELSP (base + 0x230), context control (base + 0x244),
  context status pointer (base + 0x3A0), and execlist control
  (base + 0x550)
- init(): enable execlist control, configure context restore
  inhibit + RS context enable, clear CSB pointer
- submit(): queue ExeclistContext to next available ELSP slot,
  flush to hardware via ELSP register writes
- check_completion(): read RING_EXECLIST_STATUS_LO for completed
  context count, update active counter
- create_lrc_descriptor(): allocate 4KB LRC in GGTT with
  ELSP_VALID + ELSP_PRIVILEGE_ACCESS flags

Linux reference: intel_execlists_submission.c, i915_reg.h (ELSP)
This commit is contained in:
2026-05-30 09:14:54 +03:00
parent 493555b105
commit a60917387f
2 changed files with 146 additions and 0 deletions
@@ -0,0 +1,145 @@
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use log::{debug, info, warn};
use redox_driver_sys::memory::MmioRegion;
use super::gtt::IntelGtt;
use crate::driver::Result;
use crate::driver::DriverError;
const RING_ELSP_OFFSET: usize = 0x230;
const RING_EXECLIST_STATUS_LO: usize = 0x234;
const RING_CONTEXT_CONTROL_OFFSET: usize = 0x244;
const RING_CONTEXT_STATUS_PTR_OFFSET: usize = 0x3A0;
const RING_EXECLIST_CONTROL_OFFSET: usize = 0x550;
const RING_ELSP_WRITE_COUNT: usize = 2;
const LRC_DESC_SIZE: usize = 4096;
const CTX_CTRL_CTX_RESTORE_INHIBIT: u32 = 1 << 0;
const CTX_CTRL_ENGINE_CTX_RESTORE_INHIBIT: u32 = 1 << 1;
const CTX_CTRL_RS_CTX_ENABLE: u32 = 1 << 3;
const EXECLIST_CONTROL_ENABLE: u32 = 1 << 0;
const ELSP_VALID: u32 = 1 << 0;
const ELSP_PRIVILEGE_ACCESS: u32 = 1 << 8;
const CSB_ENTRY_SIZE: usize = 8;
const MAX_CSB_ENTRIES: usize = 6;
#[derive(Debug, Clone, Copy)]
pub struct ExeclistContext {
pub desc: u64,
pub engine_class: u8,
pub engine_instance: u8,
}
pub struct ExeclistPort {
mmio: Arc<MmioRegion>,
ring_base: usize,
pending: [Option<ExeclistContext>; RING_ELSP_WRITE_COUNT],
active: u32,
}
impl ExeclistPort {
pub fn new(mmio: Arc<MmioRegion>, ring_base: usize) -> Self {
Self {
mmio,
ring_base,
pending: [None, None],
active: 0,
}
}
pub fn init(&mut self) -> Result<()> {
let ctrl = self.ring_base + RING_EXECLIST_CONTROL_OFFSET;
self.mmio.write_u32(ctrl, EXECLIST_CONTROL_ENABLE);
let ctx_ctrl = self.ring_base + RING_CONTEXT_CONTROL_OFFSET;
self.mmio.write_u32(ctx_ctrl,
CTX_CTRL_CTX_RESTORE_INHIBIT
| CTX_CTRL_ENGINE_CTX_RESTORE_INHIBIT
| CTX_CTRL_RS_CTX_ENABLE,
);
let csb_ptr = self.ring_base + RING_CONTEXT_STATUS_PTR_OFFSET;
self.mmio.write_u32(csb_ptr, 0);
info!("redox-drm-intel: execlist port initialized at {:#06x}", self.ring_base);
Ok(())
}
pub fn submit(&mut self, ctx: ExeclistContext) -> Result<()> {
let slot = if self.pending[0].is_none() { 0 } else { 1 };
self.pending[slot] = Some(ctx);
self.flush_pending()?;
self.active += 1;
Ok(())
}
fn flush_pending(&mut self) -> Result<()> {
for slot in 0..RING_ELSP_WRITE_COUNT {
if let Some(ref ctx) = self.pending[slot] {
let elsp = self.ring_base + RING_ELSP_OFFSET + slot * 4;
let desc_lo = ctx.desc as u32;
let desc_hi = (ctx.desc >> 32) as u32;
self.mmio.write_u32(elsp, desc_lo);
self.mmio.write_u32(elsp + 4, desc_hi);
self.pending[slot] = None;
}
}
Ok(())
}
pub fn check_completion(&mut self) -> usize {
let status_lo = self.ring_base + RING_EXECLIST_STATUS_LO;
let status = self.mmio.read_u32(status_lo);
let completed = (status & 0x07) as usize;
if completed > 0 {
self.active = self.active.saturating_sub(completed);
debug!("redox-drm-intel: {} contexts completed, {} active",
completed, self.active);
}
completed
}
pub fn active_count(&self) -> u32 {
self.active
}
pub fn is_idle(&self) -> bool {
self.active == 0
}
}
pub fn create_lrc_descriptor(gtt: &IntelGtt, ring_addr: u64, indirect_ctx: bool) -> Result<u64> {
let lrc_addr = gtt.allocate_range(LRC_DESC_SIZE)?;
let desc = if indirect_ctx {
lrc_addr | ELSP_VALID | ELSP_PRIVILEGE_ACCESS | (1 << 42)
} else {
lrc_addr | ELSP_VALID | ELSP_PRIVILEGE_ACCESS
};
debug!("redox-drm-intel: LRC descriptor {:#018x} (ring {:#018x})", desc, ring_addr);
Ok(desc)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_execlist_context() {
let ctx = ExeclistContext {
desc: 0x1000 | ELSP_VALID as u64,
engine_class: 0,
engine_instance: 0,
};
assert!(ctx.desc & ELSP_VALID as u64 != 0);
}
}
@@ -9,6 +9,7 @@ pub mod display_power;
pub mod display_watermark;
pub mod dp_aux;
pub mod dp_link;
pub mod execlists;
pub mod fence;
pub mod gmbus;
pub mod gtt;