Merge branch 'drm' into 'master'

Implement DRM ioctls for Redox

See merge request redox-os/relibc!801
This commit is contained in:
Jeremy Soller
2025-12-16 12:56:22 -07:00
9 changed files with 580 additions and 7 deletions
Generated
+25
View File
@@ -179,6 +179,16 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "drm-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c"
dependencies = [
"libc",
"linux-raw-sys",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -248,6 +258,12 @@ dependencies = [
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -401,6 +417,14 @@ dependencies = [
"rand_core",
]
[[package]]
name = "redox-ioctl"
version = "0.1.0"
dependencies = [
"drm-sys",
"redox_syscall",
]
[[package]]
name = "redox-path"
version = "0.3.1"
@@ -461,6 +485,7 @@ dependencies = [
"rand",
"rand_jitter",
"rand_xorshift",
"redox-ioctl",
"redox-path",
"redox-rt",
"redox_event",
+1
View File
@@ -68,6 +68,7 @@ redox-path = "0.3"
redox_event = { git = "https://gitlab.redox-os.org/redox-os/event.git", default-features = false, features = [
"redox_syscall",
] }
redox-ioctl = { path = "redox-ioctl" }
[features]
default = ["check_against_libc_crate"]
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "redox-ioctl"
authors = ["bjorn3 <bjorn3_gh@protonmail.com>"]
version = "0.1.0"
edition = "2024"
license = "MIT"
description = "Ioctl definitions and (de)serialization for Redox"
[dependencies]
drm-sys = "0.8.0"
redox_syscall = "0.5.8"
[features]
+172
View File
@@ -0,0 +1,172 @@
use alloc::vec::Vec;
use core::{
cmp,
ffi::{c_char, c_int},
iter, mem, slice,
};
pub use drm_sys::{
__kernel_size_t, drm_get_cap, drm_mode_card_res, drm_mode_crtc, drm_mode_fb_cmd,
drm_mode_fb_cmd2, drm_mode_get_connector, drm_mode_get_encoder, drm_mode_get_plane,
drm_mode_get_plane_res, drm_mode_modeinfo, drm_mode_obj_get_properties, drm_set_client_cap,
drm_version,
};
pub const VERSION: u64 = 0;
define_ioctl_data! {
struct drm_version, DrmVersion {
version_major: c_int,
version_minor: c_int,
version_patchlevel: c_int,
name_len: __kernel_size_t,
name: *mut c_char [array<c_char, name_len>],
date_len: __kernel_size_t,
date: *mut c_char [array<c_char, date_len>],
desc_len: __kernel_size_t,
desc: *mut c_char [array<c_char, desc_len>],
}
}
pub const GET_CAP: u64 = 0x0C;
pub use drm_sys::DRM_CAP_DUMB_BUFFER;
define_ioctl_data! {
struct drm_get_cap, DrmGetCap {
capability: u64,
value: u64,
}
}
pub const SET_CLIENT_CAP: u64 = 0x0D;
pub use drm_sys::DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT;
define_ioctl_data! {
struct drm_set_client_cap, DrmSetClientCap {
capability: u64,
value: u64,
}
}
pub const MODE_CARD_RES: u64 = 0xA0;
define_ioctl_data! {
struct drm_mode_card_res, DrmModeCardRes {
fb_id_ptr: u64 [array<u32, count_fbs>],
crtc_id_ptr: u64 [array<u32, count_crtcs>],
connector_id_ptr: u64 [array<u32, count_connectors>],
encoder_id_ptr: u64 [array<u32, count_encoders>],
count_fbs: u32,
count_crtcs: u32,
count_connectors: u32,
count_encoders: u32,
min_width: u32,
max_width: u32,
min_height: u32,
max_height: u32,
}
}
pub const MODE_GET_CRTC: u64 = 0xA1;
define_ioctl_data! {
struct drm_mode_crtc, DrmModeCrtc {
set_connectors_ptr: u64,
count_connectors: u32,
crtc_id: u32,
fb_id: u32,
x: u32,
y: u32,
gamma_size: u32,
mode_valid: u32,
mode: drm_mode_modeinfo,
}
}
pub const MODE_GET_ENCODER: u64 = 0xA6;
define_ioctl_data! {
struct drm_mode_get_encoder, DrmModeGetEncoder {
encoder_id: u32,
encoder_type: u32,
crtc_id: u32,
possible_crtcs: u32,
possible_clones: u32,
}
}
pub const MODE_GET_CONNECTOR: u64 = 0xA7;
define_ioctl_data! {
struct drm_mode_get_connector, DrmModeGetConnector {
encoders_ptr: u64 [array<u32, count_encoders>],
modes_ptr: u64 [array<drm_mode_modeinfo, count_modes>],
props_ptr: u64 [array<u32, count_props>],
prop_values_ptr: u64 [array<u64, count_props>],
count_modes: u32,
count_props: u32,
count_encoders: u32,
encoder_id: u32,
connector_id: u32,
connector_type: u32,
connector_type_id: u32,
connection: u32,
mm_width: u32,
mm_height: u32,
subpixel: u32,
pad: u32,
}
}
pub const MODE_GET_FB: u64 = 0xAD;
define_ioctl_data! {
struct drm_mode_fb_cmd, DrmModeFbCmd {
fb_id: u32,
width: u32,
height: u32,
pitch: u32,
bpp: u32,
depth: u32,
handle: u32,
}
}
pub const MODE_GET_PLANE_RES: u64 = 0xB5;
define_ioctl_data! {
struct drm_mode_get_plane_res, DrmModeGetPlaneRes {
plane_id_ptr: u64 [array<u32, count_planes>],
count_planes: u32,
}
}
pub const MODE_GET_PLANE: u64 = 0xB6;
define_ioctl_data! {
struct drm_mode_get_plane, DrmModeGetPlane {
plane_id: u32,
crtc_id: u32,
fb_id: u32,
possible_crtcs: u32,
gamma_size: u32,
count_format_types: u32,
format_type_ptr: u64 [array<u32, count_format_types>],
}
}
pub const MODE_OBJ_GET_PROPERTIES: u64 = 0xB9;
define_ioctl_data! {
struct drm_mode_obj_get_properties, DrmModeObjGetProperties {
props_ptr: u64 [array<u32, count_props>],
prop_values_ptr: u64 [array<u64, count_props>],
count_props: u32,
obj_id: u32,
obj_type: u32,
}
}
pub const MODE_GET_FB2: u64 = 0xCE;
define_ioctl_data! {
struct drm_mode_fb_cmd2, DrmModeFbCmd2 {
fb_id: u32,
width: u32,
height: u32,
pixel_format: u32,
flags: u32,
handles: [u32; 4],
pitches: [u32; 4],
offsets: [u32; 4],
modifier: [u64; 4],
}
}
+169
View File
@@ -0,0 +1,169 @@
use alloc::vec::Vec;
pub trait IoctlData {
unsafe fn write(&self) -> Vec<u8>;
unsafe fn read_from(&mut self, buf: &[u8]);
}
macro_rules! define_ioctl_data {
(struct $ioctl_ty:ident, $mem_ty:ident {
$($rest:tt)*
}) => {
define_ioctl_data!(
struct $ioctl_ty, $mem_ty { $($rest)* } => (), (), ()
);
};
(struct $ioctl_ty:ident, $mem_ty:ident {
$field:ident: $ty:ty,
$($rest:tt)*
} =>
($($ioctl_fields:tt)*),
($($counted_fields:tt)*),
($($noncounted_fields:tt)*)
) => {
define_ioctl_data!(
struct $ioctl_ty, $mem_ty { $($rest)* } =>
($($ioctl_fields)* $field: $ty,),
($($counted_fields)*),
($($noncounted_fields)* $field: $ty,)
);
};
(struct $ioctl_ty:ident, $mem_ty:ident {
$field:ident: $ty:ty [array<$el:ty, $counted_by:ident>],
$($rest:tt)*
} =>
($($ioctl_fields:tt)*),
($($counted_fields:tt)*),
($($noncounted_fields:tt)*)
) => {
define_ioctl_data!(
struct $ioctl_ty, $mem_ty { $($rest)* } =>
($($ioctl_fields)* $field: $ty,),
($($counted_fields)* $field: $ty [array<$el, $counted_by>],),
($($noncounted_fields)*)
);
};
(struct $ioctl_ty:ident, $mem_ty:ident {} =>
($($ioctl_field:ident: $ioctl_field_ty:ty,)*),
($($counted_field:ident: $counted_ty:ty [array<$el:ty, $counted_by:ident>],)*),
($($noncounted_field:ident: $noncounted_ty:ty,)*)
) => {
// FIXME check ioctl_ty doesn't have padding
const _: $ioctl_ty = $ioctl_ty {
$($ioctl_field: unsafe { mem::zeroed::<$ioctl_field_ty>() },)*
};
#[repr(C)]
pub struct ${concat(__, $mem_ty, Noncounted)} {
$($noncounted_field: $noncounted_ty,)*
}
pub struct $mem_ty<'a> {
noncounted_fields: &'a mut ${concat(__, $mem_ty, Noncounted)},
$($counted_field: &'a mut [$el],)*
}
impl $crate::ioctl_data::IoctlData for $ioctl_ty {
unsafe fn write(&self) -> Vec<u8> {
let noncounted_fields = ${concat(__, $mem_ty, Noncounted)} {
$($noncounted_field: self.$noncounted_field,)*
};
// FIXME use Vec::with_capacity
let mut data = Vec::<u8>::new();
data.extend_from_slice(&unsafe {
mem::transmute::<
${concat(__, $mem_ty, Noncounted)},
[u8; size_of::<${concat(__, $mem_ty, Noncounted)}>()],
>(noncounted_fields)
});
$(
let size = self.$counted_by as usize * size_of::<$el>();
if self.$counted_field as usize != 0 {
let $counted_field = unsafe {
slice::from_raw_parts(self.$counted_field as *const u8, size)
};
data.extend_from_slice(&$counted_field);
} else {
data.extend(iter::repeat(0u8).take(size));
};
)*
data
}
unsafe fn read_from(&mut self, mut buf: &[u8]) {
// FIXME be robust against malicious scheme implementations by returning an error
// when the buf is the wrong size
let noncounted_fields = buf.split_off(..size_of::<${concat(__, $mem_ty, Noncounted)}>()).unwrap();
$(
let size = self.$counted_by as usize * size_of::<$el>();
let $counted_field = buf.split_off(..size).unwrap();
if self.$counted_field as usize != 0 {
unsafe {
slice::from_raw_parts_mut(self.$counted_field as *mut u8, size).copy_from_slice($counted_field);
}
}
)*
assert!(buf.is_empty());
let noncounted_fields = unsafe { &*(noncounted_fields as *const _ as *const ${concat(__, $mem_ty, Noncounted)}) };
$(self.$noncounted_field = noncounted_fields.$noncounted_field;)*
}
}
impl<'a> $mem_ty<'a> {
pub fn with(
mut buf: &'a mut [u8],
f: impl FnOnce($mem_ty<'a>) -> syscall::Result<usize>,
) -> syscall::Result<usize> {
let noncounted_fields = buf.split_off_mut(..size_of::<${concat(__, $mem_ty, Noncounted)}>())
.ok_or(syscall::Error::new(syscall::EINVAL))?;
let noncounted_fields = unsafe { &mut *(noncounted_fields as *mut _ as *mut ${concat(__, $mem_ty, Noncounted)}) };
$(
let $counted_field = buf.split_off_mut(..noncounted_fields.$counted_by as usize * size_of::<$el>())
.ok_or(syscall::Error::new(syscall::EINVAL))?;
let $counted_field = unsafe {
slice::from_raw_parts_mut($counted_field as *mut _ as *mut $el, noncounted_fields.$counted_by as usize)
};
)*
if !buf.is_empty() {
return Err(syscall::Error::new(syscall::EINVAL));
}
Ok( f($mem_ty {
noncounted_fields,
$($counted_field,)*
})?)
}
$(
pub fn $noncounted_field(&self) -> $noncounted_ty {
self.noncounted_fields.$noncounted_field
}
/// Should not be called for fields used as array length
pub fn ${concat(set_, $noncounted_field)}(&mut self, data: $noncounted_ty) {
self.noncounted_fields.$noncounted_field = data;
}
)*
$(
pub fn $counted_field(&self) -> &[$el] {
self.$counted_field
}
pub fn ${concat(set_, $counted_field)}(&mut self, data: &[$el]) {
let copied_count = cmp::min(data.len(), self.$counted_field.len());
self.$counted_field[..copied_count].copy_from_slice(&data[..copied_count]);
self.noncounted_fields.$counted_by = data.len() as _;
}
)*
}
};
}
+10
View File
@@ -0,0 +1,10 @@
#![feature(macro_metavar_expr_concat)]
#![no_std]
extern crate alloc;
#[macro_use]
mod ioctl_data;
pub use ioctl_data::IoctlData;
pub mod drm;
+7 -5
View File
@@ -27,12 +27,14 @@ impl winsize {
}
}
pub use self::sys::*;
#[cfg(target_os = "linux")]
pub use self::linux::*;
#[cfg(target_os = "linux")]
#[path = "linux.rs"]
pub mod sys;
pub mod linux;
#[cfg(target_os = "redox")]
#[path = "redox.rs"]
pub mod sys;
pub use self::redox::*;
#[cfg(target_os = "redox")]
pub mod redox;
+120
View File
@@ -0,0 +1,120 @@
use core::slice;
use redox_ioctl::{IoctlData, drm::*};
use crate::{
error::{Errno, Result},
header::errno::EINVAL,
platform::types::*,
};
use super::IoctlBuffer;
const DRM_FORMAT_ARGB8888: u32 = 0x34325241; // 'AR24' fourcc code, for ARGB8888
fn id_index(id: u32) -> u32 {
id & 0xFF
}
fn conn_id(i: u32) -> u32 {
id_index(i) | (1 << 8)
}
fn crtc_id(i: u32) -> u32 {
id_index(i) | (1 << 9)
}
fn enc_id(i: u32) -> u32 {
id_index(i) | (1 << 10)
}
fn fb_id(i: u32) -> u32 {
id_index(i) | (1 << 11)
}
fn fb_handle_id(i: u32) -> u32 {
id_index(i) | (1 << 12)
}
fn plane_id(i: u32) -> u32 {
id_index(i) | (1 << 13)
}
unsafe fn copy_array<T: Copy>(src: &[T], dst_ptr: *mut T, dst_len: usize) -> usize {
let dst = slice::from_raw_parts_mut(dst_ptr, dst_len);
dst.copy_from_slice(&src[..src.len().min(dst_len)]);
src.len()
}
struct Dev {
fd: c_int,
}
impl Dev {
fn new(fd: c_int) -> Result<Self> {
//TODO: check display scheme using fpath?
Ok(Self { fd })
}
unsafe fn call<T>(&self, payload: &mut T, func: u64) -> syscall::Result<usize> {
let bytes = slice::from_raw_parts_mut(payload as *mut T as *mut u8, size_of::<T>());
redox_rt::sys::sys_call(
self.fd as usize,
bytes,
syscall::CallFlags::empty(),
&[func],
)
}
unsafe fn read_write_ioctl<T: IoctlData>(
&self,
mut buf: IoctlBuffer,
func: u64,
) -> Result<c_int> {
let mut data = buf.read::<T>()?;
let mut wire = data.write();
let res = redox_rt::sys::sys_call(
self.fd as usize,
&mut wire,
syscall::CallFlags::empty(),
&[func],
)?;
data.read_from(&wire);
buf.write(data)?;
Ok(res as c_int)
}
unsafe fn write_ioctl<T: IoctlData>(&self, mut buf: IoctlBuffer, func: u64) -> Result<c_int> {
let mut data = buf.read::<T>()?;
let mut wire = data.write();
let res = redox_rt::sys::sys_call(
self.fd as usize,
&mut wire,
syscall::CallFlags::empty(),
&[func],
)?;
Ok(res as c_int)
}
}
pub(super) unsafe fn ioctl(fd: c_int, func: u8, buf: IoctlBuffer) -> Result<c_int> {
let dev = Dev::new(fd)?;
match func {
0x00 => dev.read_write_ioctl::<drm_version>(buf, VERSION),
0x0C => dev.read_write_ioctl::<drm_get_cap>(buf, GET_CAP),
0x0D => dev.write_ioctl::<drm_set_client_cap>(buf, SET_CLIENT_CAP),
0xA0 => dev.read_write_ioctl::<drm_mode_card_res>(buf, MODE_CARD_RES),
0xA1 => dev.read_write_ioctl::<drm_mode_crtc>(buf, MODE_GET_CRTC),
0xA6 => dev.read_write_ioctl::<drm_mode_get_encoder>(buf, MODE_GET_ENCODER),
0xA7 => dev.read_write_ioctl::<drm_mode_get_connector>(buf, MODE_GET_CONNECTOR),
0xAD => dev.read_write_ioctl::<drm_mode_fb_cmd>(buf, MODE_GET_FB),
0xB5 => dev.read_write_ioctl::<drm_mode_get_plane_res>(buf, MODE_GET_PLANE_RES),
0xB6 => dev.read_write_ioctl::<drm_mode_get_plane>(buf, MODE_GET_PLANE),
0xB9 => dev.read_write_ioctl::<drm_mode_obj_get_properties>(buf, MODE_OBJ_GET_PROPERTIES),
0xCE => dev.read_write_ioctl::<drm_mode_fb_cmd2>(buf, MODE_GET_FB2),
_ => {
eprintln!("unimplemented DRM ioctl({}, 0x{:02x}, {:?})", fd, func, buf);
Err(Errno(EINVAL))
}
}
}
@@ -1,4 +1,4 @@
use core::{mem, slice};
use core::{mem, ptr, slice};
use redox_rt::proc::FdGuard;
use syscall;
@@ -13,6 +13,8 @@ use crate::{
use super::winsize;
mod drm;
pub const TCGETS: c_ulong = 0x5401;
pub const TCSETS: c_ulong = 0x5402;
pub const TCSETSW: c_ulong = 0x5403;
@@ -61,6 +63,47 @@ fn dup_write<T>(fd: c_int, name: &str, t: &T) -> Result<usize> {
Ok(bytes / size)
}
#[derive(Debug)]
enum IoctlBuffer {
None,
Read(*mut c_void, usize), // read (write to userspace)
Write(*const c_void, usize), // write (read from userspace)
ReadWrite(*mut c_void, usize),
}
impl IoctlBuffer {
unsafe fn read<T>(&self) -> Result<T> {
let (ptr, size) = match *self {
Self::Write(ptr, size) => (ptr, size),
Self::ReadWrite(ptr, size) => (ptr as *const c_void, size),
_ => {
return Err(Errno(EINVAL));
}
};
if size == mem::size_of::<T>() {
let value = unsafe { ptr::read(ptr as *const T) };
Ok(value)
} else {
Err(Errno(EINVAL))
}
}
unsafe fn write<T>(&mut self, value: T) -> Result<()> {
let (ptr, size) = match *self {
Self::Read(ptr, size) | Self::ReadWrite(ptr, size) => (ptr, size),
_ => {
return Err(Errno(EINVAL));
}
};
if size == mem::size_of::<T>() {
unsafe { ptr::write(ptr as *mut T, value) };
Ok(())
} else {
Err(Errno(EINVAL))
}
}
}
unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result<c_int> {
match request {
FIONBIO => {
@@ -120,7 +163,25 @@ unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result<c
eprintln!("TODO: ioctl SIOCATMARK");
}
_ => {
return Err(Errno(EINVAL));
// See https://docs.kernel.org/userspace-api/ioctl/ioctl-decoding.html for details
let dir = (request >> 30) & 0b11;
let size = ((request >> 16) & 0x3FFF) as usize;
let name = (((request >> 8) & 0xFF) as u8) as char;
let func = (request & 0xFF) as u8;
match name {
'd' => {
let buf = match dir {
0b10 => IoctlBuffer::Read(out, size),
0b01 => IoctlBuffer::Write(out, size),
0b11 => IoctlBuffer::ReadWrite(out, size),
_ => IoctlBuffer::None,
};
return drm::ioctl(fd, func, buf);
}
_ => {
return Err(Errno(EINVAL));
}
}
}
}
Ok(0)