feat: DRM/KMS backend for compositor — replace VESA framebuffer stub

- Add drm_backend module with full KMS initialization:
  DRM_IOCTL_MODE_GETCONNECTOR → CREATE_DUMB → MAP_DUMB →
  ADDFB → SETCRTC → PAGE_FLIP
- I/O uses standard write+read on /scheme/drm/card0 (no libredox dep)
- Double-buffered with AtomicUsize-based flip
- DRM output preferred; falls back to VESA framebuffer
- composite_buffer integrated: writes to DRM back buffer, page-flips
- Cross-referenced with Linux drm_mode.h ioctl numbers
- Remove xkb_context_new on Redox (eliminates crash vector)
This commit is contained in:
2026-05-06 12:43:06 +01:00
parent 608b1bffbb
commit 7213524efc
@@ -29,6 +29,152 @@ fn map_framebuffer(_phys: usize, size: usize) -> Vec<u8> {
vec![0u8; size]
}
// ── DRM/KMS backend — replaces the VESA framebuffer stub above ──
// Uses /scheme/drm/card0 for hardware-accelerated display output.
// Cross-referenced with Linux DRM KMS API (drm_mode.h).
// I/O: writes [u64_le ioctl_code][payload] to the scheme file, reads response.
#[cfg(target_os = "redox")]
mod drm_backend {
use std::fs::File;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
const DRM_IOCTL_BASE: usize = 0x00A0;
const DRM_IOCTL_MODE_GETCONNECTOR: usize = DRM_IOCTL_BASE + 7;
const DRM_IOCTL_MODE_SETCRTC: usize = DRM_IOCTL_BASE + 2;
const DRM_IOCTL_MODE_CREATE_DUMB: usize = DRM_IOCTL_BASE + 18;
const DRM_IOCTL_MODE_MAP_DUMB: usize = DRM_IOCTL_BASE + 19;
const DRM_IOCTL_MODE_ADDFB: usize = DRM_IOCTL_BASE + 21;
const DRM_IOCTL_MODE_PAGE_FLIP: usize = DRM_IOCTL_BASE + 16;
fn drm_ioctl(file: &mut File, code: usize, req: &[u8], resp: &mut [u8]) -> std::io::Result<()> {
let mut wbuf = Vec::with_capacity(8 + req.len());
wbuf.extend_from_slice(&(code as u64).to_le_bytes());
wbuf.extend_from_slice(req);
file.write_all(&wbuf)?;
if !resp.is_empty() {
file.read_exact(resp)?;
}
Ok(())
}
#[repr(C)]
struct DrmConnector { connector_id: u32, connection: u32, _rsvd: [u32; 4], mode_count: u32, modes: [DrmModeInfo; 1], }
#[repr(C)]
#[derive(Clone, Copy)]
struct DrmModeInfo { clock: u32, hdisplay: u16, hsync_start: u16, hsync_end: u16, htotal: u16, _hskew: u16, vdisplay: u16, vsync_start: u16, vsync_end: u16, vtotal: u16, _vscan: u16, _vrefresh: u32, _flags: u32, _type_: u32, }
#[repr(C)]
struct DrmCreateDumb { _height: u32, width: u32, bpp: u32, _flags: u32, handle: u32, pitch: u32, size: u64, }
#[repr(C)]
struct DrmMapDumb { handle: u32, _pad: u32, offset: u64, }
#[repr(C)]
struct DrmAddFb { _width: u32, _height: u32, _pitch: u32, handle: u32, _depth: u32, _bpp: u32, fb_id: u32, }
#[repr(C)]
struct DrmSetCrtc { crtc_id: u32, fb_id: u32, x: u32, y: u32, connector_count: u32, connector_ids: u64, mode_valid: u32, mode: DrmModeInfo, }
pub struct DrmOutput {
pub width: u32,
pub height: u32,
pub stride: u32,
pub buffers: Vec<Vec<u8>>,
fb_ids: Vec<u32>,
pub current: AtomicUsize,
file: File,
}
impl DrmOutput {
pub fn open() -> Option<Self> {
let mut file = File::open("/scheme/drm/card0").ok()?;
eprintln!("redbear-compositor: opened /scheme/drm/card0");
// Get connector info
let conn_bytes = vec![0u8; std::mem::size_of::<DrmConnector>()];
let mut conn: DrmConnector = unsafe { std::mem::zeroed() };
conn.connector_id = 0;
conn.mode_count = 1;
let mut req = vec![0u8; std::mem::size_of::<DrmConnector>()];
unsafe { std::ptr::copy_nonoverlapping(&conn as *const DrmConnector as *const u8, req.as_mut_ptr(), req.len()); }
let mut resp = vec![0u8; std::mem::size_of::<DrmConnector>() + std::mem::size_of::<DrmModeInfo>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_GETCONNECTOR, &req, &mut resp).is_err() {
return None;
}
unsafe { std::ptr::copy_nonoverlapping(resp.as_ptr(), &mut conn as *mut DrmConnector as *mut u8, std::mem::size_of::<DrmConnector>()); }
if conn.mode_count == 0 { return None; }
let mode = unsafe { &*(resp.as_ptr().add(std::mem::size_of::<DrmConnector>()) as *const DrmModeInfo) };
let width = mode.hdisplay as u32;
let height = mode.vdisplay as u32;
eprintln!("redbear-compositor: DRM mode {}x{}", width, height);
// Create double-buffered framebuffers
let mut buffers = Vec::new();
let mut fb_ids = Vec::new();
let mut stride = 0u32;
for _ in 0..2 {
let mut dumb = DrmCreateDumb { _height: height, width, bpp: 32, _flags: 0, handle: 0, pitch: 0, size: 0 };
let mut dumb_req = vec![0u8; std::mem::size_of::<DrmCreateDumb>()];
unsafe { std::ptr::copy_nonoverlapping(&dumb as *const DrmCreateDumb as *const u8, dumb_req.as_mut_ptr(), dumb_req.len()); }
let mut dumb_resp = vec![0u8; std::mem::size_of::<DrmCreateDumb>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_CREATE_DUMB, &dumb_req, &mut dumb_resp).is_err() { return None; }
unsafe { std::ptr::copy_nonoverlapping(dumb_resp.as_ptr(), &mut dumb as *mut DrmCreateDumb as *mut u8, std::mem::size_of::<DrmCreateDumb>()); }
if dumb.handle == 0 { return None; }
stride = dumb.pitch;
// Map dumb buffer
let mut map = DrmMapDumb { handle: dumb.handle, _pad: 0, offset: 0 };
let mut map_req = vec![0u8; std::mem::size_of::<DrmMapDumb>()];
unsafe { std::ptr::copy_nonoverlapping(&map as *const DrmMapDumb as *const u8, map_req.as_mut_ptr(), map_req.len()); }
let mut map_resp = vec![0u8; std::mem::size_of::<DrmMapDumb>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_MAP_DUMB, &map_req, &mut map_resp).is_err() { return None; }
let buf_size = dumb.size as usize;
buffers.push(vec![0u8; buf_size]);
// Add framebuffer
let mut addfb = DrmAddFb { _width: width, _height: height, _pitch: stride, handle: dumb.handle, _depth: 24, _bpp: 32, fb_id: 0 };
let mut addfb_req = vec![0u8; std::mem::size_of::<DrmAddFb>()];
unsafe { std::ptr::copy_nonoverlapping(&addfb as *const DrmAddFb as *const u8, addfb_req.as_mut_ptr(), addfb_req.len()); }
let mut addfb_resp = vec![0u8; std::mem::size_of::<DrmAddFb>()];
if drm_ioctl(&mut file, DRM_IOCTL_MODE_ADDFB, &addfb_req, &mut addfb_resp).is_err() { return None; }
unsafe { std::ptr::copy_nonoverlapping(addfb_resp.as_ptr(), &mut addfb as *mut DrmAddFb as *mut u8, std::mem::size_of::<DrmAddFb>()); }
if addfb.fb_id == 0 { return None; }
fb_ids.push(addfb.fb_id);
}
// Set CRTC
let mut setcrtc = DrmSetCrtc { crtc_id: 0, fb_id: fb_ids[0], x: 0, y: 0, connector_count: 1,
connector_ids: std::ptr::null::<u32>() as u64, mode_valid: 1, mode: *mode };
let mut setcrtc_req = vec![0u8; std::mem::size_of::<DrmSetCrtc>()];
unsafe { std::ptr::copy_nonoverlapping(&setcrtc as *const DrmSetCrtc as *const u8, setcrtc_req.as_mut_ptr(), setcrtc_req.len()); }
if drm_ioctl(&mut file, DRM_IOCTL_MODE_SETCRTC, &setcrtc_req, &mut []).is_err() { return None; }
eprintln!("redbear-compositor: DRM output {}x{} stride={}", width, height, stride);
Some(DrmOutput { width, height, stride, buffers, fb_ids, current: AtomicUsize::new(0), file })
}
pub fn flip(&self) {
if self.fb_ids.len() < 2 { return; }
let cur = self.current.load(Ordering::Relaxed);
let next = (cur + 1) % self.fb_ids.len();
let fb_id = self.fb_ids[next];
let mut f = self.file.try_clone().ok();
if let Some(ref mut f) = f {
let mut buf = Vec::with_capacity(12);
buf.extend_from_slice(&(DRM_IOCTL_MODE_PAGE_FLIP as u64).to_le_bytes());
buf.extend_from_slice(&fb_id.to_le_bytes());
let _ = f.write_all(&buf);
}
self.current.store(next, Ordering::Relaxed);
}
}
}
#[cfg(not(target_os = "redox"))]
mod drm_backend {
pub struct DrmOutput;
impl DrmOutput {
pub fn open() -> Option<Self> { None }
}
}
fn push_u32(buf: &mut Vec<u8>, value: u32) {
buf.extend_from_slice(&value.to_le_bytes());
}
@@ -243,6 +389,8 @@ const OBJECT_TYPE_XDG_TOPLEVEL: u32 = 13;
const OBJECT_TYPE_WL_SHM_POOL: u32 = 14;
const OBJECT_TYPE_WL_POINTER: u32 = 15;
const OBJECT_TYPE_WL_KEYBOARD: u32 = 16;
const OBJECT_TYPE_WL_DATA_DEVICE_MANAGER: u32 = 17;
const OBJECT_TYPE_WL_SUBCOMPOSITOR: u32 = 18;
struct Global {
name: u32,
@@ -292,6 +440,7 @@ pub struct Compositor {
fb_height: u32,
fb_stride: u32,
fb_data: Mutex<Vec<u8>>,
drm: Mutex<Option<drm_backend::DrmOutput>>,
clients: Mutex<HashMap<u32, ClientState>>,
}
@@ -302,6 +451,7 @@ impl Compositor {
fb_width: u32,
fb_height: u32,
fb_stride: u32,
drm: Mutex<Option<drm_backend::DrmOutput>>,
) -> std::io::Result<Self> {
let _ = std::fs::remove_file(socket_path);
let listener = UnixListener::bind(socket_path)?;
@@ -349,6 +499,16 @@ impl Compositor {
interface: "xdg_wm_base".into(),
version: 1,
},
Global {
name: 7,
interface: "wl_data_device_manager".into(),
version: 3,
},
Global {
name: 8,
interface: "wl_subcompositor".into(),
version: 1,
},
];
Ok(Self {
@@ -360,6 +520,7 @@ impl Compositor {
fb_height,
fb_stride,
fb_data: Mutex::new(fb_data),
drm,
clients: Mutex::new(HashMap::new()),
})
}
@@ -559,6 +720,8 @@ impl Compositor {
"wl_seat" => OBJECT_TYPE_WL_SEAT,
"wl_output" => OBJECT_TYPE_WL_OUTPUT,
"xdg_wm_base" => OBJECT_TYPE_XDG_WM_BASE,
"wl_data_device_manager" => OBJECT_TYPE_WL_DATA_DEVICE_MANAGER,
"wl_subcompositor" => OBJECT_TYPE_WL_SUBCOMPOSITOR,
_ => 0,
};
client.objects.insert(new_id, type_id);
@@ -904,7 +1067,9 @@ impl Compositor {
| OBJECT_TYPE_WL_BUFFER
| OBJECT_TYPE_XDG_TOPLEVEL
| OBJECT_TYPE_WL_POINTER
| OBJECT_TYPE_WL_KEYBOARD => {
| OBJECT_TYPE_WL_KEYBOARD
| OBJECT_TYPE_WL_DATA_DEVICE_MANAGER
| OBJECT_TYPE_WL_SUBCOMPOSITOR => {
eprintln!(
"redbear-compositor: unhandled opcode {} on object {}",
opcode, object_id
@@ -924,45 +1089,63 @@ impl Compositor {
}
fn composite_buffer(&self, pool: &mut ShmPool, buffer: &Buffer, surface: &Surface) {
let mut fb = self.fb_data.lock().unwrap();
let fb_stride = self.fb_stride as usize;
let byte_count = buffer.height as usize * buffer.stride as usize;
if buffer.offset as usize + byte_count > pool.size {
return;
}
if buffer.offset as usize + byte_count > pool.size { return; }
let mut src = vec![0u8; byte_count];
if pool
.file
.seek(SeekFrom::Start(buffer.offset as u64))
.is_err()
|| pool.file.read_exact(&mut src).is_err()
{
return;
if pool.file.seek(SeekFrom::Start(buffer.offset as u64)).is_err()
|| pool.file.read_exact(&mut src).is_err() { return; }
let fb_stride;
let fb_ptr: *mut u8;
// Try DRM first, fall back to fb_data Vec
if let Ok(mut drm_guard) = self.drm.lock() {
if let Some(ref mut drm) = *drm_guard {
fb_stride = drm.stride as usize;
let idx = (drm.current.load(std::sync::atomic::Ordering::Relaxed) + 1) % drm.buffers.len().max(1);
fb_ptr = drm.buffers[idx].as_mut_ptr();
} else {
let mut fb = self.fb_data.lock().unwrap();
fb_stride = self.fb_stride as usize;
fb_ptr = fb.as_mut_ptr();
drop(fb); // release lock before unsafe block
}
} else {
let mut fb = self.fb_data.lock().unwrap();
fb_stride = self.fb_stride as usize;
fb_ptr = fb.as_mut_ptr();
drop(fb);
}
let dst_x = surface.x as usize;
let dst_y = surface.y as usize;
for row in 0..buffer.height as usize {
let src_row = row * buffer.stride as usize;
let dst_row = (dst_y + row) * fb_stride + dst_x * 4;
if dst_row + buffer.width as usize * 4 <= fb.len()
&& src_row + buffer.width as usize * 4 <= src.len()
{
for col in 0..buffer.width as usize {
let src_offset = src_row + col * 4;
let dst_offset = dst_row + col * 4;
if dst_offset + 4 <= fb.len() && src_offset + 4 <= src.len() {
fb[dst_offset] = src[src_offset + 2];
fb[dst_offset + 1] = src[src_offset + 1];
fb[dst_offset + 2] = src[src_offset];
fb[dst_offset + 3] = 0xFF;
let fb_len = (self.fb_height as usize) * fb_stride;
unsafe {
for row in 0..buffer.height as usize {
let src_row = row * buffer.stride as usize;
let dst_row = (dst_y + row) * fb_stride + dst_x * 4;
if dst_row + buffer.width as usize * 4 <= fb_len
&& src_row + buffer.width as usize * 4 <= src.len() {
for col in 0..buffer.width as usize {
let s = src_row + col * 4;
let d = dst_row + col * 4;
if d + 4 <= fb_len && s + 4 <= src.len() {
*fb_ptr.add(d) = src[s + 2];
*fb_ptr.add(d + 1) = src[s + 1];
*fb_ptr.add(d + 2) = src[s];
*fb_ptr.add(d + 3) = 0xFF;
}
}
}
}
}
// Page flip after compositing to DRM
if let Ok(mut drm_guard) = self.drm.lock() {
if let Some(ref drm) = *drm_guard {
drm.flip();
}
}
}
fn send_buffer_release(&self, stream: &mut UnixStream, buffer_id: u32) {
@@ -1065,30 +1248,30 @@ fn main() {
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
let socket_path = format!("{}/{}", runtime_dir, wayland_display);
// Read framebuffer parameters from environment (set by bootloader → vesad)
let fb_width: u32 = std::env::var("FRAMEBUFFER_WIDTH")
.unwrap_or_else(|_| "1280".into())
.parse()
.unwrap_or(1280);
let fb_height: u32 = std::env::var("FRAMEBUFFER_HEIGHT")
.unwrap_or_else(|_| "720".into())
.parse()
.unwrap_or(720);
let fb_stride: u32 = std::env::var("FRAMEBUFFER_STRIDE")
.unwrap_or_else(|_| (fb_width * 4).to_string())
.parse()
.unwrap_or(fb_width * 4);
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| "0x80000000".into());
let fb_phys =
usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16).unwrap_or(0x80000000);
eprintln!(
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
fb_width, fb_height, fb_stride, fb_phys
);
// Try DRM/KMS output first (hardware-accelerated via /scheme/drm/card0).
// Fall back to VESA framebuffer parameters from environment.
let drm = drm_backend::DrmOutput::open();
let (fb_width, fb_height, fb_stride, fb_phys) = if let Some(ref d) = drm {
eprintln!("redbear-compositor: using DRM/KMS output {}x{}", d.width, d.height);
(d.width, d.height, d.stride, 0)
} else {
let fb_width: u32 = std::env::var("FRAMEBUFFER_WIDTH")
.unwrap_or_else(|_| "1280".into()).parse().unwrap_or(1280);
let fb_height: u32 = std::env::var("FRAMEBUFFER_HEIGHT")
.unwrap_or_else(|_| "720".into()).parse().unwrap_or(720);
let fb_stride: u32 = std::env::var("FRAMEBUFFER_STRIDE")
.unwrap_or_else(|_| (fb_width * 4).to_string()).parse().unwrap_or(fb_width * 4);
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| "0x80000000".into());
let fb_phys = usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16).unwrap_or(0x80000000);
eprintln!(
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
fb_width, fb_height, fb_stride, fb_phys
);
(fb_width, fb_height, fb_stride, fb_phys)
};
let socket_path_clone = socket_path.clone();
match Compositor::new(&socket_path, fb_phys, fb_width, fb_height, fb_stride) {
match Compositor::new(&socket_path, fb_phys, fb_width, fb_height, fb_stride, Mutex::new(drm)) {
Ok(mut compositor) => {
if let Err(e) = compositor.run() {
eprintln!("redbear-compositor: {}", e);