Commit ce32e2d4 authored by Viresh Kumar's avatar Viresh Kumar
Browse files

rust: opp: Add abstractions for the configuration options



Introduce Rust abstractions for the OPP core configuration options,
enabling safe access to various configurable aspects of the OPP
framework.

Signed-off-by: default avatarViresh Kumar <viresh.kumar@linaro.org>
parent d52c7e86
Loading
Loading
Loading
Loading
+293 −2
Original line number Diff line number Diff line
@@ -12,12 +12,29 @@
    clk::Hertz,
    cpumask::{Cpumask, CpumaskVar},
    device::Device,
    error::{code::*, from_err_ptr, to_result, Error, Result},
    error::{code::*, from_err_ptr, from_result, to_result, Error, Result, VTABLE_DEFAULT_ERROR},
    ffi::c_ulong,
    prelude::*,
    str::CString,
    types::{ARef, AlwaysRefCounted, Opaque},
};

use core::ptr;
use core::{marker::PhantomData, ptr};

use macros::vtable;

/// Creates a null-terminated slice of pointers to [`Cstring`]s.
fn to_c_str_array(names: &[CString]) -> Result<KVec<*const u8>> {
    // Allocated a null-terminated vector of pointers.
    let mut list = KVec::with_capacity(names.len() + 1, GFP_KERNEL)?;

    for name in names.iter() {
        list.push(name.as_ptr() as _, GFP_KERNEL)?;
    }

    list.push(ptr::null(), GFP_KERNEL)?;
    Ok(list)
}

/// The voltage unit.
///
@@ -205,6 +222,280 @@ pub enum SearchType {
    Ceil,
}

/// OPP configuration callbacks.
///
/// Implement this trait to customize OPP clock and regulator setup for your device.
#[vtable]
pub trait ConfigOps {
    /// This is typically used to scale clocks when transitioning between OPPs.
    #[inline]
    fn config_clks(_dev: &Device, _table: &Table, _opp: &OPP, _scaling_down: bool) -> Result {
        build_error!(VTABLE_DEFAULT_ERROR)
    }

    /// This provides access to the old and new OPPs, allowing for safe regulator adjustments.
    #[inline]
    fn config_regulators(
        _dev: &Device,
        _opp_old: &OPP,
        _opp_new: &OPP,
        _data: *mut *mut bindings::regulator,
        _count: u32,
    ) -> Result {
        build_error!(VTABLE_DEFAULT_ERROR)
    }
}

/// OPP configuration token.
///
/// Returned by the OPP core when configuration is applied to a [`Device`]. The associated
/// configuration is automatically cleared when the token is dropped.
pub struct ConfigToken(i32);

impl Drop for ConfigToken {
    fn drop(&mut self) {
        // SAFETY: This is the same token value returned by the C code via `dev_pm_opp_set_config`.
        unsafe { bindings::dev_pm_opp_clear_config(self.0) };
    }
}

/// OPP configurations.
///
/// Rust abstraction for the C `struct dev_pm_opp_config`.
///
/// ## Examples
///
/// The following example demonstrates how to set OPP property-name configuration for a [`Device`].
///
/// ```
/// use kernel::device::Device;
/// use kernel::error::Result;
/// use kernel::opp::{Config, ConfigOps, ConfigToken};
/// use kernel::str::CString;
/// use kernel::types::ARef;
/// use kernel::macros::vtable;
///
/// #[derive(Default)]
/// struct Driver;
///
/// #[vtable]
/// impl ConfigOps for Driver {}
///
/// fn configure(dev: &ARef<Device>) -> Result<ConfigToken> {
///     let name = CString::try_from_fmt(fmt!("{}", "slow"))?;
///
///     // The OPP configuration is cleared once the [`ConfigToken`] goes out of scope.
///     Config::<Driver>::new()
///         .set_prop_name(name)?
///         .set(dev)
/// }
/// ```
#[derive(Default)]
pub struct Config<T: ConfigOps>
where
    T: Default,
{
    clk_names: Option<KVec<CString>>,
    prop_name: Option<CString>,
    regulator_names: Option<KVec<CString>>,
    supported_hw: Option<KVec<u32>>,

    // Tuple containing (required device, index)
    required_dev: Option<(ARef<Device>, u32)>,
    _data: PhantomData<T>,
}

impl<T: ConfigOps + Default> Config<T> {
    /// Creates a new instance of [`Config`].
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Initializes clock names.
    pub fn set_clk_names(mut self, names: KVec<CString>) -> Result<Self> {
        if self.clk_names.is_some() {
            return Err(EBUSY);
        }

        if names.is_empty() {
            return Err(EINVAL);
        }

        self.clk_names = Some(names);
        Ok(self)
    }

    /// Initializes property name.
    pub fn set_prop_name(mut self, name: CString) -> Result<Self> {
        if self.prop_name.is_some() {
            return Err(EBUSY);
        }

        self.prop_name = Some(name);
        Ok(self)
    }

    /// Initializes regulator names.
    pub fn set_regulator_names(mut self, names: KVec<CString>) -> Result<Self> {
        if self.regulator_names.is_some() {
            return Err(EBUSY);
        }

        if names.is_empty() {
            return Err(EINVAL);
        }

        self.regulator_names = Some(names);

        Ok(self)
    }

    /// Initializes required devices.
    pub fn set_required_dev(mut self, dev: ARef<Device>, index: u32) -> Result<Self> {
        if self.required_dev.is_some() {
            return Err(EBUSY);
        }

        self.required_dev = Some((dev, index));
        Ok(self)
    }

    /// Initializes supported hardware.
    pub fn set_supported_hw(mut self, hw: KVec<u32>) -> Result<Self> {
        if self.supported_hw.is_some() {
            return Err(EBUSY);
        }

        if hw.is_empty() {
            return Err(EINVAL);
        }

        self.supported_hw = Some(hw);
        Ok(self)
    }

    /// Sets the configuration with the OPP core.
    ///
    /// The returned [`ConfigToken`] will remove the configuration when dropped.
    pub fn set(self, dev: &Device) -> Result<ConfigToken> {
        let (_clk_list, clk_names) = match &self.clk_names {
            Some(x) => {
                let list = to_c_str_array(x)?;
                let ptr = list.as_ptr();
                (Some(list), ptr)
            }
            None => (None, ptr::null()),
        };

        let (_regulator_list, regulator_names) = match &self.regulator_names {
            Some(x) => {
                let list = to_c_str_array(x)?;
                let ptr = list.as_ptr();
                (Some(list), ptr)
            }
            None => (None, ptr::null()),
        };

        let prop_name = self
            .prop_name
            .as_ref()
            .map_or(ptr::null(), |p| p.as_char_ptr());

        let (supported_hw, supported_hw_count) = self
            .supported_hw
            .as_ref()
            .map_or((ptr::null(), 0), |hw| (hw.as_ptr(), hw.len() as u32));

        let (required_dev, required_dev_index) = self
            .required_dev
            .as_ref()
            .map_or((ptr::null_mut(), 0), |(dev, idx)| (dev.as_raw(), *idx));

        let mut config = bindings::dev_pm_opp_config {
            clk_names,
            config_clks: if T::HAS_CONFIG_CLKS {
                Some(Self::config_clks)
            } else {
                None
            },
            prop_name,
            regulator_names,
            config_regulators: if T::HAS_CONFIG_REGULATORS {
                Some(Self::config_regulators)
            } else {
                None
            },
            supported_hw,
            supported_hw_count,

            required_dev,
            required_dev_index,
        };

        // SAFETY: The requirements are satisfied by the existence of [`Device`] and its safety
        // requirements. The OPP core guarantees not to access fields of [`Config`] after this call
        // and so we don't need to save a copy of them for future use.
        let ret = unsafe { bindings::dev_pm_opp_set_config(dev.as_raw(), &mut config) };
        if ret < 0 {
            Err(Error::from_errno(ret))
        } else {
            Ok(ConfigToken(ret))
        }
    }

    /// Config's clk callback.
    ///
    /// SAFETY: Called from C. Inputs must be valid pointers.
    extern "C" fn config_clks(
        dev: *mut bindings::device,
        opp_table: *mut bindings::opp_table,
        opp: *mut bindings::dev_pm_opp,
        _data: *mut kernel::ffi::c_void,
        scaling_down: bool,
    ) -> kernel::ffi::c_int {
        from_result(|| {
            // SAFETY: 'dev' is guaranteed by the C code to be valid.
            let dev = unsafe { Device::get_device(dev) };
            T::config_clks(
                &dev,
                // SAFETY: 'opp_table' is guaranteed by the C code to be valid.
                &unsafe { Table::from_raw_table(opp_table, &dev) },
                // SAFETY: 'opp' is guaranteed by the C code to be valid.
                unsafe { OPP::from_raw_opp(opp)? },
                scaling_down,
            )
            .map(|()| 0)
        })
    }

    /// Config's regulator callback.
    ///
    /// SAFETY: Called from C. Inputs must be valid pointers.
    extern "C" fn config_regulators(
        dev: *mut bindings::device,
        old_opp: *mut bindings::dev_pm_opp,
        new_opp: *mut bindings::dev_pm_opp,
        regulators: *mut *mut bindings::regulator,
        count: kernel::ffi::c_uint,
    ) -> kernel::ffi::c_int {
        from_result(|| {
            // SAFETY: 'dev' is guaranteed by the C code to be valid.
            let dev = unsafe { Device::get_device(dev) };
            T::config_regulators(
                &dev,
                // SAFETY: 'old_opp' is guaranteed by the C code to be valid.
                unsafe { OPP::from_raw_opp(old_opp)? },
                // SAFETY: 'new_opp' is guaranteed by the C code to be valid.
                unsafe { OPP::from_raw_opp(new_opp)? },
                regulators,
                count,
            )
            .map(|()| 0)
        })
    }
}

/// A reference-counted OPP table.
///
/// Rust abstraction for the C `struct opp_table`.