Skip to content

Commit

Permalink
ACPI: platform: Add platform profile support
Browse files Browse the repository at this point in the history
This is the initial implementation of the platform-profile feature.
It provides the details discussed and outlined in the
sysfs-platform_profile document.

Many modern systems have the ability to modify the operating profile to
control aspects like fan speed, temperature and power levels. This
module provides a common sysfs interface that platform modules can register
against to control their individual profile options.

Signed-off-by: Mark Pearson <markpearson@lenovo.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
[ rjw: Use full words in enum values names ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
  • Loading branch information
Mark Pearson authored and Rafael J. Wysocki committed Dec 30, 2020
1 parent 8e0cbf3 commit a2ff95e
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
17 changes: 17 additions & 0 deletions drivers/acpi/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,23 @@ config ACPI_THERMAL
To compile this driver as a module, choose M here:
the module will be called thermal.

config ACPI_PLATFORM_PROFILE
tristate "ACPI Platform Profile Driver"
default m
help
This driver adds support for platform-profiles on platforms that
support it.

Platform-profiles can be used to control the platform behaviour. For
example whether to operate in a lower power mode, in a higher
power performance mode or between the two.

This driver provides the sysfs interface and is used as the registration
point for platform specific drivers.

Which profiles are supported is determined on a per-platform basis and
should be obtained from the platform specific driver.

config ACPI_CUSTOM_DSDT_FILE
string "Custom DSDT Table file to include"
default ""
Expand Down
1 change: 1 addition & 0 deletions drivers/acpi/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ obj-$(CONFIG_ACPI_PCI_SLOT) += pci_slot.o
obj-$(CONFIG_ACPI_PROCESSOR) += processor.o
obj-$(CONFIG_ACPI) += container.o
obj-$(CONFIG_ACPI_THERMAL) += thermal.o
obj-$(CONFIG_ACPI_PLATFORM_PROFILE) += platform_profile.o
obj-$(CONFIG_ACPI_NFIT) += nfit/
obj-$(CONFIG_ACPI_NUMA) += numa/
obj-$(CONFIG_ACPI) += acpi_memhotplug.o
Expand Down
181 changes: 181 additions & 0 deletions drivers/acpi/platform_profile.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-or-later

/* Platform profile sysfs interface */

#include <linux/acpi.h>
#include <linux/bits.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/platform_profile.h>
#include <linux/sysfs.h>

static const struct platform_profile_handler *cur_profile;
static DEFINE_MUTEX(profile_lock);

static const char * const profile_names[] = {
[PLATFORM_PROFILE_LOW_POWER] = "low-power",
[PLATFORM_PROFILE_COOL] = "cool",
[PLATFORM_PROFILE_QUIET] = "quiet",
[PLATFORM_PROFILE_BALANCED] = "balanced",
[PLATFORM_PROFILE_PERFORMANCE] = "performance",
};
static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);

static ssize_t platform_profile_choices_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int len = 0;
int err, i;

err = mutex_lock_interruptible(&profile_lock);
if (err)
return err;

if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}

for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {
if (len == 0)
len += sysfs_emit_at(buf, len, "%s", profile_names[i]);
else
len += sysfs_emit_at(buf, len, " %s", profile_names[i]);
}
len += sysfs_emit_at(buf, len, "\n");
mutex_unlock(&profile_lock);
return len;
}

static ssize_t platform_profile_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
int err;

err = mutex_lock_interruptible(&profile_lock);
if (err)
return err;

if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}

err = cur_profile->profile_get(&profile);
mutex_unlock(&profile_lock);
if (err)
return err;

/* Check that profile is valid index */
if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
return -EIO;

return sysfs_emit(buf, "%s\n", profile_names[profile]);
}

static ssize_t platform_profile_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int err, i;

err = mutex_lock_interruptible(&profile_lock);
if (err)
return err;

if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}

/* Scan for a matching profile */
i = sysfs_match_string(profile_names, buf);
if (i < 0) {
mutex_unlock(&profile_lock);
return -EINVAL;
}

/* Check that platform supports this profile choice */
if (!test_bit(i, cur_profile->choices)) {
mutex_unlock(&profile_lock);
return -EOPNOTSUPP;
}

err = cur_profile->profile_set(i);
mutex_unlock(&profile_lock);
if (err)
return err;
return count;
}

static DEVICE_ATTR_RO(platform_profile_choices);
static DEVICE_ATTR_RW(platform_profile);

static struct attribute *platform_profile_attrs[] = {
&dev_attr_platform_profile_choices.attr,
&dev_attr_platform_profile.attr,
NULL
};

static const struct attribute_group platform_profile_group = {
.attrs = platform_profile_attrs
};

void platform_profile_notify(void)
{
if (!cur_profile)
return;
sysfs_notify(acpi_kobj, NULL, "platform_profile");
}
EXPORT_SYMBOL_GPL(platform_profile_notify);

int platform_profile_register(const struct platform_profile_handler *pprof)
{
int err;

mutex_lock(&profile_lock);
/* We can only have one active profile */
if (cur_profile) {
mutex_unlock(&profile_lock);
return -EEXIST;
}

/* Sanity check the profile handler field are set */
if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
!pprof->profile_set || !pprof->profile_get) {
mutex_unlock(&profile_lock);
return -EINVAL;
}

err = sysfs_create_group(acpi_kobj, &platform_profile_group);
if (err) {
mutex_unlock(&profile_lock);
return err;
}

cur_profile = pprof;
mutex_unlock(&profile_lock);
return 0;
}
EXPORT_SYMBOL_GPL(platform_profile_register);

int platform_profile_remove(void)
{
mutex_lock(&profile_lock);
if (!cur_profile) {
mutex_unlock(&profile_lock);
return -ENODEV;
}

sysfs_remove_group(acpi_kobj, &platform_profile_group);
cur_profile = NULL;
mutex_unlock(&profile_lock);
return 0;
}
EXPORT_SYMBOL_GPL(platform_profile_remove);

MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
MODULE_LICENSE("GPL");
39 changes: 39 additions & 0 deletions include/linux/platform_profile.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Platform profile sysfs interface
*
* See Documentation/ABI/testing/sysfs-platform_profile.rst for more
* information.
*/

#ifndef _PLATFORM_PROFILE_H_
#define _PLATFORM_PROFILE_H_

#include <linux/bitops.h>

/*
* If more options are added please update profile_names
* array in platform-profile.c and sysfs-platform-profile.rst
* documentation.
*/

enum platform_profile_option {
PLATFORM_PROFILE_LOW_POWER,
PLATFORM_PROFILE_COOL,
PLATFORM_PROFILE_QUIET,
PLATFORM_PROFILE_BALANCED,
PLATFORM_PROFILE_PERFORMANCE,
PLATFORM_PROFILE_LAST, /*must always be last */
};

struct platform_profile_handler {
unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
int (*profile_get)(enum platform_profile_option *profile);
int (*profile_set)(enum platform_profile_option profile);
};

int platform_profile_register(const struct platform_profile_handler *pprof);
int platform_profile_remove(void);
void platform_profile_notify(void);

#endif /*_PLATFORM_PROFILE_H_*/

0 comments on commit a2ff95e

Please sign in to comment.