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: Viresh Kumar <viresh.kumar@linaro.org>
This commit is contained in:
Viresh Kumar 2023-12-18 11:15:13 +05:30
parent d52c7e868f
commit ce32e2d47c

View File

@ -12,12 +12,29 @@ use crate::{
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`.