More power management updates for 6.16-rc1

- Revert an x86 commit that went into 6.15 and caused idle power,
    including power in suspend-to-idle, to rise rather dramatically
    on systems booting with "nosmt" in the kernel command line (Rafael
    Wysocki).
 
  - Prevent freeing an uninitialized pointer in error path of
    dt_idle_state_present() in the PSCI cpuidle driver (Dan Carpenter).
 
  - Use KHz as the nominal_freq units in get_max_boost_ratio() in the
    ACPI cpufreq driver (iGautham Shenoy).
 
  - 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).
 -----BEGIN PGP SIGNATURE-----
 
 iQFGBAABCAAwFiEEcM8Aw/RY0dgsiRUR7l+9nS/U47UFAmg5+xcSHHJqd0Byand5
 c29ja2kubmV0AAoJEO5fvZ0v1OO16TEH/AgeHkVPfEDWDsaPDSHmYPyITQKu6NOD
 udFxbh93TTcgEOK3iradZLdZvXDpqiHqwMxoiCadETuJMvMHdUpJJ/sPG5mju9AY
 lRd7ajhHNQOV4I+BIwP7CUVK3CSpnBBlBDHk/SuEbviSL+oJifeZdRvk0GTzfkz1
 fbr51qAS2TfAcxI1Y+KnFbrUW6R0lC38kf7ZlMbSt5ZcWFWlLxuzrxaqeriObs7Z
 jNQCypbOi/btbVkPfC+0m+qc6PVmxV22naBHWV/rqI3y5Xg6UPMTlquxO1C/K51J
 p3k37pvWSwVXF4AbgsRz074QXsrugfvgbsJArq7zk180XwTj5aiY4sY=
 =ADMT
 -----END PGP SIGNATURE-----

Merge tag 'pm-6.16-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm

Pull more power management updates from Rafael Wysocki:
 "These revert an x86 commit that introduced a nasty power regression on
  some systems, fix PSCI cpuidle driver and ACPI cpufreq driver
  regressions, add Rust abstractions for cpufreq, OPP, clk, and
  cpumasks, add a Rust-based cpufreq-dt driver, and do a minor SCMI
  cpufreq driver cleanup:

   - Revert an x86 commit that went into 6.15 and caused idle power,
     including power in suspend-to-idle, to rise rather dramatically on
     systems booting with "nosmt" in the kernel command line (Rafael
     Wysocki)

   - Prevent freeing an uninitialized pointer in error path of
     dt_idle_state_present() in the PSCI cpuidle driver (Dan Carpenter)

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

   - 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)"

* tag 'pm-6.16-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: (21 commits)
  Revert "x86/smp: Eliminate mwait_play_dead_cpuid_hint()"
  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
  cpuidle: psci: Fix uninitialized variable in dt_idle_state_present()
  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
  ...
This commit is contained in:
Linus Torvalds 2025-05-30 11:54:29 -07:00
commit 976aa630da
20 changed files with 3624 additions and 22 deletions

View File

@ -5952,6 +5952,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)
@ -6211,6 +6213,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
@ -6224,6 +6227,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>
@ -6308,6 +6312,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
@ -18514,6 +18524,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>

View File

@ -1244,10 +1244,6 @@ void play_dead_common(void)
local_irq_disable();
}
/*
* We need to flush the caches before going to sleep, lest we have
* dirty data in our caches when we come back up.
*/
void __noreturn mwait_play_dead(unsigned int eax_hint)
{
struct mwait_cpu_dead *md = this_cpu_ptr(&mwait_cpu_dead);
@ -1293,6 +1289,50 @@ void __noreturn mwait_play_dead(unsigned int eax_hint)
}
}
/*
* We need to flush the caches before going to sleep, lest we have
* dirty data in our caches when we come back up.
*/
static inline void mwait_play_dead_cpuid_hint(void)
{
unsigned int eax, ebx, ecx, edx;
unsigned int highest_cstate = 0;
unsigned int highest_subcstate = 0;
int i;
if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD ||
boot_cpu_data.x86_vendor == X86_VENDOR_HYGON)
return;
if (!this_cpu_has(X86_FEATURE_MWAIT))
return;
if (!this_cpu_has(X86_FEATURE_CLFLUSH))
return;
eax = CPUID_LEAF_MWAIT;
ecx = 0;
native_cpuid(&eax, &ebx, &ecx, &edx);
/*
* eax will be 0 if EDX enumeration is not valid.
* Initialized below to cstate, sub_cstate value when EDX is valid.
*/
if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED)) {
eax = 0;
} else {
edx >>= MWAIT_SUBSTATE_SIZE;
for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
if (edx & MWAIT_SUBSTATE_MASK) {
highest_cstate = i;
highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
}
}
eax = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
(highest_subcstate - 1);
}
mwait_play_dead(eax);
}
/*
* Kick all "offline" CPUs out of mwait on kexec(). See comment in
* mwait_play_dead().
@ -1343,9 +1383,9 @@ void native_play_dead(void)
play_dead_common();
tboot_shutdown(TB_SHUTDOWN_WFS);
/* Below returns only on error. */
cpuidle_play_dead();
hlt_play_dead();
mwait_play_dead_cpuid_hint();
if (cpuidle_play_dead())
hlt_play_dead();
}
#else /* ... !CONFIG_HOTPLUG_CPU */

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1,226 @@
// 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",
}

View File

@ -393,6 +393,40 @@ static struct cpufreq_driver scmi_cpufreq_driver = {
.set_boost = cpufreq_boost_set_sw,
};
static bool scmi_dev_used_by_cpus(struct device *scmi_dev)
{
struct device_node *scmi_np = dev_of_node(scmi_dev);
struct device_node *cpu_np, *np;
struct device *cpu_dev;
int cpu, idx;
if (!scmi_np)
return false;
for_each_possible_cpu(cpu) {
cpu_dev = get_cpu_device(cpu);
if (!cpu_dev)
continue;
cpu_np = dev_of_node(cpu_dev);
np = of_parse_phandle(cpu_np, "clocks", 0);
of_node_put(np);
if (np == scmi_np)
return true;
idx = of_property_match_string(cpu_np, "power-domain-names", "perf");
np = of_parse_phandle(cpu_np, "power-domains", idx);
of_node_put(np);
if (np == scmi_np)
return true;
}
return false;
}
static int scmi_cpufreq_probe(struct scmi_device *sdev)
{
int ret;
@ -401,7 +435,7 @@ static int scmi_cpufreq_probe(struct scmi_device *sdev)
handle = sdev->handle;
if (!handle)
if (!handle || !scmi_dev_used_by_cpus(dev))
return -ENODEV;
scmi_cpufreq_driver.driver_data = sdev;

View File

@ -456,14 +456,13 @@ static struct faux_device_ops psci_cpuidle_ops = {
static bool __init dt_idle_state_present(void)
{
struct device_node *cpu_node __free(device_node);
struct device_node *state_node __free(device_node);
cpu_node = of_cpu_device_node_get(cpumask_first(cpu_possible_mask));
struct device_node *cpu_node __free(device_node) =
of_cpu_device_node_get(cpumask_first(cpu_possible_mask));
if (!cpu_node)
return false;
state_node = of_get_cpu_state_node(cpu_node, 0);
struct device_node *state_node __free(device_node) =
of_get_cpu_state_node(cpu_node, 0);
if (!state_node)
return false;

View File

@ -16,7 +16,10 @@
#include <linux/blk-mq.h>
#include <linux/blk_types.h>
#include <linux/blkdev.h>
#include <linux/clk.h>
#include <linux/configfs.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/cpumask.h>
#include <linux/cred.h>
#include <linux/device/faux.h>
@ -35,6 +38,7 @@
#include <linux/phy.h>
#include <linux/pid_namespace.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/poll.h>
#include <linux/property.h>
#include <linux/refcount.h>

66
rust/helpers/clk.c Normal file
View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/clk.h>
/*
* The "inline" implementation of below helpers are only available when
* CONFIG_HAVE_CLK or CONFIG_HAVE_CLK_PREPARE aren't set.
*/
#ifndef CONFIG_HAVE_CLK
struct clk *rust_helper_clk_get(struct device *dev, const char *id)
{
return clk_get(dev, id);
}
void rust_helper_clk_put(struct clk *clk)
{
clk_put(clk);
}
int rust_helper_clk_enable(struct clk *clk)
{
return clk_enable(clk);
}
void rust_helper_clk_disable(struct clk *clk)
{
clk_disable(clk);
}
unsigned long rust_helper_clk_get_rate(struct clk *clk)
{
return clk_get_rate(clk);
}
int rust_helper_clk_set_rate(struct clk *clk, unsigned long rate)
{
return clk_set_rate(clk, rate);
}
#endif
#ifndef CONFIG_HAVE_CLK_PREPARE
int rust_helper_clk_prepare(struct clk *clk)
{
return clk_prepare(clk);
}
void rust_helper_clk_unprepare(struct clk *clk)
{
clk_unprepare(clk);
}
#endif
struct clk *rust_helper_clk_get_optional(struct device *dev, const char *id)
{
return clk_get_optional(dev, id);
}
int rust_helper_clk_prepare_enable(struct clk *clk)
{
return clk_prepare_enable(clk);
}
void rust_helper_clk_disable_unprepare(struct clk *clk)
{
clk_disable_unprepare(clk);
}

10
rust/helpers/cpufreq.c Normal file
View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/cpufreq.h>
#ifdef CONFIG_CPU_FREQ
void rust_helper_cpufreq_register_em_with_opp(struct cpufreq_policy *policy)
{
cpufreq_register_em_with_opp(policy);
}
#endif

View File

@ -7,16 +7,41 @@ void rust_helper_cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
cpumask_set_cpu(cpu, dstp);
}
void rust_helper___cpumask_set_cpu(unsigned int cpu, struct cpumask *dstp)
{
__cpumask_set_cpu(cpu, dstp);
}
void rust_helper_cpumask_clear_cpu(int cpu, struct cpumask *dstp)
{
cpumask_clear_cpu(cpu, dstp);
}
void rust_helper___cpumask_clear_cpu(int cpu, struct cpumask *dstp)
{
__cpumask_clear_cpu(cpu, dstp);
}
bool rust_helper_cpumask_test_cpu(int cpu, struct cpumask *srcp)
{
return cpumask_test_cpu(cpu, srcp);
}
void rust_helper_cpumask_setall(struct cpumask *dstp)
{
cpumask_setall(dstp);
}
bool rust_helper_cpumask_empty(struct cpumask *srcp)
{
return cpumask_empty(srcp);
}
bool rust_helper_cpumask_full(struct cpumask *srcp)
{
return cpumask_full(srcp);
}
unsigned int rust_helper_cpumask_weight(struct cpumask *srcp)
{
return cpumask_weight(srcp);

View File

@ -12,6 +12,8 @@
#include "bug.c"
#include "build_assert.c"
#include "build_bug.c"
#include "clk.c"
#include "cpufreq.c"
#include "cpumask.c"
#include "cred.c"
#include "device.c"

334
rust/kernel/clk.rs Normal file
View File

@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0
//! Clock abstractions.
//!
//! C header: [`include/linux/clk.h`](srctree/include/linux/clk.h)
//!
//! Reference: <https://docs.kernel.org/driver-api/clk.html>
use crate::ffi::c_ulong;
/// The frequency unit.
///
/// Represents a frequency in hertz, wrapping a [`c_ulong`] value.
///
/// ## Examples
///
/// ```
/// use kernel::clk::Hertz;
///
/// let hz = 1_000_000_000;
/// let rate = Hertz(hz);
///
/// assert_eq!(rate.as_hz(), hz);
/// assert_eq!(rate, Hertz(hz));
/// assert_eq!(rate, Hertz::from_khz(hz / 1_000));
/// assert_eq!(rate, Hertz::from_mhz(hz / 1_000_000));
/// assert_eq!(rate, Hertz::from_ghz(hz / 1_000_000_000));
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Hertz(pub c_ulong);
impl Hertz {
/// Create a new instance from kilohertz (kHz)
pub fn from_khz(khz: c_ulong) -> Self {
Self(khz * 1_000)
}
/// Create a new instance from megahertz (MHz)
pub fn from_mhz(mhz: c_ulong) -> Self {
Self(mhz * 1_000_000)
}
/// Create a new instance from gigahertz (GHz)
pub fn from_ghz(ghz: c_ulong) -> Self {
Self(ghz * 1_000_000_000)
}
/// Get the frequency in hertz
pub fn as_hz(&self) -> c_ulong {
self.0
}
/// Get the frequency in kilohertz
pub fn as_khz(&self) -> c_ulong {
self.0 / 1_000
}
/// Get the frequency in megahertz
pub fn as_mhz(&self) -> c_ulong {
self.0 / 1_000_000
}
/// Get the frequency in gigahertz
pub fn as_ghz(&self) -> c_ulong {
self.0 / 1_000_000_000
}
}
impl From<Hertz> for c_ulong {
fn from(freq: Hertz) -> Self {
freq.0
}
}
#[cfg(CONFIG_COMMON_CLK)]
mod common_clk {
use super::Hertz;
use crate::{
device::Device,
error::{from_err_ptr, to_result, Result},
prelude::*,
};
use core::{ops::Deref, ptr};
/// A reference-counted clock.
///
/// Rust abstraction for the C [`struct clk`].
///
/// # Invariants
///
/// A [`Clk`] instance holds either a pointer to a valid [`struct clk`] created by the C
/// portion of the kernel or a NULL pointer.
///
/// Instances of this type are reference-counted. Calling [`Clk::get`] ensures that the
/// allocation remains valid for the lifetime of the [`Clk`].
///
/// ## Examples
///
/// The following example demonstrates how to obtain and configure a clock for a device.
///
/// ```
/// use kernel::c_str;
/// use kernel::clk::{Clk, Hertz};
/// use kernel::device::Device;
/// use kernel::error::Result;
///
/// fn configure_clk(dev: &Device) -> Result {
/// let clk = Clk::get(dev, Some(c_str!("apb_clk")))?;
///
/// clk.prepare_enable()?;
///
/// let expected_rate = Hertz::from_ghz(1);
///
/// if clk.rate() != expected_rate {
/// clk.set_rate(expected_rate)?;
/// }
///
/// clk.disable_unprepare();
/// Ok(())
/// }
/// ```
///
/// [`struct clk`]: https://docs.kernel.org/driver-api/clk.html
#[repr(transparent)]
pub struct Clk(*mut bindings::clk);
impl Clk {
/// Gets [`Clk`] corresponding to a [`Device`] and a connection id.
///
/// Equivalent to the kernel's [`clk_get`] API.
///
/// [`clk_get`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_get
pub fn get(dev: &Device, name: Option<&CStr>) -> Result<Self> {
let con_id = if let Some(name) = name {
name.as_ptr()
} else {
ptr::null()
};
// SAFETY: It is safe to call [`clk_get`] for a valid device pointer.
//
// INVARIANT: The reference-count is decremented when [`Clk`] goes out of scope.
Ok(Self(from_err_ptr(unsafe {
bindings::clk_get(dev.as_raw(), con_id)
})?))
}
/// Obtain the raw [`struct clk`] pointer.
#[inline]
pub fn as_raw(&self) -> *mut bindings::clk {
self.0
}
/// Enable the clock.
///
/// Equivalent to the kernel's [`clk_enable`] API.
///
/// [`clk_enable`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_enable
#[inline]
pub fn enable(&self) -> Result {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_enable`].
to_result(unsafe { bindings::clk_enable(self.as_raw()) })
}
/// Disable the clock.
///
/// Equivalent to the kernel's [`clk_disable`] API.
///
/// [`clk_disable`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_disable
#[inline]
pub fn disable(&self) {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_disable`].
unsafe { bindings::clk_disable(self.as_raw()) };
}
/// Prepare the clock.
///
/// Equivalent to the kernel's [`clk_prepare`] API.
///
/// [`clk_prepare`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_prepare
#[inline]
pub fn prepare(&self) -> Result {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_prepare`].
to_result(unsafe { bindings::clk_prepare(self.as_raw()) })
}
/// Unprepare the clock.
///
/// Equivalent to the kernel's [`clk_unprepare`] API.
///
/// [`clk_unprepare`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_unprepare
#[inline]
pub fn unprepare(&self) {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_unprepare`].
unsafe { bindings::clk_unprepare(self.as_raw()) };
}
/// Prepare and enable the clock.
///
/// Equivalent to calling [`Clk::prepare`] followed by [`Clk::enable`].
#[inline]
pub fn prepare_enable(&self) -> Result {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_prepare_enable`].
to_result(unsafe { bindings::clk_prepare_enable(self.as_raw()) })
}
/// Disable and unprepare the clock.
///
/// Equivalent to calling [`Clk::disable`] followed by [`Clk::unprepare`].
#[inline]
pub fn disable_unprepare(&self) {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_disable_unprepare`].
unsafe { bindings::clk_disable_unprepare(self.as_raw()) };
}
/// Get clock's rate.
///
/// Equivalent to the kernel's [`clk_get_rate`] API.
///
/// [`clk_get_rate`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_get_rate
#[inline]
pub fn rate(&self) -> Hertz {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_get_rate`].
Hertz(unsafe { bindings::clk_get_rate(self.as_raw()) })
}
/// Set clock's rate.
///
/// Equivalent to the kernel's [`clk_set_rate`] API.
///
/// [`clk_set_rate`]: https://docs.kernel.org/core-api/kernel-api.html#c.clk_set_rate
#[inline]
pub fn set_rate(&self, rate: Hertz) -> Result {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for
// [`clk_set_rate`].
to_result(unsafe { bindings::clk_set_rate(self.as_raw(), rate.as_hz()) })
}
}
impl Drop for Clk {
fn drop(&mut self) {
// SAFETY: By the type invariants, self.as_raw() is a valid argument for [`clk_put`].
unsafe { bindings::clk_put(self.as_raw()) };
}
}
/// A reference-counted optional clock.
///
/// A lightweight wrapper around an optional [`Clk`]. An [`OptionalClk`] represents a [`Clk`]
/// that a driver can function without but may improve performance or enable additional
/// features when available.
///
/// # Invariants
///
/// An [`OptionalClk`] instance encapsulates a [`Clk`] with either a valid [`struct clk`] or
/// `NULL` pointer.
///
/// Instances of this type are reference-counted. Calling [`OptionalClk::get`] ensures that the
/// allocation remains valid for the lifetime of the [`OptionalClk`].
///
/// ## Examples
///
/// The following example demonstrates how to obtain and configure an optional clock for a
/// device. The code functions correctly whether or not the clock is available.
///
/// ```
/// use kernel::c_str;
/// use kernel::clk::{OptionalClk, Hertz};
/// use kernel::device::Device;
/// use kernel::error::Result;
///
/// fn configure_clk(dev: &Device) -> Result {
/// let clk = OptionalClk::get(dev, Some(c_str!("apb_clk")))?;
///
/// clk.prepare_enable()?;
///
/// let expected_rate = Hertz::from_ghz(1);
///
/// if clk.rate() != expected_rate {
/// clk.set_rate(expected_rate)?;
/// }
///
/// clk.disable_unprepare();
/// Ok(())
/// }
/// ```
///
/// [`struct clk`]: https://docs.kernel.org/driver-api/clk.html
pub struct OptionalClk(Clk);
impl OptionalClk {
/// Gets [`OptionalClk`] corresponding to a [`Device`] and a connection id.
///
/// Equivalent to the kernel's [`clk_get_optional`] API.
///
/// [`clk_get_optional`]:
/// https://docs.kernel.org/core-api/kernel-api.html#c.clk_get_optional
pub fn get(dev: &Device, name: Option<&CStr>) -> Result<Self> {
let con_id = if let Some(name) = name {
name.as_ptr()
} else {
ptr::null()
};
// SAFETY: It is safe to call [`clk_get_optional`] for a valid device pointer.
//
// INVARIANT: The reference-count is decremented when [`OptionalClk`] goes out of
// scope.
Ok(Self(Clk(from_err_ptr(unsafe {
bindings::clk_get_optional(dev.as_raw(), con_id)
})?)))
}
}
// Make [`OptionalClk`] behave like [`Clk`].
impl Deref for OptionalClk {
type Target = Clk;
fn deref(&self) -> &Clk {
&self.0
}
}
}
#[cfg(CONFIG_COMMON_CLK)]
pub use common_clk::*;

30
rust/kernel/cpu.rs Normal file
View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0
//! Generic CPU definitions.
//!
//! C header: [`include/linux/cpu.h`](srctree/include/linux/cpu.h)
use crate::{bindings, device::Device, error::Result, prelude::ENODEV};
/// Creates a new instance of CPU's device.
///
/// # Safety
///
/// Reference counting is not implemented for the CPU device in the C code. When a CPU is
/// hot-unplugged, the corresponding CPU device is unregistered, but its associated memory
/// is not freed.
///
/// Callers must ensure that the CPU device is not used after it has been unregistered.
/// This can be achieved, for example, by registering a CPU hotplug notifier and removing
/// any references to the CPU device within the notifier's callback.
pub unsafe fn from_cpu(cpu: u32) -> Result<&'static Device> {
// SAFETY: It is safe to call `get_cpu_device()` for any CPU.
let ptr = unsafe { bindings::get_cpu_device(cpu) };
if ptr.is_null() {
return Err(ENODEV);
}
// SAFETY: The pointer returned by `get_cpu_device()`, if not `NULL`, is a valid pointer to
// a `struct device` and is never freed by the C code.
Ok(unsafe { Device::as_ref(ptr) })
}

1321
rust/kernel/cpufreq.rs Normal file

File diff suppressed because it is too large Load Diff

330
rust/kernel/cpumask.rs Normal file
View File

@ -0,0 +1,330 @@
// SPDX-License-Identifier: GPL-2.0
//! CPU Mask abstractions.
//!
//! C header: [`include/linux/cpumask.h`](srctree/include/linux/cpumask.h)
use crate::{
alloc::{AllocError, Flags},
prelude::*,
types::Opaque,
};
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
use core::ptr::{self, NonNull};
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
/// A CPU Mask.
///
/// Rust abstraction for the C `struct cpumask`.
///
/// # Invariants
///
/// A [`Cpumask`] instance always corresponds to a valid C `struct cpumask`.
///
/// The callers must ensure that the `struct cpumask` is valid for access and
/// remains valid for the lifetime of the returned reference.
///
/// ## Examples
///
/// The following example demonstrates how to update a [`Cpumask`].
///
/// ```
/// use kernel::bindings;
/// use kernel::cpumask::Cpumask;
///
/// fn set_clear_cpu(ptr: *mut bindings::cpumask, set_cpu: u32, clear_cpu: i32) {
/// // SAFETY: The `ptr` is valid for writing and remains valid for the lifetime of the
/// // returned reference.
/// let mask = unsafe { Cpumask::as_mut_ref(ptr) };
///
/// mask.set(set_cpu);
/// mask.clear(clear_cpu);
/// }
/// ```
#[repr(transparent)]
pub struct Cpumask(Opaque<bindings::cpumask>);
impl Cpumask {
/// Creates a mutable reference to an existing `struct cpumask` pointer.
///
/// # Safety
///
/// The caller must ensure that `ptr` is valid for writing and remains valid for the lifetime
/// of the returned reference.
pub unsafe fn as_mut_ref<'a>(ptr: *mut bindings::cpumask) -> &'a mut Self {
// SAFETY: Guaranteed by the safety requirements of the function.
//
// INVARIANT: The caller ensures that `ptr` is valid for writing and remains valid for the
// lifetime of the returned reference.
unsafe { &mut *ptr.cast() }
}
/// Creates a reference to an existing `struct cpumask` pointer.
///
/// # Safety
///
/// The caller must ensure that `ptr` is valid for reading and remains valid for the lifetime
/// of the returned reference.
pub unsafe fn as_ref<'a>(ptr: *const bindings::cpumask) -> &'a Self {
// SAFETY: Guaranteed by the safety requirements of the function.
//
// INVARIANT: The caller ensures that `ptr` is valid for reading and remains valid for the
// lifetime of the returned reference.
unsafe { &*ptr.cast() }
}
/// Obtain the raw `struct cpumask` pointer.
pub fn as_raw(&self) -> *mut bindings::cpumask {
let this: *const Self = self;
this.cast_mut().cast()
}
/// Set `cpu` in the cpumask.
///
/// ATTENTION: Contrary to C, this Rust `set()` method is non-atomic.
/// This mismatches kernel naming convention and corresponds to the C
/// function `__cpumask_set_cpu()`.
#[inline]
pub fn set(&mut self, cpu: u32) {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `__cpumask_set_cpu`.
unsafe { bindings::__cpumask_set_cpu(cpu, self.as_raw()) };
}
/// Clear `cpu` in the cpumask.
///
/// ATTENTION: Contrary to C, this Rust `clear()` method is non-atomic.
/// This mismatches kernel naming convention and corresponds to the C
/// function `__cpumask_clear_cpu()`.
#[inline]
pub fn clear(&mut self, cpu: i32) {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to
// `__cpumask_clear_cpu`.
unsafe { bindings::__cpumask_clear_cpu(cpu, self.as_raw()) };
}
/// Test `cpu` in the cpumask.
///
/// Equivalent to the kernel's `cpumask_test_cpu` API.
#[inline]
pub fn test(&self, cpu: i32) -> bool {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `cpumask_test_cpu`.
unsafe { bindings::cpumask_test_cpu(cpu, self.as_raw()) }
}
/// Set all CPUs in the cpumask.
///
/// Equivalent to the kernel's `cpumask_setall` API.
#[inline]
pub fn setall(&mut self) {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `cpumask_setall`.
unsafe { bindings::cpumask_setall(self.as_raw()) };
}
/// Checks if cpumask is empty.
///
/// Equivalent to the kernel's `cpumask_empty` API.
#[inline]
pub fn empty(&self) -> bool {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `cpumask_empty`.
unsafe { bindings::cpumask_empty(self.as_raw()) }
}
/// Checks if cpumask is full.
///
/// Equivalent to the kernel's `cpumask_full` API.
#[inline]
pub fn full(&self) -> bool {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `cpumask_full`.
unsafe { bindings::cpumask_full(self.as_raw()) }
}
/// Get weight of the cpumask.
///
/// Equivalent to the kernel's `cpumask_weight` API.
#[inline]
pub fn weight(&self) -> u32 {
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `cpumask_weight`.
unsafe { bindings::cpumask_weight(self.as_raw()) }
}
/// Copy cpumask.
///
/// Equivalent to the kernel's `cpumask_copy` API.
#[inline]
pub fn copy(&self, dstp: &mut Self) {
// SAFETY: By the type invariant, `Self::as_raw` is a valid argument to `cpumask_copy`.
unsafe { bindings::cpumask_copy(dstp.as_raw(), self.as_raw()) };
}
}
/// A CPU Mask pointer.
///
/// Rust abstraction for the C `struct cpumask_var_t`.
///
/// # Invariants
///
/// A [`CpumaskVar`] instance always corresponds to a valid C `struct cpumask_var_t`.
///
/// The callers must ensure that the `struct cpumask_var_t` is valid for access and remains valid
/// for the lifetime of [`CpumaskVar`].
///
/// ## Examples
///
/// The following example demonstrates how to create and update a [`CpumaskVar`].
///
/// ```
/// use kernel::cpumask::CpumaskVar;
///
/// let mut mask = CpumaskVar::new_zero(GFP_KERNEL).unwrap();
///
/// assert!(mask.empty());
/// mask.set(2);
/// assert!(mask.test(2));
/// mask.set(3);
/// assert!(mask.test(3));
/// assert_eq!(mask.weight(), 2);
///
/// let mask2 = CpumaskVar::try_clone(&mask).unwrap();
/// assert!(mask2.test(2));
/// assert!(mask2.test(3));
/// assert_eq!(mask2.weight(), 2);
/// ```
pub struct CpumaskVar {
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
ptr: NonNull<Cpumask>,
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
mask: Cpumask,
}
impl CpumaskVar {
/// Creates a zero-initialized instance of the [`CpumaskVar`].
pub fn new_zero(_flags: Flags) -> Result<Self, AllocError> {
Ok(Self {
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
ptr: {
let mut ptr: *mut bindings::cpumask = ptr::null_mut();
// SAFETY: It is safe to call this method as the reference to `ptr` is valid.
//
// INVARIANT: The associated memory is freed when the `CpumaskVar` goes out of
// scope.
unsafe { bindings::zalloc_cpumask_var(&mut ptr, _flags.as_raw()) };
NonNull::new(ptr.cast()).ok_or(AllocError)?
},
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
// SAFETY: FFI type is valid to be zero-initialized.
//
// INVARIANT: The associated memory is freed when the `CpumaskVar` goes out of scope.
mask: unsafe { core::mem::zeroed() },
})
}
/// Creates an instance of the [`CpumaskVar`].
///
/// # Safety
///
/// The caller must ensure that the returned [`CpumaskVar`] is properly initialized before
/// getting used.
pub unsafe fn new(_flags: Flags) -> Result<Self, AllocError> {
Ok(Self {
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
ptr: {
let mut ptr: *mut bindings::cpumask = ptr::null_mut();
// SAFETY: It is safe to call this method as the reference to `ptr` is valid.
//
// INVARIANT: The associated memory is freed when the `CpumaskVar` goes out of
// scope.
unsafe { bindings::alloc_cpumask_var(&mut ptr, _flags.as_raw()) };
NonNull::new(ptr.cast()).ok_or(AllocError)?
},
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
// SAFETY: Guaranteed by the safety requirements of the function.
//
// INVARIANT: The associated memory is freed when the `CpumaskVar` goes out of scope.
mask: unsafe { MaybeUninit::uninit().assume_init() },
})
}
/// Creates a mutable reference to an existing `struct cpumask_var_t` pointer.
///
/// # Safety
///
/// The caller must ensure that `ptr` is valid for writing and remains valid for the lifetime
/// of the returned reference.
pub unsafe fn as_mut_ref<'a>(ptr: *mut bindings::cpumask_var_t) -> &'a mut Self {
// SAFETY: Guaranteed by the safety requirements of the function.
//
// INVARIANT: The caller ensures that `ptr` is valid for writing and remains valid for the
// lifetime of the returned reference.
unsafe { &mut *ptr.cast() }
}
/// Creates a reference to an existing `struct cpumask_var_t` pointer.
///
/// # Safety
///
/// The caller must ensure that `ptr` is valid for reading and remains valid for the lifetime
/// of the returned reference.
pub unsafe fn as_ref<'a>(ptr: *const bindings::cpumask_var_t) -> &'a Self {
// SAFETY: Guaranteed by the safety requirements of the function.
//
// INVARIANT: The caller ensures that `ptr` is valid for reading and remains valid for the
// lifetime of the returned reference.
unsafe { &*ptr.cast() }
}
/// Clones cpumask.
pub fn try_clone(cpumask: &Cpumask) -> Result<Self> {
// SAFETY: The returned cpumask_var is initialized right after this call.
let mut cpumask_var = unsafe { Self::new(GFP_KERNEL) }?;
cpumask.copy(&mut cpumask_var);
Ok(cpumask_var)
}
}
// Make [`CpumaskVar`] behave like a pointer to [`Cpumask`].
impl Deref for CpumaskVar {
type Target = Cpumask;
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
fn deref(&self) -> &Self::Target {
// SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
unsafe { &*self.ptr.as_ptr() }
}
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
fn deref(&self) -> &Self::Target {
&self.mask
}
}
impl DerefMut for CpumaskVar {
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
fn deref_mut(&mut self) -> &mut Cpumask {
// SAFETY: The caller owns CpumaskVar, so it is safe to deref the cpumask.
unsafe { self.ptr.as_mut() }
}
#[cfg(not(CONFIG_CPUMASK_OFFSTACK))]
fn deref_mut(&mut self) -> &mut Cpumask {
&mut self.mask
}
}
impl Drop for CpumaskVar {
fn drop(&mut self) {
#[cfg(CONFIG_CPUMASK_OFFSTACK)]
// SAFETY: By the type invariant, `self.as_raw` is a valid argument to `free_cpumask_var`.
unsafe {
bindings::free_cpumask_var(self.as_raw())
};
}
}

View File

@ -44,8 +44,13 @@ pub mod auxiliary;
pub mod block;
#[doc(hidden)]
pub mod build_assert;
pub mod clk;
#[cfg(CONFIG_CONFIGFS_FS)]
pub mod configfs;
pub mod cpu;
#[cfg(CONFIG_CPU_FREQ)]
pub mod cpufreq;
pub mod cpumask;
pub mod cred;
pub mod device;
pub mod device_id;
@ -70,6 +75,8 @@ pub mod miscdevice;
#[cfg(CONFIG_NET)]
pub mod net;
pub mod of;
#[cfg(CONFIG_PM_OPP)]
pub mod opp;
pub mod page;
#[cfg(CONFIG_PCI)]
pub mod pci;

1146
rust/kernel/opp.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -176,7 +176,9 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
let info = ModuleInfo::parse(&mut it);
let mut modinfo = ModInfoBuilder::new(info.name.as_ref());
// Rust does not allow hyphens in identifiers, use underscore instead.
let ident = info.name.replace('-', "_");
let mut modinfo = ModInfoBuilder::new(ident.as_ref());
if let Some(author) = info.author {
modinfo.emit("author", &author);
}
@ -301,14 +303,15 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
#[doc(hidden)]
#[link_section = \"{initcall_section}\"]
#[used]
pub static __{name}_initcall: extern \"C\" fn() -> kernel::ffi::c_int = __{name}_init;
pub static __{ident}_initcall: extern \"C\" fn() ->
kernel::ffi::c_int = __{ident}_init;
#[cfg(not(MODULE))]
#[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
core::arch::global_asm!(
r#\".section \"{initcall_section}\", \"a\"
__{name}_initcall:
.long __{name}_init - .
__{ident}_initcall:
.long __{ident}_init - .
.previous
\"#
);
@ -316,7 +319,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
#[cfg(not(MODULE))]
#[doc(hidden)]
#[no_mangle]
pub extern \"C\" fn __{name}_init() -> kernel::ffi::c_int {{
pub extern \"C\" fn __{ident}_init() -> kernel::ffi::c_int {{
// SAFETY: This function is inaccessible to the outside due to the double
// module wrapping it. It is called exactly once by the C side via its
// placement above in the initcall section.
@ -326,13 +329,13 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
#[cfg(not(MODULE))]
#[doc(hidden)]
#[no_mangle]
pub extern \"C\" fn __{name}_exit() {{
pub extern \"C\" fn __{ident}_exit() {{
// SAFETY:
// - This function is inaccessible to the outside due to the double
// module wrapping it. It is called exactly once by the C side via its
// unique name,
// - furthermore it is only called after `__{name}_init` has returned `0`
// (which delegates to `__init`).
// - furthermore it is only called after `__{ident}_init` has
// returned `0` (which delegates to `__init`).
unsafe {{ __exit() }}
}}
@ -372,6 +375,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
",
type_ = info.type_,
name = info.name,
ident = ident,
modinfo = modinfo.buffer,
initcall_section = ".initcall6.init"
)