// SPDX-License-Identifier: GPL-2.0

// Copyright (C) 2024 Google LLC.

//! Support for defining statics containing locks.

use crate::{
    str::{CStr, CStrExt as _},
    sync::lock::{Backend, Guard, Lock},
    sync::{LockClassKey, LockedBy},
    types::Opaque,
};
use core::{
    cell::UnsafeCell,
    marker::{PhantomData, PhantomPinned},
    pin::Pin,
};

/// Trait implemented for marker types for global locks.
///
/// See [`global_lock!`] for examples.
pub trait GlobalLockBackend {
    /// The name for this global lock.
    const NAME: &'static CStr;
    /// Item type stored in this global lock.
    type Item: 'static;
    /// The backend used for this global lock.
    type Backend: Backend + 'static;
    /// The class for this global lock.
    fn get_lock_class() -> Pin<&'static LockClassKey>;
}

/// Type used for global locks.
///
/// See [`global_lock!`] for examples.
pub struct GlobalLock<B: GlobalLockBackend> {
    inner: Lock<B::Item, B::Backend>,
}

impl<B: GlobalLockBackend> GlobalLock<B> {
    /// Creates a global lock.
    ///
    /// # Safety
    ///
    /// * Before any other method on this lock is called, [`Self::init`] must be called.
    /// * The type `B` must not be used with any other lock.
    pub const unsafe fn new(data: B::Item) -> Self {
        Self {
            inner: Lock {
                state: Opaque::uninit(),
                data: UnsafeCell::new(data),
                _pin: PhantomPinned,
            },
        }
    }

    /// Initializes a global lock.
    ///
    /// # Safety
    ///
    /// Must not be called more than once on a given lock.
    pub unsafe fn init(&'static self) {
        // SAFETY: The pointer to `state` is valid for the duration of this call, and both `name`
        // and `key` are valid indefinitely. The `state` is pinned since we have a `'static`
        // reference to `self`.
        //
        // We have exclusive access to the `state` since the caller of `new` promised to call
        // `init` before using any other methods. As `init` can only be called once, all other
        // uses of this lock must happen after this call.
        unsafe {
            B::Backend::init(
                self.inner.state.get(),
                B::NAME.as_char_ptr(),
                B::get_lock_class().as_ptr(),
            )
        }
    }

    /// Lock this global lock.
    pub fn lock(&'static self) -> GlobalGuard<B> {
        GlobalGuard {
            inner: self.inner.lock(),
        }
    }

    /// Try to lock this global lock.
    pub fn try_lock(&'static self) -> Option<GlobalGuard<B>> {
        Some(GlobalGuard {
            inner: self.inner.try_lock()?,
        })
    }
}

/// A guard for a [`GlobalLock`].
///
/// See [`global_lock!`] for examples.
pub struct GlobalGuard<B: GlobalLockBackend> {
    inner: Guard<'static, B::Item, B::Backend>,
}

impl<B: GlobalLockBackend> core::ops::Deref for GlobalGuard<B> {
    type Target = B::Item;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<B: GlobalLockBackend> core::ops::DerefMut for GlobalGuard<B>
where
    B::Item: Unpin,
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

/// A version of [`LockedBy`] for a [`GlobalLock`].
///
/// See [`global_lock!`] for examples.
pub struct GlobalLockedBy<T: ?Sized, B: GlobalLockBackend> {
    _backend: PhantomData<B>,
    value: UnsafeCell<T>,
}

// SAFETY: The same thread-safety rules as `LockedBy` apply to `GlobalLockedBy`.
unsafe impl<T, B> Send for GlobalLockedBy<T, B>
where
    T: ?Sized,
    B: GlobalLockBackend,
    LockedBy<T, B::Item>: Send,
{
}

// SAFETY: The same thread-safety rules as `LockedBy` apply to `GlobalLockedBy`.
unsafe impl<T, B> Sync for GlobalLockedBy<T, B>
where
    T: ?Sized,
    B: GlobalLockBackend,
    LockedBy<T, B::Item>: Sync,
{
}

impl<T, B: GlobalLockBackend> GlobalLockedBy<T, B> {
    /// Create a new [`GlobalLockedBy`].
    ///
    /// The provided value will be protected by the global lock indicated by `B`.
    pub fn new(val: T) -> Self {
        Self {
            value: UnsafeCell::new(val),
            _backend: PhantomData,
        }
    }
}

impl<T: ?Sized, B: GlobalLockBackend> GlobalLockedBy<T, B> {
    /// Access the value immutably.
    ///
    /// The caller must prove shared access to the lock.
    pub fn as_ref<'a>(&'a self, _guard: &'a GlobalGuard<B>) -> &'a T {
        // SAFETY: The lock is globally unique, so there can only be one guard.
        unsafe { &*self.value.get() }
    }

    /// Access the value mutably.
    ///
    /// The caller must prove shared exclusive to the lock.
    pub fn as_mut<'a>(&'a self, _guard: &'a mut GlobalGuard<B>) -> &'a mut T {
        // SAFETY: The lock is globally unique, so there can only be one guard.
        unsafe { &mut *self.value.get() }
    }

    /// Access the value mutably directly.
    ///
    /// The caller has exclusive access to this `GlobalLockedBy`, so they do not need to hold the
    /// lock.
    pub fn get_mut(&mut self) -> &mut T {
        self.value.get_mut()
    }
}

/// Defines a global lock.
///
/// The global mutex must be initialized before first use. Usually this is done by calling
/// [`GlobalLock::init`] in the module initializer.
///
/// # Examples
///
/// A global counter:
///
/// ```
/// # mod ex {
/// # use kernel::prelude::*;
/// kernel::sync::global_lock! {
///     // SAFETY: Initialized in module initializer before first use.
///     unsafe(uninit) static MY_COUNTER: Mutex<u32> = 0;
/// }
///
/// fn increment_counter() -> u32 {
///     let mut guard = MY_COUNTER.lock();
///     *guard += 1;
///     *guard
/// }
///
/// impl kernel::Module for MyModule {
///     fn init(_module: &'static ThisModule) -> Result<Self> {
///         // SAFETY: Called exactly once.
///         unsafe { MY_COUNTER.init() };
///
///         Ok(MyModule {})
///     }
/// }
/// # struct MyModule {}
/// # }
/// ```
///
/// A global mutex used to protect all instances of a given struct:
///
/// ```
/// # mod ex {
/// # use kernel::prelude::*;
/// use kernel::sync::{GlobalGuard, GlobalLockedBy};
///
/// kernel::sync::global_lock! {
///     // SAFETY: Initialized in module initializer before first use.
///     unsafe(uninit) static MY_MUTEX: Mutex<()> = ();
/// }
///
/// /// All instances of this struct are protected by `MY_MUTEX`.
/// struct MyStruct {
///     my_counter: GlobalLockedBy<u32, MY_MUTEX>,
/// }
///
/// impl MyStruct {
///     /// Increment the counter in this instance.
///     ///
///     /// The caller must hold the `MY_MUTEX` mutex.
///     fn increment(&self, guard: &mut GlobalGuard<MY_MUTEX>) -> u32 {
///         let my_counter = self.my_counter.as_mut(guard);
///         *my_counter += 1;
///         *my_counter
///     }
/// }
///
/// impl kernel::Module for MyModule {
///     fn init(_module: &'static ThisModule) -> Result<Self> {
///         // SAFETY: Called exactly once.
///         unsafe { MY_MUTEX.init() };
///
///         Ok(MyModule {})
///     }
/// }
/// # struct MyModule {}
/// # }
/// ```
#[macro_export]
macro_rules! global_lock {
    {
        $(#[$meta:meta])* $pub:vis
        unsafe(uninit) static $name:ident: $kind:ident<$valuety:ty> = $value:expr;
    } => {
        #[doc = ::core::concat!(
            "Backend type used by [`",
            ::core::stringify!($name),
            "`](static@",
            ::core::stringify!($name),
            ")."
        )]
        #[allow(non_camel_case_types, unreachable_pub)]
        $pub enum $name {}

        impl $crate::sync::lock::GlobalLockBackend for $name {
            const NAME: &'static $crate::str::CStr = $crate::c_str!(::core::stringify!($name));
            type Item = $valuety;
            type Backend = $crate::global_lock_inner!(backend $kind);

            fn get_lock_class() -> Pin<&'static $crate::sync::LockClassKey> {
                $crate::static_lock_class!()
            }
        }

        $(#[$meta])*
        $pub static $name: $crate::sync::lock::GlobalLock<$name> = {
            // Defined here to be outside the unsafe scope.
            let init: $valuety = $value;

            // SAFETY:
            // * The user of this macro promises to initialize the macro before use.
            // * We are only generating one static with this backend type.
            unsafe { $crate::sync::lock::GlobalLock::new(init) }
        };
    };
}
pub use global_lock;

#[doc(hidden)]
#[macro_export]
macro_rules! global_lock_inner {
    (backend Mutex) => {
        $crate::sync::lock::mutex::MutexBackend
    };
    (backend SpinLock) => {
        $crate::sync::lock::spinlock::SpinLockBackend
    };
}
