diff mbox series

[RFC,6/6] rust: cpufreq: Add rust implementation of cppc_cpufreq driver

Message ID 20240815082916.1210110-7-pierre.gondois@arm.com (mailing list archive)
State New
Headers show
Series rust: cpufreq: Add cppc_cpufreq driver implementation | expand

Commit Message

Pierre Gondois Aug. 15, 2024, 8:29 a.m. UTC
In an effort to add test/support the cpufreq framework in rust,
add a rust implementation of the cppc_cpufreq driver named:
`rcppc_cpufreq`.

This implementation doesn't support/implement:
- vendor specific workarounds
- Frequency Invariance Engine (FIE)
- artificial Energy Model (EM)
- (struct cpufreq_driver).attr field
- QoS requests

Basic support is provided to get/set the frequency on a platform
implementing the CPPC section of the ACPI spec.

Signed-off-by: Pierre Gondois <pierre.gondois@arm.com>
---
 drivers/cpufreq/Kconfig          |  16 ++
 drivers/cpufreq/Makefile         |   1 +
 drivers/cpufreq/rcppc_cpufreq.rs | 333 +++++++++++++++++++++++++++++++
 3 files changed, 350 insertions(+)
 create mode 100644 drivers/cpufreq/rcppc_cpufreq.rs

Comments

Greg KH Aug. 15, 2024, 9:24 a.m. UTC | #1
On Thu, Aug 15, 2024 at 10:29:10AM +0200, Pierre Gondois wrote:
> In an effort to add test/support the cpufreq framework in rust,
> add a rust implementation of the cppc_cpufreq driver named:
> `rcppc_cpufreq`.
> 
> This implementation doesn't support/implement:
> - vendor specific workarounds
> - Frequency Invariance Engine (FIE)
> - artificial Energy Model (EM)
> - (struct cpufreq_driver).attr field
> - QoS requests
> 
> Basic support is provided to get/set the frequency on a platform
> implementing the CPPC section of the ACPI spec.
> 
> Signed-off-by: Pierre Gondois <pierre.gondois@arm.com>
> ---
>  drivers/cpufreq/Kconfig          |  16 ++
>  drivers/cpufreq/Makefile         |   1 +
>  drivers/cpufreq/rcppc_cpufreq.rs | 333 +++++++++++++++++++++++++++++++
>  3 files changed, 350 insertions(+)
>  create mode 100644 drivers/cpufreq/rcppc_cpufreq.rs

I'm missing why you want to re-implement an existing driver here.  Why
are you going to have 2 drivers for the same functionality/hardware?

How is the system going to handle switching between the two drivers?

thanks,

greg k-h
Miguel Ojeda Aug. 15, 2024, 1:04 p.m. UTC | #2
On Thu, Aug 15, 2024 at 10:31 AM Pierre Gondois <pierre.gondois@arm.com> wrote:
>
> In an effort to add test/support the cpufreq framework in rust,
> add a rust implementation of the cppc_cpufreq driver named:
> `rcppc_cpufreq`.

Similar to what Greg said -- is this intended to be something like a
"Rust reference driver" [1] for the subsystem?

[1] https://rust-for-linux.com/rust-reference-drivers

> +       depends on ACPI_PROCESSOR
> +       depends on ARM || ARM64 || RISCV

`depends on RUST`?

Also, I imagine you skipped all safety comments etc. since it is an
RFC, but I thought I would mention it nevertheless.

Thanks for experimenting with Rust!

Cheers,
Miguel
Pierre Gondois Aug. 16, 2024, 6:59 a.m. UTC | #3
Hello Greg, Miguel,

On 8/15/24 15:04, Miguel Ojeda wrote:
> On Thu, Aug 15, 2024 at 10:31 AM Pierre Gondois <pierre.gondois@arm.com> wrote:
>>
>> In an effort to add test/support the cpufreq framework in rust,
>> add a rust implementation of the cppc_cpufreq driver named:
>> `rcppc_cpufreq`.
> 
> Similar to what Greg said -- is this intended to be something like a
> "Rust reference driver" [1] for the subsystem?

The initial intent was to review/test Viresh's patchset [1]. I then
thought it would be a good idea to implement another cpufreq driver
to see if the provided interface would work.
As the cpufreq-dt driver is re-implemented in Viresh's patchset,
I thought it was also ok to have this driver.

> 
> [1] https://rust-for-linux.com/rust-reference-drivers
> 
>> +       depends on ACPI_PROCESSOR
>> +       depends on ARM || ARM64 || RISCV
> 

Yes right

> `depends on RUST`?
> 
> Also, I imagine you skipped all safety comments etc. since it is an
> RFC, but I thought I would mention it nevertheless.

Ok, if it is decided not to drop this patchset, I ll add some comments,

> 
> Thanks for experimenting with Rust!
> 
> Cheers,
> Miguel


Regards,
Pierre
Greg Kroah-Hartman Aug. 16, 2024, 7:06 a.m. UTC | #4
On Fri, Aug 16, 2024 at 08:59:22AM +0200, Pierre Gondois wrote:
> Hello Greg, Miguel,
> 
> On 8/15/24 15:04, Miguel Ojeda wrote:
> > On Thu, Aug 15, 2024 at 10:31 AM Pierre Gondois <pierre.gondois@arm.com> wrote:
> > > 
> > > In an effort to add test/support the cpufreq framework in rust,
> > > add a rust implementation of the cppc_cpufreq driver named:
> > > `rcppc_cpufreq`.
> > 
> > Similar to what Greg said -- is this intended to be something like a
> > "Rust reference driver" [1] for the subsystem?
> 
> The initial intent was to review/test Viresh's patchset [1]. I then
> thought it would be a good idea to implement another cpufreq driver
> to see if the provided interface would work.
> As the cpufreq-dt driver is re-implemented in Viresh's patchset,
> I thought it was also ok to have this driver.

Duplicate drivers for the same hardware are never a good idea, we need
to learn from our past mistakes when we have done this before (hint, it
did not work out and we ended up dropping the duplicates.)

However, if the subsystem maintainer agrees, they are free to have
duplicate drivers, as long as the maintainer of the "new" one will be
there to help out with all of the confusion and problems that users and
distros will have :)

good luck!

greg k-h
diff mbox series

Patch

diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig
index eb9359bd3c5c..57130d0789b0 100644
--- a/drivers/cpufreq/Kconfig
+++ b/drivers/cpufreq/Kconfig
@@ -343,4 +343,20 @@  config ACPI_CPPC_CPUFREQ_FIE
 
 	  If in doubt, say N.
 
+config ACPI_CPPC_CPUFREQ_RUST
+	tristate "Rust based CPUFreq driver based on the ACPI CPPC spec"
+	depends on ACPI_PROCESSOR
+	depends on ARM || ARM64 || RISCV
+	select ACPI_CPPC_LIB
+	help
+	  This adds a Rust based CPUFreq driver based on the ACPI CPPC spec.
+	  Basic support is only available for now, i.e. the following are
+	  not supported:
+	  - vendor specific workarounds
+	  - Frequency Invariance Engine (FIE)
+	  - artificial Energy Model (EM)
+	  - QoS requests
+
+	  If in doubt, say N.
+
 endmenu
diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile
index 4981d908b803..5e17db481a50 100644
--- a/drivers/cpufreq/Makefile
+++ b/drivers/cpufreq/Makefile
@@ -16,6 +16,7 @@  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_ACPI_CPPC_CPUFREQ_RUST)	+= rcppc_cpufreq.o
 obj-$(CONFIG_CPUFREQ_DT_PLATDEV)	+= cpufreq-dt-platdev.o
 
 # Traces
diff --git a/drivers/cpufreq/rcppc_cpufreq.rs b/drivers/cpufreq/rcppc_cpufreq.rs
new file mode 100644
index 000000000000..198857a5b966
--- /dev/null
+++ b/drivers/cpufreq/rcppc_cpufreq.rs
@@ -0,0 +1,333 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust based implementation of the cppc_cpufreq driver.
+
+use core::format_args;
+
+use kernel::prelude::*;
+
+use kernel::{
+    bindings, c_str, cpufreq,
+    cpumask::Cpumask,
+    error::{to_result, Result},
+    sync::Arc,
+};
+
+/// 2 usec delay between sampling
+const COUNTERS_SAMPLING_DELAY_US: u64 = 2;
+
+// Whether boost is supported
+static mut BOOST_SUPPORTED: bool = false;
+
+struct CPUFreqCppcDriver {
+    _drv: cpufreq::Registration<Self>,
+}
+
+struct CPUFreqCppcData {
+    perf_caps: bindings::cppc_perf_caps,
+    perf_ctrls: bindings::cppc_perf_ctrls,
+    shared_type: u32,
+    shared_cpu_map: Cpumask,
+}
+
+impl CPUFreqCppcData {
+    fn new(cpu: u32) -> Result<Self> {
+        let mut shared_type = bindings::CPUFREQ_SHARED_TYPE_NONE;
+        let mut perf_caps = bindings::cppc_perf_caps::default();
+        let mut shared_cpu_map = Cpumask::new()?;
+
+        match acpi_get_psd_map(cpu, &mut shared_cpu_map, &mut shared_type) {
+            Err(e) => {
+                pr_debug!("Err parsing CPU{cpu} PSD data: err:{e:?}\n");
+                return Err(e);
+            }
+            _ => {}
+        }
+
+        match cppc_get_perf_caps(cpu as i32, &mut perf_caps) {
+            Err(e) => {
+                pr_debug!("Err reading CPU{cpu} perf caps: e:{e:?}\n");
+                return Err(e);
+            }
+            _ => {}
+        }
+
+        Ok(Self {
+            perf_caps: perf_caps,
+            perf_ctrls: bindings::cppc_perf_ctrls::default(),
+            shared_type: shared_type,
+            shared_cpu_map: shared_cpu_map,
+        })
+    }
+}
+
+fn acpi_get_psd_map(cpu: u32, shared_cpu_map: &mut Cpumask, shared_type: &mut u32) -> Result<()> {
+    unsafe {
+        to_result(bindings::acpi_get_psd_map(
+            cpu,
+            shared_cpu_map.as_mut_ptr(),
+            shared_type,
+        ))
+    }
+}
+
+fn cppc_get_perf_caps(cpu: i32, perf_caps: &mut bindings::cppc_perf_caps) -> Result<()> {
+    unsafe { to_result(bindings::cppc_get_perf_caps(cpu, perf_caps)) }
+}
+
+fn cppc_perf_to_khz(caps: &mut bindings::cppc_perf_caps, perf: u32) -> u32 {
+    unsafe { bindings::cppc_perf_to_khz(caps, perf) }
+}
+
+fn cppc_khz_to_perf(caps: &mut bindings::cppc_perf_caps, freq: u32) -> u32 {
+    unsafe { bindings::cppc_khz_to_perf(caps, freq) }
+}
+
+fn cppc_set_perf(cpu: u32, perf_ctrls: &mut bindings::cppc_perf_ctrls) -> Result<()> {
+    unsafe { to_result(bindings::cppc_set_perf(cpu as i32, perf_ctrls)) }
+}
+
+fn cpufreq_freq_transition_begin(
+    policy: &mut cpufreq::Policy,
+    freqs: &mut bindings::cpufreq_freqs,
+) {
+    unsafe { bindings::cpufreq_freq_transition_begin(policy.as_raw(), freqs) }
+}
+
+fn cpufreq_freq_transition_end(
+    policy: &mut cpufreq::Policy,
+    freqs: &mut bindings::cpufreq_freqs,
+    transition_failed: bool,
+) {
+    unsafe {
+        bindings::cpufreq_freq_transition_end(policy.as_raw(), freqs, transition_failed as i32)
+    }
+}
+
+fn cppc_get_perf_ctrs(cpu: u32, fb_ctrs: &mut bindings::cppc_perf_fb_ctrs) -> Result<()> {
+    unsafe { to_result(bindings::cppc_get_perf_ctrs(cpu as i32, fb_ctrs)) }
+}
+
+fn cppc_perf_from_fbctrs(
+    desired_perf: u32,
+    fb_ctrs_t0: bindings::cppc_perf_fb_ctrs,
+    fb_ctrs_t1: bindings::cppc_perf_fb_ctrs,
+) -> u32 {
+    let reference_perf = fb_ctrs_t0.reference_perf;
+    let delta_reference = fb_ctrs_t1.reference.wrapping_sub(fb_ctrs_t0.reference);
+    let delta_delivered = fb_ctrs_t1.delivered.wrapping_sub(fb_ctrs_t0.delivered);
+
+    if delta_reference == 0 || delta_delivered == 0 {
+        return desired_perf;
+    }
+
+    return ((reference_perf - delta_delivered) / delta_reference) as u32;
+}
+
+#[vtable]
+impl cpufreq::Driver for CPUFreqCppcDriver {
+    type Data = ();
+    type PData = Arc<CPUFreqCppcData>;
+
+    fn init(policy: &mut cpufreq::Policy) -> Result<Self::PData> {
+        let cpu = policy.cpu();
+        let mut data = CPUFreqCppcData::new(cpu)?;
+
+        // Set min to lowest nonlinear perf to avoid any efficiency penalty
+        // (see Section 8.4.7.1.1.5 of ACPI 6.1 spec)
+        policy
+            .set_min(data.perf_caps.lowest_nonlinear_perf)
+            .set_max(data.perf_caps.nominal_perf);
+
+        let lowest_nonlinear_perf = data.perf_caps.lowest_nonlinear_perf;
+        let nominal_perf = data.perf_caps.nominal_perf;
+        let highest_perf = data.perf_caps.highest_perf;
+
+        // Set cpuinfo.min_freq to Lowest to make the full range of performance
+        // available if userspace wants to use any perf between lowest & lowest
+        // nonlinear perf
+        policy
+            .set_cpuinfo_min_freq(cppc_perf_to_khz(&mut data.perf_caps, lowest_nonlinear_perf))
+            .set_cpuinfo_max_freq(cppc_perf_to_khz(&mut data.perf_caps, nominal_perf));
+
+        policy
+            .set_transition_delay_us(unsafe { bindings::cppc_get_transition_latency(cpu as i32) })
+            .set_shared_type(data.shared_type);
+
+        match data.shared_type {
+            bindings::CPUFREQ_SHARED_TYPE_HW => {}
+            bindings::CPUFREQ_SHARED_TYPE_NONE => {}
+            bindings::CPUFREQ_SHARED_TYPE_ANY => {
+                // All CPUs in the domain will share a policy and all cpufreq
+                // operations will use the same CPUFreqCppcData struct.
+                let cpus = policy.cpus();
+                data.shared_cpu_map.copy(cpus);
+            }
+            default => {
+                pr_err!("Unsupported CPU co-ord type: {default}\n");
+                return Err(EFAULT);
+            }
+        }
+
+        if unsafe { bindings::cppc_allow_fast_switch() } {
+            policy.set_fast_switch_possible(true);
+        }
+
+        // If 'highest_perf' is greater than 'nominal_perf', we assume CPU Boost
+        // is supported.
+        if highest_perf > nominal_perf {
+            unsafe { BOOST_SUPPORTED = true };
+        }
+
+        // Set policy->cur to max now. The governors will adjust later.
+        policy
+            .set_dvfs_possible_from_any_cpu()
+            .set_cur(cppc_perf_to_khz(&mut data.perf_caps, highest_perf));
+
+        cppc_set_perf(cpu, &mut data.perf_ctrls)?;
+
+        Ok(Arc::new(data, GFP_KERNEL)?)
+    }
+
+    fn exit(_policy: &mut cpufreq::Policy, _data: Option<Self::PData>) -> Result<()> {
+        Ok(())
+    }
+
+    fn verify(policy_data: &mut cpufreq::PolicyData) -> Result<()> {
+        unsafe { bindings::cpufreq_verify_within_cpu_limits(policy_data.as_raw()) };
+        Ok(())
+    }
+
+    fn target(
+        policy: &mut cpufreq::Policy,
+        target_freq: u32,
+        _relation: cpufreq::Relation,
+    ) -> Result<()> {
+        let data = match policy.data::<Self::PData>() {
+            Some(data) => data,
+            None => return Err(ENOENT),
+        };
+
+        let mut perf_ctrls: bindings::cppc_perf_ctrls = data.perf_ctrls;
+        let mut perf_caps: bindings::cppc_perf_caps = data.perf_caps;
+        let desired_perf: u32 = cppc_khz_to_perf(&mut perf_caps, target_freq);
+        let cpu = policy.cpu();
+
+        // Return if it is exactly the same perf.
+        if desired_perf == perf_ctrls.desired_perf {
+            return Ok(());
+        }
+
+        perf_ctrls.desired_perf = desired_perf;
+
+        let mut freqs = bindings::cpufreq_freqs::default();
+        freqs.old = policy.cur();
+        freqs.new = target_freq;
+
+        cpufreq_freq_transition_begin(policy, &mut freqs);
+        let ret = cppc_set_perf(cpu, &mut perf_ctrls);
+        let transition_failed = match ret {
+            Ok(_) => false,
+            Err(e) => {
+                pr_debug!("Failed to set target on CPU:{cpu}. err:{e:?}\n");
+                true
+            }
+        };
+        cpufreq_freq_transition_end(policy, &mut freqs, transition_failed);
+
+        ret
+    }
+
+    fn fast_switch(policy: &mut cpufreq::Policy, target_freq: u32) -> u32 {
+        let data = match policy.data::<Self::PData>() {
+            Some(data) => data,
+            None => return 0,
+        };
+
+        let mut perf_caps = data.perf_caps;
+        let mut perf_ctrls = data.perf_ctrls;
+        let desired_perf = cppc_khz_to_perf(&mut perf_caps, target_freq);
+        let cpu = policy.cpu();
+
+        perf_ctrls.desired_perf = desired_perf;
+
+        if let Err(ret) = cppc_set_perf(cpu, &mut perf_ctrls) {
+            pr_debug!("Failed to set target on CPU:{cpu}. ret:{ret:?}\n");
+            return 0;
+        }
+
+        return target_freq;
+    }
+
+    fn get(policy: &mut cpufreq::Policy) -> Result<u32> {
+        let mut fb_ctrs_t0 = bindings::cppc_perf_fb_ctrs::default();
+        let mut fb_ctrs_t1 = bindings::cppc_perf_fb_ctrs::default();
+        let cpu = policy.cpu();
+
+        let data = match policy.data::<Self::PData>() {
+            Some(data) => data,
+            None => return Err(ENOENT),
+        };
+
+        let mut perf_caps = data.perf_caps;
+        let desired_perf = data.perf_ctrls.desired_perf;
+
+        cppc_get_perf_ctrs(cpu, &mut fb_ctrs_t0)?;
+        unsafe { bindings::__udelay(COUNTERS_SAMPLING_DELAY_US) };
+        cppc_get_perf_ctrs(cpu, &mut fb_ctrs_t1)?;
+
+        let delivered_perf = cppc_perf_from_fbctrs(desired_perf, fb_ctrs_t0, fb_ctrs_t1);
+        let freq = cppc_perf_to_khz(&mut perf_caps, delivered_perf);
+
+        Ok(freq)
+    }
+
+    fn set_boost(policy: &mut cpufreq::Policy, state: i32) -> Result<()> {
+        if unsafe { !BOOST_SUPPORTED } {
+            pr_err!("BOOST not supported by CPU or firmware\n");
+            return Err(EINVAL);
+        }
+
+        let data = match policy.data::<Self::PData>() {
+            Some(data) => data,
+            None => return Err(ENOENT),
+        };
+        let mut caps = data.perf_caps;
+        let highest_perf = caps.highest_perf;
+        let nominal_perf = caps.nominal_perf;
+
+        let max_freq = if state != 0 {
+            cppc_perf_to_khz(&mut caps, highest_perf)
+        } else {
+            cppc_perf_to_khz(&mut caps, nominal_perf)
+        };
+
+        policy
+            .set_max(max_freq)
+            .set_cpuinfo_max_freq(max_freq);
+
+        Ok(())
+    }
+}
+
+module! {
+    type: CPUFreqCppcDriver,
+    name: "cppc_cpufreq",
+    author: "Pierre Gondois",
+    description: "CPPC cpufreq driver",
+    license: "GPL v2",
+    initcall: ".initcall7.init",
+}
+
+impl kernel::Module for CPUFreqCppcDriver {
+    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
+        let drv = cpufreq::Registration::<CPUFreqCppcDriver>::register(
+            c_str!("rcppc-cpufreq"),
+            (),
+            cpufreq::flags::CONST_LOOPS,
+            false,
+        )?;
+
+        Ok(CPUFreqCppcDriver { _drv: drv })
+    }
+}