diff --git a/Cargo.lock b/Cargo.lock index c194a25be8..42bed02ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index be96f42c01..c5191f508e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/redox-ioctl/Cargo.toml b/redox-ioctl/Cargo.toml new file mode 100644 index 0000000000..1096248d3a --- /dev/null +++ b/redox-ioctl/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "redox-ioctl" +authors = ["bjorn3 "] +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] diff --git a/redox-ioctl/src/drm.rs b/redox-ioctl/src/drm.rs new file mode 100644 index 0000000000..edab1d5424 --- /dev/null +++ b/redox-ioctl/src/drm.rs @@ -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], + date_len: __kernel_size_t, + date: *mut c_char [array], + desc_len: __kernel_size_t, + desc: *mut c_char [array], + } +} + +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], + crtc_id_ptr: u64 [array], + connector_id_ptr: u64 [array], + encoder_id_ptr: u64 [array], + 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], + modes_ptr: u64 [array], + props_ptr: u64 [array], + prop_values_ptr: u64 [array], + 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], + 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], + } +} + +pub const MODE_OBJ_GET_PROPERTIES: u64 = 0xB9; +define_ioctl_data! { + struct drm_mode_obj_get_properties, DrmModeObjGetProperties { + props_ptr: u64 [array], + prop_values_ptr: u64 [array], + 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], + } +} diff --git a/redox-ioctl/src/ioctl_data.rs b/redox-ioctl/src/ioctl_data.rs new file mode 100644 index 0000000000..f9673f6d7d --- /dev/null +++ b/redox-ioctl/src/ioctl_data.rs @@ -0,0 +1,169 @@ +use alloc::vec::Vec; + +pub trait IoctlData { + unsafe fn write(&self) -> Vec; + 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 { + let noncounted_fields = ${concat(__, $mem_ty, Noncounted)} { + $($noncounted_field: self.$noncounted_field,)* + }; + // FIXME use Vec::with_capacity + let mut data = Vec::::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, + ) -> syscall::Result { + 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 _; + } + )* + } + }; +} diff --git a/redox-ioctl/src/lib.rs b/redox-ioctl/src/lib.rs new file mode 100644 index 0000000000..2405f5eeb2 --- /dev/null +++ b/redox-ioctl/src/lib.rs @@ -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; diff --git a/src/header/sys_ioctl/mod.rs b/src/header/sys_ioctl/mod.rs index c1716d0861..a5892f9a17 100644 --- a/src/header/sys_ioctl/mod.rs +++ b/src/header/sys_ioctl/mod.rs @@ -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; diff --git a/src/header/sys_ioctl/redox/drm.rs b/src/header/sys_ioctl/redox/drm.rs new file mode 100644 index 0000000000..a0692bdd95 --- /dev/null +++ b/src/header/sys_ioctl/redox/drm.rs @@ -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(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 { + //TODO: check display scheme using fpath? + Ok(Self { fd }) + } + + unsafe fn call(&self, payload: &mut T, func: u64) -> syscall::Result { + let bytes = slice::from_raw_parts_mut(payload as *mut T as *mut u8, size_of::()); + redox_rt::sys::sys_call( + self.fd as usize, + bytes, + syscall::CallFlags::empty(), + &[func], + ) + } + + unsafe fn read_write_ioctl( + &self, + mut buf: IoctlBuffer, + func: u64, + ) -> Result { + let mut data = buf.read::()?; + 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(&self, mut buf: IoctlBuffer, func: u64) -> Result { + let mut data = buf.read::()?; + 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 { + let dev = Dev::new(fd)?; + match func { + 0x00 => dev.read_write_ioctl::(buf, VERSION), + 0x0C => dev.read_write_ioctl::(buf, GET_CAP), + 0x0D => dev.write_ioctl::(buf, SET_CLIENT_CAP), + 0xA0 => dev.read_write_ioctl::(buf, MODE_CARD_RES), + 0xA1 => dev.read_write_ioctl::(buf, MODE_GET_CRTC), + 0xA6 => dev.read_write_ioctl::(buf, MODE_GET_ENCODER), + 0xA7 => dev.read_write_ioctl::(buf, MODE_GET_CONNECTOR), + 0xAD => dev.read_write_ioctl::(buf, MODE_GET_FB), + 0xB5 => dev.read_write_ioctl::(buf, MODE_GET_PLANE_RES), + 0xB6 => dev.read_write_ioctl::(buf, MODE_GET_PLANE), + 0xB9 => dev.read_write_ioctl::(buf, MODE_OBJ_GET_PROPERTIES), + 0xCE => dev.read_write_ioctl::(buf, MODE_GET_FB2), + _ => { + eprintln!("unimplemented DRM ioctl({}, 0x{:02x}, {:?})", fd, func, buf); + Err(Errno(EINVAL)) + } + } +} diff --git a/src/header/sys_ioctl/redox.rs b/src/header/sys_ioctl/redox/mod.rs similarity index 64% rename from src/header/sys_ioctl/redox.rs rename to src/header/sys_ioctl/redox/mod.rs index 5cf69b4c5b..c859e6193c 100644 --- a/src/header/sys_ioctl/redox.rs +++ b/src/header/sys_ioctl/redox/mod.rs @@ -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(fd: c_int, name: &str, t: &T) -> Result { 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(&self) -> Result { + 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::() { + let value = unsafe { ptr::read(ptr as *const T) }; + Ok(value) + } else { + Err(Errno(EINVAL)) + } + } + + unsafe fn write(&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::() { + 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 { match request { FIONBIO => { @@ -120,7 +163,25 @@ unsafe fn ioctl_inner(fd: c_int, request: c_ulong, out: *mut c_void) -> Result { - 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)