public inbox for linux-acpi@vger.kernel.org
 help / color / mirror / Atom feed
From: Antheas Kapenekakis <lkml@antheas.dev>
To: dmitry.osipenko@collabora.com
Cc: bob.beckett@collabora.com, bookeldor@gmail.com,
	hadess@hadess.net, jaap@haitsma.org, kernel@collabora.com,
	lennart@poettering.net, linux-acpi@vger.kernel.org,
	linux-kernel@vger.kernel.org, lkml@antheas.dev, mccann@jhu.edu,
	rafael@kernel.org, richard@hughsie.com,
	sebastian.reichel@collabora.com, superm1@kernel.org,
	systemd-devel@lists.freedesktop.org, xaver.hugl@gmail.com
Subject: [RFC v2 08/10] hint: Add hint class ABI for devices to receive updates on host activity
Date: Sat, 25 Apr 2026 23:57:32 +0200	[thread overview]
Message-ID: <20260425215734.14116-9-lkml@antheas.dev> (raw)
In-Reply-To: <20260425215734.14116-1-lkml@antheas.dev>

The s2idle device requires the ability for userspace to inform of its
idleness state (e.g., inactive, active, snooze) which is used to update
device appearance (e.g., turn off keyboard backlight). Ideally, we would
want to extend this ability to non-ACPI/non-X86 devices and drivers,
e.g. memory mapped ECs, to be able to use this activity hint to for
example turn off their backlight. Moreover, to prevent adding new ABIs,
we would also like the ability to add new types of hints in the future.

Therefore, introduce a /sys/class/hint sysfs interface, with
{idle, idle_choices} to allow userspace to inform devices of
its current idleness. The initial ABI for class hint makes {idle,
idle_choices} optional, which allows for future hint additions that
are unrelated without forcing the implementation of the idle hint.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 MAINTAINERS           |   8 ++
 drivers/base/Kconfig  |   3 +
 drivers/base/Makefile |   1 +
 drivers/base/hint.c   | 283 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/hint.h  |  38 ++++++
 5 files changed, 333 insertions(+)
 create mode 100644 drivers/base/hint.c
 create mode 100644 include/linux/hint.h

diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e12fe1f..12712b628449 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7324,6 +7324,14 @@ F:	drivers/devfreq/event/
 F:	include/dt-bindings/pmu/exynos_ppmu.h
 F:	include/linux/devfreq-event.h
 
+DEVICE HINTS
+M:	Antheas Kapenekakis <lkml@antheas.dev>
+L:	driver-core@lists.linux.dev
+S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-hint
+F:	drivers/base/hint.c
+F:	include/linux/hint.h
+
 DEVICE I/O & IRQ [RUST]
 M:	Danilo Krummrich <dakr@kernel.org>
 M:	Alice Ryhl <aliceryhl@google.com>
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 1786d87b29e2..c5316e8f98d0 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -222,6 +222,9 @@ config DMA_FENCE_TRACE
 	  lockup related problems for dma-buffers shared across multiple
 	  devices.
 
+config HINT
+	bool
+
 config GENERIC_ARCH_TOPOLOGY
 	bool
 	help
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 8074a10183dc..27557351fa20 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_GENERIC_MSI_IRQ) += platform-msi.o
 obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o
 obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o
 obj-$(CONFIG_ACPI) += physical_location.o
+obj-$(CONFIG_HINT) += hint.o
 
 obj-y			+= test/
 
diff --git a/drivers/base/hint.c b/drivers/base/hint.c
new file mode 100644
index 000000000000..0be4116d68cc
--- /dev/null
+++ b/drivers/base/hint.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* Hint sysfs interface */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/hint.h>
+#include <linux/sysfs.h>
+
+struct hint_handler {
+	const char *name;
+	struct device dev;
+	int minor;
+	struct mutex lock; /* Prevents parallel calls to class device. */
+	unsigned long idle_choices[BITS_TO_LONGS(HINT_IDLE_LAST)];
+	enum hint_idle_option idle;
+	const struct hint_ops *ops;
+};
+
+#define to_hint_handler(d)	(container_of(d, struct hint_handler, dev))
+
+static const char * const hint_idle_names[] = {
+	[HINT_IDLE_SNOOZE]   = "snooze",
+	[HINT_IDLE_RESUME]   = "resume",
+	[HINT_IDLE_INACTIVE] = "inactive",
+	[HINT_IDLE_ACTIVE]   = "active",
+};
+static_assert(ARRAY_SIZE(hint_idle_names) == HINT_IDLE_LAST);
+
+static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct hint_handler *handler = to_hint_handler(dev);
+
+	return sysfs_emit(buf, "%s\n", handler->name);
+}
+static DEVICE_ATTR_RO(name);
+
+static ssize_t idle_choices_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct hint_handler *handler = to_hint_handler(dev);
+	int i, len = 0;
+
+	for_each_set_bit(i, handler->idle_choices, HINT_IDLE_LAST) {
+		if (len == 0)
+			len += sysfs_emit_at(buf, len, "%s", hint_idle_names[i]);
+		else
+			len += sysfs_emit_at(buf, len, " %s", hint_idle_names[i]);
+	}
+	len += sysfs_emit_at(buf, len, "\n");
+
+	return len;
+}
+static DEVICE_ATTR_RO(idle_choices);
+
+static ssize_t idle_show(struct device *dev, struct device_attribute *attr,
+			 char *buf)
+{
+	enum hint_idle_option idle = HINT_IDLE_LAST;
+	struct hint_handler *handler = to_hint_handler(dev);
+	int err;
+
+	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &handler->lock) {
+		if (!handler->ops->idle_get)
+			return handler->idle;
+
+		err = handler->ops->idle_get(dev, &idle);
+		if (err)
+			return err;
+
+		if (WARN_ON(idle >= HINT_IDLE_LAST))
+			return -EINVAL;
+	}
+
+	return sysfs_emit(buf, "%s\n", hint_idle_names[idle]);
+}
+
+static ssize_t idle_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t count)
+{
+	struct hint_handler *handler = to_hint_handler(dev);
+	int index, ret;
+
+	index = sysfs_match_string(hint_idle_names, buf);
+	if (index < 0)
+		return -EINVAL;
+
+	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &handler->lock) {
+		if (!test_bit(index, handler->idle_choices))
+			return -EOPNOTSUPP;
+
+		if (handler->ops->idle_set) {
+			ret = handler->ops->idle_set(dev, index);
+			if (ret)
+				return ret;
+		}
+		handler->idle = index;
+	}
+
+	return count;
+}
+static DEVICE_ATTR_RW(idle);
+
+static umode_t hint_attr_is_visible(struct kobject *kobj,
+				    struct attribute *attr, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	struct hint_handler *handler = to_hint_handler(dev);
+
+	if ((attr == &dev_attr_idle.attr ||
+	     attr == &dev_attr_idle_choices.attr) &&
+	    bitmap_empty(handler->idle_choices, HINT_IDLE_LAST))
+		return 0;
+
+	return attr->mode;
+}
+
+static struct attribute *hint_attrs[] = {
+	&dev_attr_name.attr,
+	&dev_attr_idle_choices.attr,
+	&dev_attr_idle.attr,
+	NULL
+};
+
+static const struct attribute_group hint_group = {
+	.attrs      = hint_attrs,
+	.is_visible = hint_attr_is_visible,
+};
+
+static const struct attribute_group *hint_groups[] = {
+	&hint_group,
+	NULL,
+};
+
+static void hint_device_release(struct device *dev)
+{
+	struct hint_handler *handler = to_hint_handler(dev);
+
+	kfree(handler);
+}
+
+static const struct class hint_class = {
+	.name = "hint",
+	.dev_groups = hint_groups,
+	.dev_release = hint_device_release,
+};
+
+/**
+ * hint_register - Creates and registers a hint class device
+ * @dev: Parent device
+ * @name: Name of the class device
+ * @drvdata: Driver data that will be attached to the class device
+ * @ops: Hint probes and getters/setters
+ *
+ * Return: pointer to the new class device on success, ERR_PTR on failure
+ */
+struct device *hint_register(struct device *dev, const char *name,
+			     void *drvdata,
+			     const struct hint_ops *ops)
+{
+	struct device *adev;
+	int minor, err;
+
+	/* Sanity check */
+	if (WARN_ON_ONCE(!dev || !name || !ops))
+		return ERR_PTR(-EINVAL);
+
+	struct hint_handler *handler __free(kfree) = kzalloc_obj(*handler);
+	if (!handler)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * Hint probes
+	 */
+
+	if (ops->idle_probe) {
+		err = ops->idle_probe(drvdata, handler->idle_choices);
+		if (err) {
+			dev_err(dev, "idle state hint probe failed\n");
+			return ERR_PTR(err);
+		}
+		handler->idle =
+			find_first_bit(handler->idle_choices, HINT_IDLE_LAST);
+	}
+
+	/* create class interface for handler */
+	handler->name = name;
+	handler->ops = ops;
+	handler->minor = minor;
+	handler->dev.class = &hint_class;
+	handler->dev.parent = dev;
+	mutex_init(&handler->lock);
+	dev_set_drvdata(&handler->dev, drvdata);
+	dev_set_name(&handler->dev, name, handler->minor);
+
+	adev = &no_free_ptr(handler)->dev;
+	err = device_register(adev);
+	if (err) {
+		put_device(adev);
+		return ERR_PTR(err);
+	}
+
+	return adev;
+}
+EXPORT_SYMBOL_GPL(hint_register);
+
+/**
+ * hint_remove - Unregisters a hint class device
+ * @dev: Class device
+ */
+void hint_remove(struct device *dev)
+{
+	struct hint_handler *handler;
+
+	if (IS_ERR_OR_NULL(dev))
+		return;
+
+	handler = to_hint_handler(dev);
+
+	guard(mutex)(&handler->lock);
+
+	device_unregister(&handler->dev);
+}
+EXPORT_SYMBOL_GPL(hint_remove);
+
+static void devm_hint_release(struct device *dev, void *res)
+{
+	struct device **adev = res;
+
+	hint_remove(*adev);
+}
+
+/**
+ * devm_hint_register - Device managed version of hint_register
+ * @dev: Parent device
+ * @name: Name of the class device
+ * @drvdata: Driver data that will be attached to the class device
+ * @ops: Activity operations
+ *
+ * Return: pointer to the new class device on success, ERR_PTR on failure
+ */
+struct device *devm_hint_register(struct device *dev, const char *name,
+				  void *drvdata, const struct hint_ops *ops)
+{
+	struct device *adev;
+	struct device **dr;
+
+	dr = devres_alloc(devm_hint_release, sizeof(*dr), GFP_KERNEL);
+	if (!dr)
+		return ERR_PTR(-ENOMEM);
+
+	adev = hint_register(dev, name, drvdata, ops);
+	if (IS_ERR(adev)) {
+		devres_free(dr);
+		return adev;
+	}
+
+	*dr = adev;
+	devres_add(dev, dr);
+
+	return adev;
+}
+EXPORT_SYMBOL_GPL(devm_hint_register);
+
+static int __init hint_init(void)
+{
+	return class_register(&hint_class);
+}
+
+/*
+ * Required for s2idle to be able to register hints.
+ * module_init() would run after it tries to register the device.
+ */
+postcore_initcall(hint_init);
+
+MODULE_AUTHOR("Antheas Kapenekakis <lkml@antheas.dev>");
+MODULE_DESCRIPTION("Activity sysfs interface");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/hint.h b/include/linux/hint.h
new file mode 100644
index 000000000000..56686691442c
--- /dev/null
+++ b/include/linux/hint.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Device hints sysfs interface
+ */
+
+#ifndef _HINT_H_
+#define _HINT_H_
+
+#include <linux/device.h>
+#include <linux/bitops.h>
+
+enum hint_idle_option {
+	HINT_IDLE_ACTIVE,
+	HINT_IDLE_INACTIVE,
+	HINT_IDLE_SNOOZE,
+	HINT_IDLE_RESUME,
+	HINT_IDLE_LAST, /*must always be last */
+};
+
+/**
+ * struct hint_ops - hint probes and get/set operations
+ * @idle_probe:	Callback to setup idle hints available to the device.
+ * @idle_get:	Will be called when showing the current idle hint in sysfs.
+ * @idle_set:	Will be called when storing a new idle hint in sysfs.
+ */
+struct hint_ops {
+	int (*idle_probe)(void *drvdata, unsigned long *choices);
+	int (*idle_get)(struct device *dev, enum hint_idle_option *idle);
+	int (*idle_set)(struct device *dev, enum hint_idle_option idle);
+};
+
+struct device *hint_register(struct device *dev, const char *name,
+			     void *drvdata, const struct hint_ops *ops);
+void hint_remove(struct device *dev);
+struct device *devm_hint_register(struct device *dev, const char *name,
+				  void *drvdata, const struct hint_ops *ops);
+
+#endif  /*_HINT_H_*/
-- 
2.53.0



  parent reply	other threads:[~2026-04-25 21:57 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-25 21:57 [RFC v2 00/10] acpi/x86: s2idle: Introduce and implement hint class ABI and idle hint for s2idle Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 01/10] acpi/x86: s2idle: Rename LPS0 constants so they mirror their function Antheas Kapenekakis
2026-04-26 14:14   ` Rafael J. Wysocki
2026-04-26 16:54     ` Antheas Kapenekakis
2026-04-27 15:07       ` Rafael J. Wysocki
2026-04-25 21:57 ` [RFC v2 02/10] acpi/x86: s2idle: Move Modern Standby calls to s2idle begin/end Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 03/10] acpi/x86: s2idle: Add support for adding a delay after begin MS calls Antheas Kapenekakis
2026-04-28  1:57   ` Mario Limonciello
2026-04-28  7:47     ` Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 04/10] platform/x86: asus-wmi: add s2idle begin delay for Ally devices Antheas Kapenekakis
2026-04-28  1:56   ` Mario Limonciello
2026-04-28  6:34   ` [systemd-devel] " Paul Menzel
2026-04-28  8:18     ` Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 05/10] HID: asus: remove quirk handling " Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 06/10] platform/x86: asus-wmi: Remove Ally s2idle resume fixes Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 07/10] Documentation: Add documentation for the new sysfs hints class Antheas Kapenekakis
2026-04-25 21:57 ` Antheas Kapenekakis [this message]
2026-04-25 21:57 ` [RFC v2 09/10] acpi/x86: s2idle: Listen to idle hints to perform MS transitions Antheas Kapenekakis
2026-04-25 21:57 ` [RFC v2 10/10] acpi/x86: s2idle: Subtract delay from last DSM fire in begin delay Antheas Kapenekakis

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260425215734.14116-9-lkml@antheas.dev \
    --to=lkml@antheas.dev \
    --cc=bob.beckett@collabora.com \
    --cc=bookeldor@gmail.com \
    --cc=dmitry.osipenko@collabora.com \
    --cc=hadess@hadess.net \
    --cc=jaap@haitsma.org \
    --cc=kernel@collabora.com \
    --cc=lennart@poettering.net \
    --cc=linux-acpi@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mccann@jhu.edu \
    --cc=rafael@kernel.org \
    --cc=richard@hughsie.com \
    --cc=sebastian.reichel@collabora.com \
    --cc=superm1@kernel.org \
    --cc=systemd-devel@lists.freedesktop.org \
    --cc=xaver.hugl@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox