| // SPDX-License-Identifier: GPL-2.0
|
| /*
|
| * KernelSpace Profiles
|
| *
|
| * This Linux kernel module provides a framework for managing and switching between system profiles or modes
|
| * at the kernel level. Each profile represents a specific configuration of kernel features and settings
|
| * optimized for different use cases such as battery life, balanced performance, or maximum performance.
|
| *
|
| * The module supports various subsystems, including MSM DRM, MI DRM, and framebuffer (FB). It integrates
|
| * with these subsystems to receive notifications about screen state changes and adjust the active profile
|
| * accordingly.
|
| *
|
| * The module offers functions for setting the profile mode, overriding the mode temporarily, and retrieving
|
| * the active profile mode. Profiles can be dynamically switched based on system events, user requests, or
|
| * time-based rules.
|
| *
|
| * For more information and usage examples, refer to the README file at:
|
| * https://github.com/beakthoven/Kprofiles/blob/main/README.md
|
| *
|
| * Copyright (C) 2021-2025 Dakkshesh <dakkshesh5@gmail.com>
|
| * Version: 6.0.0
|
| * License: GPL-2.0
|
| */
|
|
|
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
| #include <linux/delay.h>
|
| #include <linux/module.h>
|
| #include <linux/moduleparam.h>
|
| #include <linux/notifier.h>
|
| #include <linux/kobject.h>
|
| #include <linux/sysfs.h>
|
| #include <linux/mutex.h>
|
| #include <linux/compiler.h>
|
| #include "version.h"
|
|
|
| static BLOCKING_NOTIFIER_HEAD(kp_mode_notifier);
|
| unsigned int KP_MODE_CHANGE = 0x80000000;
|
| EXPORT_SYMBOL_GPL(KP_MODE_CHANGE);
|
|
|
| static unsigned int kp_override_mode;
|
| static bool kp_override;
|
|
|
| static bool auto_kp __read_mostly = 0;
|
| module_param(auto_kp, bool, 0664);
|
| MODULE_PARM_DESC(auto_kp, "Enable/disable automatic kernel profile management");
|
|
|
| static unsigned int kp_mode = CONFIG_KP_DEFAULT_MODE;
|
|
|
| static struct kobject *kp_kobj;
|
|
|
| DEFINE_MUTEX(kp_set_mode_rb_lock);
|
|
|
| #ifdef CONFIG_KP_VERBOSE_DEBUG
|
| #define kp_dbg(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
|
| #else
|
| #define kp_dbg(fmt, ...)
|
| #endif
|
|
|
| #define kp_err(fmt, ...) pr_err(fmt, ##__VA_ARGS__)
|
| #define kp_info(fmt, ...) pr_info(fmt, ##__VA_ARGS__)
|
|
|
| /*
|
| * Mapping:
|
| * - 0 -> 2 (kprofiles "disabled" behaves like mode 2 to in-kernel users)
|
| * - 1..3 unchanged
|
| */
|
| static __always_inline unsigned int kp_effective_mode(unsigned int stored)
|
| {
|
| if (stored == 0)
|
| return 2;
|
|
|
| return stored;
|
| }
|
|
|
| static void kp_trigger_mode_change_event(void);
|
|
|
| static __always_inline int __kp_set_mode(unsigned int level)
|
| {
|
| if (unlikely(level > 3))
|
| return -EINVAL;
|
|
|
| WRITE_ONCE(kp_mode, level);
|
| return 0;
|
| }
|
|
|
| /**
|
| * kp_set_mode_rollback - Change profile to a given mode for a specific duration
|
| * @level: The profile mode level to set (0-3)
|
| * @duration_ms: The duration to keep the profile mode in milliseconds
|
| *
|
| * This function changes the profile to the specified mode for a specific
|
| * duration during any in-kernel event, and then returns to the previously
|
| * active mode.
|
| *
|
| * Usage example: kp_set_mode_rollback(3, 55);
|
| */
|
| void kp_set_mode_rollback(unsigned int level, unsigned int duration_ms)
|
| {
|
| if (unlikely(level > 3)) {
|
| kp_err("Invalid mode requested, skipping mode change.\n");
|
| return;
|
| }
|
|
|
| mutex_lock(&kp_set_mode_rb_lock);
|
|
|
| WRITE_ONCE(kp_override_mode, level);
|
| WRITE_ONCE(kp_override, true);
|
| kp_trigger_mode_change_event();
|
|
|
| msleep(duration_ms);
|
|
|
| WRITE_ONCE(kp_override, false);
|
| kp_trigger_mode_change_event();
|
|
|
| mutex_unlock(&kp_set_mode_rb_lock);
|
| }
|
| EXPORT_SYMBOL_GPL(kp_set_mode_rollback);
|
|
|
| /**
|
| * kp_set_mode - Change profile to a given mode
|
| * @level: The profile mode level to set (0-3)
|
| *
|
| * This function changes the profile to the specified mode during any
|
| * in-kernel event.
|
| *
|
| * Usage example: kp_set_mode(3);
|
| */
|
| void kp_set_mode(unsigned int level)
|
| {
|
| int ret = 0;
|
|
|
| mutex_lock(&kp_set_mode_rb_lock);
|
|
|
| ret = __kp_set_mode(level);
|
| if (ret) {
|
| kp_err("Invalid mode requested, skipping mode change.\n");
|
| mutex_unlock(&kp_set_mode_rb_lock);
|
| return;
|
| }
|
|
|
| kp_trigger_mode_change_event();
|
|
|
| mutex_unlock(&kp_set_mode_rb_lock);
|
| }
|
| EXPORT_SYMBOL_GPL(kp_set_mode);
|
|
|
| /**
|
| * kp_active_mode - Get the currently active profile mode
|
| *
|
| * This function returns a number from 0 to 3 depending on the active profile mode.
|
| * The returned value can be used in conditions to disable/enable or tune kernel
|
| * features according to the profile mode.
|
| *
|
| * Return:
|
| * The currently active profile mode (0-3)
|
| *
|
| * Usage example:
|
| *
|
| * switch (kp_active_mode()) {
|
| * case 1:
|
| * // Things to be done when battery profile is active
|
| * break;
|
| * case 2:
|
| * // Things to be done when balanced profile is active
|
| * break;
|
| * case 3:
|
| * // Things to be done when performance profile is active
|
| * break;
|
| * default:
|
| * // Things to be done when kprofiles is disabled (forced to balanced in kernel)
|
| * break;
|
| * }
|
| */
|
| int kp_active_mode(void)
|
| {
|
| unsigned int mode;
|
|
|
| if (READ_ONCE(kp_override)) {
|
| mode = READ_ONCE(kp_override_mode);
|
| if (unlikely(mode > 3))
|
| mode = 0;
|
| return kp_effective_mode(mode);
|
| }
|
|
|
| mode = READ_ONCE(kp_mode);
|
| if (unlikely(mode > 3)) {
|
| WRITE_ONCE(kp_mode, 0);
|
| kp_err("Invalid value passed, falling back to level 0\n");
|
| mode = 0;
|
| }
|
|
|
| return kp_effective_mode(mode);
|
| }
|
| EXPORT_SYMBOL_GPL(kp_active_mode);
|
|
|
| /**
|
| * kp_trigger_mode_change_event - Trigger a mode change event
|
| *
|
| * This function triggers a mode change event by calling the blocking notifier
|
| * chain for kp_mode_notifier. It informs all registered listeners about the
|
| * change in the profile mode.
|
| */
|
| static __always_inline void kp_trigger_mode_change_event(void)
|
| {
|
| unsigned int current_mode = kp_active_mode();
|
|
|
| blocking_notifier_call_chain(&kp_mode_notifier, KP_MODE_CHANGE,
|
| (void *)(uintptr_t)current_mode);
|
| }
|
|
|
| /**
|
| * kp_notifier_register_client - Register a notifier client for profile mode changes
|
| * @nb: The notifier block to register
|
| *
|
| * This function registers a notifier client to receive notifications about profile mode changes.
|
| *
|
| * Return:
|
| * 0 on success, or an error code on failure.
|
| */
|
| int kp_notifier_register_client(struct notifier_block *nb)
|
| {
|
| return blocking_notifier_chain_register(&kp_mode_notifier, nb);
|
| }
|
| EXPORT_SYMBOL_GPL(kp_notifier_register_client);
|
|
|
| /**
|
| * kp_notifier_unregister_client - Unregister a notifier client for profile mode changes
|
| * @nb: The notifier block to unregister
|
| *
|
| * This function unregisters a previously registered notifier client for profile mode changes.
|
| *
|
| * Return:
|
| * 0 on success, or an error code on failure.
|
| */
|
| int kp_notifier_unregister_client(struct notifier_block *nb)
|
| {
|
| return blocking_notifier_chain_unregister(&kp_mode_notifier, nb);
|
| }
|
| EXPORT_SYMBOL_GPL(kp_notifier_unregister_client);
|
|
|
| static inline ssize_t kp_mode_show(struct kobject *kobj,
|
| struct kobj_attribute *attr, char *buf)
|
| {
|
| return sysfs_emit(buf, "%u\n", READ_ONCE(kp_mode));
|
| }
|
|
|
| static inline ssize_t kp_mode_store(struct kobject *kobj,
|
| struct kobj_attribute *attr,
|
| const char *buf, size_t count)
|
| {
|
| unsigned int new_mode;
|
| int ret;
|
|
|
| ret = kstrtouint(buf, 10, &new_mode);
|
| if (ret)
|
| return ret;
|
|
|
| mutex_lock(&kp_set_mode_rb_lock);
|
|
|
| ret = __kp_set_mode(new_mode);
|
| if (ret) {
|
| kp_err("User changed mode is invalid, skipping mode change.\n");
|
| mutex_unlock(&kp_set_mode_rb_lock);
|
| return ret;
|
| }
|
|
|
| kp_trigger_mode_change_event();
|
|
|
| mutex_unlock(&kp_set_mode_rb_lock);
|
|
|
| return count;
|
| }
|
|
|
| static struct kobj_attribute kp_mode_attribute =
|
| __ATTR(kp_mode, 0664, kp_mode_show, kp_mode_store);
|
|
|
| static struct attribute *kp_attrs[] = {
|
| &kp_mode_attribute.attr,
|
| NULL,
|
| };
|
|
|
| static struct attribute_group kp_attr_group = {
|
| .attrs = kp_attrs,
|
| };
|
|
|
| static int __init kp_init(void)
|
| {
|
| int ret = 0;
|
|
|
| kp_kobj = kobject_create_and_add("kprofiles", kernel_kobj);
|
| if (!kp_kobj) {
|
| kp_err("Failed to create Kprofiles kobject\n");
|
| return -ENOMEM;
|
| }
|
|
|
| ret = sysfs_create_group(kp_kobj, &kp_attr_group);
|
| if (ret) {
|
| kp_err("Failed to create sysfs attributes for Kprofiles\n");
|
| kobject_put(kp_kobj);
|
| return ret;
|
| }
|
|
|
| kp_info("Kprofiles " KPROFILES_VERSION
|
| " loaded successfully. For further details, visit https://github.com/beakthoven/Kprofiles/blob/main/README.md\n");
|
| kp_info("Copyright (C) 2021-2025 Dakkshesh <dakkshesh5@gmail.com>.\n");
|
|
|
| return ret;
|
| }
|
| module_init(kp_init);
|
|
|
| static void __exit kp_exit(void)
|
| {
|
| sysfs_remove_group(kp_kobj, &kp_attr_group);
|
| kobject_put(kp_kobj);
|
| }
|
| module_exit(kp_exit);
|
|
|
| MODULE_LICENSE("GPL v2");
|
| MODULE_DESCRIPTION("KernelSpace Profiles");
|
| MODULE_AUTHOR("Dakkshesh <dakkshesh5@gmail.com>");
|
| MODULE_VERSION(KPROFILES_VERSION);
|