rt 0.19.1

A real-time operating system capable of full preemption
Documentation
use core::{
    cell::UnsafeCell,
    ffi::c_int,
    mem::zeroed,
    panic::{RefUnwindSafe, UnwindSafe},
    ptr::null_mut,
};

use crate::{
    bindings::{
        rt_atomic_int, rt_sem, rt_sem_post, rt_sem_post_n, rt_sem_timedwait, rt_sem_trywait,
        rt_sem_wait, rt_syscall_args, rt_syscall_args_sem_post, rt_syscall_pendable,
        rt_syscall_record,
    },
    list::list_init,
    ptr_macros::{ptr_to_field, ptr_to_field_mut},
    tick::Utick,
};

#[repr(transparent)]
pub struct Sem {
    sem: UnsafeCell<rt_sem>,
}

unsafe impl Send for Sem {}
unsafe impl Sync for Sem {}
impl UnwindSafe for Sem {}
impl RefUnwindSafe for Sem {}

// Also used by cond and queue.
pub(crate) const fn c_sem_init(sem: *mut rt_sem, value: i32, max_value: i32) -> rt_sem {
    rt_sem {
        value: value as rt_atomic_int,
        max_value: max_value as c_int,
        wait_list: list_init(ptr_to_field_mut!(sem, wait_list)),
        num_waiters: 0,
        post_record: rt_syscall_record {
            next: null_mut(),
            args: unsafe {
                let mut x: rt_syscall_args = zeroed();
                x.sem_post = rt_syscall_args_sem_post { sem, n: 1 };
                x
            },
            syscall: rt_syscall_pendable::RT_SYSCALL_PENDABLE_SEM_POST,
            pending: unsafe { zeroed() },
        },
    }
}

impl Sem {
    /// Initialize a new `Sem` with the given data.
    ///
    /// # Safety
    ///
    /// This must be called with a pointer to a `static` `Sem` and be used to initialize that same
    /// `Sem`. Users should use the `rt::sync::sem!` macro to create a `Sem` rather than call
    /// `Sem::init` directly.
    #[must_use]
    pub const unsafe fn init(this: *const Self, value: i32, max_value: i32) -> Sem {
        let sem = UnsafeCell::raw_get(ptr_to_field!(this, sem));
        Sem {
            sem: UnsafeCell::new(c_sem_init(sem, value, max_value)),
        }
    }

    #[inline]
    pub fn post(&self) {
        unsafe { rt_sem_post(self.sem.get()) }
    }

    #[inline]
    pub fn post_n(&self, n: i32) {
        unsafe { rt_sem_post_n(self.sem.get(), n) }
    }

    #[inline]
    pub fn wait(&self) {
        unsafe { rt_sem_wait(self.sem.get()) }
    }

    #[inline]
    pub fn try_wait(&self) -> bool {
        unsafe { rt_sem_trywait(self.sem.get()) }
    }

    #[inline]
    pub fn timed_wait(&self, ticks: Utick) -> bool {
        unsafe { rt_sem_timedwait(self.sem.get(), ticks) }
    }

    #[inline]
    pub fn acquire(&self) {
        self.wait()
    }

    #[inline]
    pub fn release(&self) {
        self.post()
    }

    #[inline]
    pub fn access(&self) -> SemGuard<'_> {
        self.acquire();
        SemGuard { sem: self }
    }
}

pub struct SemGuard<'a> {
    sem: &'a Sem,
}

impl Drop for SemGuard<'_> {
    #[inline]
    fn drop(&mut self) {
        self.sem.release();
    }
}

#[macro_export]
macro_rules! sem {
    ($name: ident, $count: expr, $max: expr) => {
        static $name: $crate::sync::Sem = {
            let ptr = &raw const $name;
            let count = $count;
            let max = $max;
            unsafe { $crate::sync::Sem::init(ptr, count, max) }
        };
    };

    ($name: ident, $count: expr) => {
        $crate::sem!($name, $count, i32::MAX);
    };

    ($name: ident) => {
        $crate::sem!($name, 0, i32::MAX);
    };
}

#[macro_export]
macro_rules! sem_binary {
    ($name: ident) => {
        $crate::sem!($name, 0, 1);
    };
}

#[cfg(test)]
mod tests {
    #[test]
    fn fast_path() {
        sem!(SEM);
        SEM.post();
        SEM.post();
        assert!(SEM.try_wait());
        assert!(SEM.try_wait());
        assert!(!SEM.try_wait());
    }

    #[test]
    fn binary_fast_path() {
        sem_binary!(SEM);
        SEM.post();
        SEM.post();
        assert!(SEM.try_wait());
        assert!(!SEM.try_wait());
    }
}