// SPDX-License-Identifier: MPL-2.0 use core::mem; use super::{ sched_get_priority_max::{rt_to_static, static_to_rt, SCHED_PRIORITY_RANGE}, SyscallReturn, }; use crate::{ prelude::*, process::posix_thread::thread_table, sched::{Nice, RealTimePolicy, SchedAttr, SchedPolicy}, thread::Tid, }; pub(super) const SCHED_NORMAL: u32 = 0; pub(super) const SCHED_FIFO: u32 = 1; pub(super) const SCHED_RR: u32 = 2; // pub(super) const SCHED_BATCH: u32 = 3; // not supported (never). // SCHED_ISO: reserved but not implemented yet on Linux. pub(super) const SCHED_IDLE: u32 = 5; // pub(super) const SCHED_DEADLINE: u32 = 6; // not supported yet. // pub(super) const SCHED_EXT: u32 = 7; // not supported (never). #[derive(Default, Debug, Pod, Clone, Copy)] #[repr(C)] pub(super) struct LinuxSchedAttr { // Size of this structure pub(super) size: u32, // Policy (SCHED_*) pub(super) sched_policy: u32, // Flags pub(super) sched_flags: u64, // Nice value (SCHED_NORMAL, SCHED_BATCH) pub(super) sched_nice: i32, // Static priority (SCHED_FIFO, SCHED_RR) pub(super) sched_priority: u32, // For SCHED_DEADLINE pub(super) sched_runtime: u64, pub(super) sched_deadline: u64, pub(super) sched_period: u64, // Utilization hints pub(super) sched_util_min: u32, pub(super) sched_util_max: u32, } impl TryFrom for LinuxSchedAttr { type Error = Error; fn try_from(value: SchedPolicy) -> Result { Ok(match value { SchedPolicy::Stop => LinuxSchedAttr { sched_policy: SCHED_FIFO, sched_priority: 99, // Linux uses 99 as the default priority for STOP tasks. ..Default::default() }, SchedPolicy::RealTime { rt_prio, rt_policy } => LinuxSchedAttr { sched_policy: match rt_policy { RealTimePolicy::Fifo => SCHED_FIFO, RealTimePolicy::RoundRobin { .. } => SCHED_RR, }, sched_priority: rt_to_static(rt_prio), ..Default::default() }, SchedPolicy::Fair(nice) => LinuxSchedAttr { sched_policy: SCHED_NORMAL, sched_nice: nice.value().get().into(), ..Default::default() }, SchedPolicy::Idle => LinuxSchedAttr { sched_policy: SCHED_IDLE, ..Default::default() }, }) } } impl TryFrom for SchedPolicy { type Error = Error; fn try_from(value: LinuxSchedAttr) -> Result { Ok(match value.sched_policy { SCHED_FIFO | SCHED_RR => SchedPolicy::RealTime { rt_prio: static_to_rt(value.sched_priority)?, rt_policy: match value.sched_policy { SCHED_FIFO => RealTimePolicy::Fifo, SCHED_RR => RealTimePolicy::RoundRobin { base_slice_factor: None, }, _ => unreachable!(), }, }, _ if value.sched_priority != 0 => { return Err(Error::with_message( Errno::EINVAL, "Invalid scheduling priority", )) } SCHED_NORMAL => SchedPolicy::Fair(Nice::new( i8::try_from(value.sched_nice)? .try_into() .map_err(|msg| Error::with_message(Errno::EINVAL, msg))?, )), SCHED_IDLE => SchedPolicy::Idle, _ => { return Err(Error::with_message( Errno::EINVAL, "Invalid scheduling policy", )) } }) } } pub(super) fn read_linux_sched_attr_from_user( addr: Vaddr, ctx: &Context, ) -> Result { let type_size = mem::size_of::(); let space = ctx.user_space(); let mut attr = LinuxSchedAttr::default(); space.read_bytes( addr, &mut VmWriter::from(&mut attr.as_bytes_mut()[..mem::size_of::()]), )?; let size = type_size.min(attr.size as usize); space.read_bytes(addr, &mut VmWriter::from(&mut attr.as_bytes_mut()[..size]))?; if let Some(additional_size) = attr.size.checked_sub(type_size as u32) { let mut buf = vec![0; additional_size as usize]; space.read_bytes(addr + type_size, &mut VmWriter::from(&mut *buf))?; if buf.iter().any(|&b| b != 0) { return Err(Error::with_message(Errno::E2BIG, "too big sched_attr")); } } Ok(attr) } pub(super) fn write_linux_sched_attr_to_user( mut attr: LinuxSchedAttr, addr: Vaddr, user_size: u32, ctx: &Context, ) -> Result<()> { let space = ctx.user_space(); attr.size = (mem::size_of::() as u32).min(user_size); let range = SCHED_PRIORITY_RANGE .get(attr.sched_policy as usize) .ok_or_else(|| Error::with_message(Errno::EINVAL, "invalid scheduling policy"))?; attr.sched_util_min = *range.start(); attr.sched_util_max = *range.end(); space.write_bytes( addr, &mut VmReader::from(&attr.as_bytes()[..attr.size as usize]), )?; Ok(()) } pub(super) fn access_sched_attr_with( tid: Tid, ctx: &Context, f: impl FnOnce(&SchedAttr) -> Result, ) -> Result { match tid { 0 => f(ctx.thread.sched_attr()), _ if tid > (i32::MAX as u32) => Err(Error::with_message(Errno::EINVAL, "invalid tid")), _ => f(thread_table::get_thread(tid) .ok_or_else(|| Error::with_message(Errno::ESRCH, "thread does not exist"))? .sched_attr()), } } pub fn sys_sched_getattr( tid: Tid, addr: Vaddr, user_size: u32, flags: u32, ctx: &Context, ) -> Result { if flags != 0 { // TODO: support flags soch as `RESET_ON_FORK`. return Err(Error::with_message(Errno::EINVAL, "unsupported flags")); } let policy = access_sched_attr_with(tid, ctx, |attr| Ok(attr.policy()))?; let attr: LinuxSchedAttr = policy.try_into()?; write_linux_sched_attr_to_user(attr, addr, user_size, ctx) .map_err(|_| Error::new(Errno::EINVAL))?; Ok(SyscallReturn::Return(0)) }