Commit 25961ae6 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branch 'pm-cpufreq'

Merge Rust support for cpufreq and OPP, a new Rust-based cpufreq-dt
driver, an SCMI cpufreq driver cleanup, and an ACPI cpufreq driver
regression fix:

 - Add Rust abstractions for CPUFreq framework (Viresh Kumar).

 - Add Rust abstractions for OPP framework (Viresh Kumar).

 - Add basic Rust abstractions for Clk and Cpumask frameworks (Viresh
   Kumar).

 - Clean up the SCMI cpufreq driver somewhat (Mike Tipton).

 - Use KHz as the nominal_freq units in get_max_boost_ratio() in the
   ACPI cpufreq driver (iGautham Shenoy).

* pm-cpufreq:
  acpi-cpufreq: Fix nominal_freq units to KHz in get_max_boost_ratio()
  rust: opp: Move `cfg(CONFIG_OF)` attribute to the top of doc test
  rust: opp: Make the doctest example depend on CONFIG_OF
  cpufreq: scmi: Skip SCMI devices that aren't used by the CPUs
  cpufreq: Add Rust-based cpufreq-dt driver
  rust: opp: Extend OPP abstractions with cpufreq support
  rust: cpufreq: Extend abstractions for driver registration
  rust: cpufreq: Extend abstractions for policy and driver ops
  rust: cpufreq: Add initial abstractions for cpufreq framework
  rust: opp: Add abstractions for the configuration options
  rust: opp: Add abstractions for the OPP table
  rust: opp: Add initial abstractions for OPP framework
  rust: cpu: Add from_cpu()
  rust: macros: enable use of hyphens in module names
  rust: clk: Add initial abstractions
  rust: clk: Add helpers for Rust code
  MAINTAINERS: Add entry for Rust cpumask API
  rust: cpumask: Add initial abstractions
  rust: cpumask: Add few more helpers
parents 9d230d50 cb6a85f3
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -5949,6 +5949,8 @@ F: include/dt-bindings/clock/
F:	include/linux/clk-pr*
F:	include/linux/clk/
F:	include/linux/of_clk.h
F:	rust/helpers/clk.c
F:	rust/kernel/clk.rs
X:	drivers/clk/clkdev.c
COMMON INTERNET FILE SYSTEM CLIENT (CIFS and SMB3)
@@ -6208,6 +6210,7 @@ F: drivers/cpufreq/
F:	include/linux/cpufreq.h
F:	include/linux/sched/cpufreq.h
F:	kernel/sched/cpufreq*.c
F:	rust/kernel/cpufreq.rs
F:	tools/testing/selftests/cpufreq/
CPU HOTPLUG
@@ -6221,6 +6224,7 @@ F: include/linux/cpuhotplug.h
F:	include/linux/smpboot.h
F:	kernel/cpu.c
F:	kernel/smpboot.*
F:	rust/kernel/cpu.rs
CPU IDLE TIME MANAGEMENT FRAMEWORK
M:	"Rafael J. Wysocki" <rafael@kernel.org>
@@ -6305,6 +6309,12 @@ L: linux-riscv@lists.infradead.org
S:	Maintained
F:	drivers/cpuidle/cpuidle-riscv-sbi.c
CPUMASK API [RUST]
M:	Viresh Kumar <viresh.kumar@linaro.org>
R:	Yury Norov <yury.norov@gmail.com>
S:	Maintained
F:	rust/kernel/cpumask.rs
CRAMFS FILESYSTEM
M:	Nicolas Pitre <nico@fluxnic.net>
S:	Maintained
@@ -18504,6 +18514,7 @@ F: Documentation/devicetree/bindings/opp/
F:	Documentation/power/opp.rst
F:	drivers/opp/
F:	include/linux/pm_opp.h
F:	rust/kernel/opp.rs
OPL4 DRIVER
M:	Clemens Ladisch <clemens@ladisch.de>
+12 −0
Original line number Diff line number Diff line
@@ -217,6 +217,18 @@ config CPUFREQ_DT

	  If in doubt, say N.

config CPUFREQ_DT_RUST
	tristate "Rust based Generic DT based cpufreq driver"
	depends on HAVE_CLK && OF && RUST
	select CPUFREQ_DT_PLATDEV
	select PM_OPP
	help
	  This adds a Rust based generic DT based cpufreq driver for frequency
	  management.  It supports both uniprocessor (UP) and symmetric
	  multiprocessor (SMP) systems.

	  If in doubt, say N.

config CPUFREQ_VIRT
	tristate "Virtual cpufreq driver"
	depends on GENERIC_ARCH_TOPOLOGY
+1 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o
obj-$(CONFIG_CPU_FREQ_GOV_ATTR_SET)	+= cpufreq_governor_attr_set.o

obj-$(CONFIG_CPUFREQ_DT)		+= cpufreq-dt.o
obj-$(CONFIG_CPUFREQ_DT_RUST)		+= rcpufreq_dt.o
obj-$(CONFIG_CPUFREQ_DT_PLATDEV)	+= cpufreq-dt-platdev.o
obj-$(CONFIG_CPUFREQ_VIRT)		+= virtual-cpufreq.o

+1 −1
Original line number Diff line number Diff line
@@ -660,7 +660,7 @@ static u64 get_max_boost_ratio(unsigned int cpu, u64 *nominal_freq)
	nominal_perf = perf_caps.nominal_perf;

	if (nominal_freq)
		*nominal_freq = perf_caps.nominal_freq;
		*nominal_freq = perf_caps.nominal_freq * 1000;

	if (!highest_perf || !nominal_perf) {
		pr_debug("CPU%d: highest or nominal performance missing\n", cpu);
+226 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

//! Rust based implementation of the cpufreq-dt driver.

use kernel::{
    c_str,
    clk::Clk,
    cpu, cpufreq,
    cpumask::CpumaskVar,
    device::{Core, Device},
    error::code::*,
    fmt,
    macros::vtable,
    module_platform_driver, of, opp, platform,
    prelude::*,
    str::CString,
    sync::Arc,
};

/// Finds exact supply name from the OF node.
fn find_supply_name_exact(dev: &Device, name: &str) -> Option<CString> {
    let prop_name = CString::try_from_fmt(fmt!("{}-supply", name)).ok()?;
    dev.property_present(&prop_name)
        .then(|| CString::try_from_fmt(fmt!("{name}")).ok())
        .flatten()
}

/// Finds supply name for the CPU from DT.
fn find_supply_names(dev: &Device, cpu: u32) -> Option<KVec<CString>> {
    // Try "cpu0" for older DTs, fallback to "cpu".
    let name = (cpu == 0)
        .then(|| find_supply_name_exact(dev, "cpu0"))
        .flatten()
        .or_else(|| find_supply_name_exact(dev, "cpu"))?;

    let mut list = KVec::with_capacity(1, GFP_KERNEL).ok()?;
    list.push(name, GFP_KERNEL).ok()?;

    Some(list)
}

/// Represents the cpufreq dt device.
struct CPUFreqDTDevice {
    opp_table: opp::Table,
    freq_table: opp::FreqTable,
    _mask: CpumaskVar,
    _token: Option<opp::ConfigToken>,
    _clk: Clk,
}

#[derive(Default)]
struct CPUFreqDTDriver;

#[vtable]
impl opp::ConfigOps for CPUFreqDTDriver {}

#[vtable]
impl cpufreq::Driver for CPUFreqDTDriver {
    const NAME: &'static CStr = c_str!("cpufreq-dt");
    const FLAGS: u16 = cpufreq::flags::NEED_INITIAL_FREQ_CHECK | cpufreq::flags::IS_COOLING_DEV;
    const BOOST_ENABLED: bool = true;

    type PData = Arc<CPUFreqDTDevice>;

    fn init(policy: &mut cpufreq::Policy) -> Result<Self::PData> {
        let cpu = policy.cpu();
        // SAFETY: The CPU device is only used during init; it won't get hot-unplugged. The cpufreq
        // core  registers with CPU notifiers and the cpufreq core/driver won't use the CPU device,
        // once the CPU is hot-unplugged.
        let dev = unsafe { cpu::from_cpu(cpu)? };
        let mut mask = CpumaskVar::new_zero(GFP_KERNEL)?;

        mask.set(cpu);

        let token = find_supply_names(dev, cpu)
            .map(|names| {
                opp::Config::<Self>::new()
                    .set_regulator_names(names)?
                    .set(dev)
            })
            .transpose()?;

        // Get OPP-sharing information from "operating-points-v2" bindings.
        let fallback = match opp::Table::of_sharing_cpus(dev, &mut mask) {
            Ok(()) => false,
            Err(e) if e == ENOENT => {
                // "operating-points-v2" not supported. If the platform hasn't
                // set sharing CPUs, fallback to all CPUs share the `Policy`
                // for backward compatibility.
                opp::Table::sharing_cpus(dev, &mut mask).is_err()
            }
            Err(e) => return Err(e),
        };

        // Initialize OPP tables for all policy cpus.
        //
        // For platforms not using "operating-points-v2" bindings, we do this
        // before updating policy cpus. Otherwise, we will end up creating
        // duplicate OPPs for the CPUs.
        //
        // OPPs might be populated at runtime, don't fail for error here unless
        // it is -EPROBE_DEFER.
        let mut opp_table = match opp::Table::from_of_cpumask(dev, &mut mask) {
            Ok(table) => table,
            Err(e) => {
                if e == EPROBE_DEFER {
                    return Err(e);
                }

                // The table is added dynamically ?
                opp::Table::from_dev(dev)?
            }
        };

        // The OPP table must be initialized, statically or dynamically, by this point.
        opp_table.opp_count()?;

        // Set sharing cpus for fallback scenario.
        if fallback {
            mask.setall();
            opp_table.set_sharing_cpus(&mut mask)?;
        }

        let mut transition_latency = opp_table.max_transition_latency_ns() as u32;
        if transition_latency == 0 {
            transition_latency = cpufreq::ETERNAL_LATENCY_NS;
        }

        policy
            .set_dvfs_possible_from_any_cpu(true)
            .set_suspend_freq(opp_table.suspend_freq())
            .set_transition_latency_ns(transition_latency);

        let freq_table = opp_table.cpufreq_table()?;
        // SAFETY: The `freq_table` is not dropped while it is getting used by the C code.
        unsafe { policy.set_freq_table(&freq_table) };

        // SAFETY: The returned `clk` is not dropped while it is getting used by the C code.
        let clk = unsafe { policy.set_clk(dev, None)? };

        mask.copy(policy.cpus());

        Ok(Arc::new(
            CPUFreqDTDevice {
                opp_table,
                freq_table,
                _mask: mask,
                _token: token,
                _clk: clk,
            },
            GFP_KERNEL,
        )?)
    }

    fn exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result {
        Ok(())
    }

    fn online(_policy: &mut cpufreq::Policy) -> Result {
        // We did light-weight tear down earlier, nothing to do here.
        Ok(())
    }

    fn offline(_policy: &mut cpufreq::Policy) -> Result {
        // Preserve policy->data and don't free resources on light-weight
        // tear down.
        Ok(())
    }

    fn suspend(policy: &mut cpufreq::Policy) -> Result {
        policy.generic_suspend()
    }

    fn verify(data: &mut cpufreq::PolicyData) -> Result {
        data.generic_verify()
    }

    fn target_index(policy: &mut cpufreq::Policy, index: cpufreq::TableIndex) -> Result {
        let Some(data) = policy.data::<Self::PData>() else {
            return Err(ENOENT);
        };

        let freq = data.freq_table.freq(index)?;
        data.opp_table.set_rate(freq)
    }

    fn get(policy: &mut cpufreq::Policy) -> Result<u32> {
        policy.generic_get()
    }

    fn set_boost(_policy: &mut cpufreq::Policy, _state: i32) -> Result {
        Ok(())
    }

    fn register_em(policy: &mut cpufreq::Policy) {
        policy.register_em_opp()
    }
}

kernel::of_device_table!(
    OF_TABLE,
    MODULE_OF_TABLE,
    <CPUFreqDTDriver as platform::Driver>::IdInfo,
    [(of::DeviceId::new(c_str!("operating-points-v2")), ())]
);

impl platform::Driver for CPUFreqDTDriver {
    type IdInfo = ();
    const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);

    fn probe(
        pdev: &platform::Device<Core>,
        _id_info: Option<&Self::IdInfo>,
    ) -> Result<Pin<KBox<Self>>> {
        cpufreq::Registration::<CPUFreqDTDriver>::new_foreign_owned(pdev.as_ref())?;
        Ok(KBox::new(Self {}, GFP_KERNEL)?.into())
    }
}

module_platform_driver! {
    type: CPUFreqDTDriver,
    name: "cpufreq-dt",
    author: "Viresh Kumar <viresh.kumar@linaro.org>",
    description: "Generic CPUFreq DT driver",
    license: "GPL v2",
}
Loading