@@ -49,7 +49,8 @@ acpi-y += acpi_apd.o
acpi-y += acpi_platform.o
acpi-y += acpi_pnp.o
acpi-y += power.o
-acpi-y += event.o
+acpi-y += acpi_genl.o
+acpi-y += acpi_genl_dsm.o
acpi-y += evged.o
acpi-y += sysfs.o
acpi-y += property.o
similarity index 63%
rename from drivers/acpi/event.c
rename to drivers/acpi/acpi_genl.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * event.c - exporting ACPI events via procfs
+ * acpi_genl.c - accessing ACPI events and _DSM functions via netlink
*
* Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
* Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
@@ -50,7 +50,11 @@ int unregister_acpi_notifier(struct notifier_block *nb)
EXPORT_SYMBOL(unregister_acpi_notifier);
#ifdef CONFIG_NET
+
+int acpi_genl_dsm_invoke(struct sk_buff *skb, struct genl_info *info);
+
static unsigned int acpi_event_seqnum;
+
struct acpi_genl_event {
acpi_device_class device_class;
char bus_id[15];
@@ -59,17 +63,19 @@ struct acpi_genl_event {
};
/* attributes of acpi_genl_family */
-enum {
+enum acpi_genl_attr {
ACPI_GENL_ATTR_UNSPEC,
ACPI_GENL_ATTR_EVENT, /* ACPI event info needed by user space */
+ ACPI_GENL_ATTR_DSM, /* User space _DSM execution requests */
__ACPI_GENL_ATTR_MAX,
};
#define ACPI_GENL_ATTR_MAX (__ACPI_GENL_ATTR_MAX - 1)
/* commands supported by the acpi_genl_family */
-enum {
+enum acpi_genl_cmd {
ACPI_GENL_CMD_UNSPEC,
ACPI_GENL_CMD_EVENT, /* kernel->user notifications for ACPI events */
+ ACPI_GENL_CMD_DSM, /* use<->kernel channel for _DSM execution */
__ACPI_GENL_CMD_MAX,
};
#define ACPI_GENL_CMD_MAX (__ACPI_GENL_CMD_MAX - 1)
@@ -78,6 +84,19 @@ enum {
#define ACPI_GENL_VERSION 0x01
#define ACPI_GENL_MCAST_GROUP_NAME "acpi_mc_group"
+
+static const struct nla_policy acpi_nla_policy[__ACPI_GENL_ATTR_MAX + 1] = {
+ [ACPI_GENL_ATTR_DSM] = {.type = NLA_BINARY},
+};
+
+static const struct genl_ops acpi_genl_ops[] = {
+ {
+ .cmd = ACPI_GENL_CMD_DSM,
+ .policy = acpi_nla_policy,
+ .doit = acpi_genl_dsm_invoke,
+ },
+};
+
static const struct genl_multicast_group acpi_event_mcgrps[] = {
{ .name = ACPI_GENL_MCAST_GROUP_NAME, },
};
@@ -87,6 +106,8 @@ static struct genl_family acpi_event_genl_family __ro_after_init = {
.name = ACPI_GENL_FAMILY_NAME,
.version = ACPI_GENL_VERSION,
.maxattr = ACPI_GENL_ATTR_MAX,
+ .ops = acpi_genl_ops,
+ .n_ops = ARRAY_SIZE(acpi_genl_ops),
.mcgrps = acpi_event_mcgrps,
.n_mcgrps = ARRAY_SIZE(acpi_event_mcgrps),
};
@@ -144,11 +165,92 @@ int acpi_bus_generate_netlink_event(const char *device_class,
EXPORT_SYMBOL(acpi_bus_generate_netlink_event);
+int acpi_genl_dsm_invoke(struct sk_buff *skb, struct genl_info *info)
+{
+ int ret = 0;
+ struct acpi_genl_dsm_id *dsm_arg = NULL, *dsm_ret = NULL;
+ u16 dsm_arg_len = 0;
+ struct acpi_genl_dsm_handle *handle;
+ struct sk_buff *reply_skb;
+ struct nlattr *attr;
+ void *hdr;
+
+ if (!info->attrs[ACPI_GENL_ATTR_DSM]) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dsm_arg = (struct acpi_genl_dsm_id *)
+ nla_data(info->attrs[ACPI_GENL_ATTR_DSM]);
+
+ /* Get the handle for the requested _DSM method. */
+ handle = acpi_genl_dsm_get_handle(dsm_arg);
+ if (!handle) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Make sure massage length matches the _DSM method argument size
+ * specified in the _DSM handle registered by the dirver.
+ */
+ dsm_arg_len = nla_len(info->attrs[ACPI_GENL_ATTR_DSM]);
+ if (dsm_arg_len != handle->arg_len) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Make sure the user-space caller has right capabilities to invoke this
+ * _DSM handle.
+ */
+ if (!capable(handle->cap)) {
+ ret = -EACCES;
+ goto out;
+ }
+
+ /*
+ * Build a response of the size specified in the _DSM method handle.
+ */
+ reply_skb = genlmsg_new(handle->ret_len, GFP_ATOMIC);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ hdr = genlmsg_put_reply(reply_skb, info, &acpi_event_genl_family,
+ 0, ACPI_GENL_CMD_DSM);
+ if (!hdr) {
+ nlmsg_free(reply_skb);
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ attr = nla_reserve(reply_skb, ACPI_GENL_ATTR_DSM, handle->ret_len);
+ if (!attr) {
+ nlmsg_free(reply_skb);
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ dsm_ret = nla_data(attr);
+ memset(dsm_ret, 0, handle->ret_len);
+
+ /*
+ * Invoke the _DSM method via the driver provided callback and send
+ * response.
+ */
+ handle->dsm_cb(dsm_arg, dsm_ret);
+ genlmsg_end(reply_skb, hdr);
+ ret = genlmsg_reply(reply_skb, info);
+out:
+ return ret;
+}
+
static int __init acpi_event_genetlink_init(void)
{
return genl_register_family(&acpi_event_genl_family);
}
-
#else
int acpi_bus_generate_netlink_event(const char *device_class,
const char *bus_id,
new file mode 100644
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * acpi_genl_dsm.c - exports ACPI _DSM functions via netlink
+ *
+ * Copyright (C) 2024 Wathsala Vithanage <wathsala.vithanage@arm.com>
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/jhash.h>
+#include <linux/hashtable.h>
+
+#include "internal.h"
+
+#define acpi_genl_dsm_hash_key(id) \
+ jhash2((u32 *)&(id), sizeof((id))/sizeof(u32), 0)
+
+DEFINE_HASHTABLE(acpi_dsm_tbl, 4);
+
+/**
+ * acpi_genl_dsm_add_handle - add netlink handle to invoke _DSM via netlink.
+ * @dsm_handle: _DSM handle
+ *
+ * Add handle to a _DSM method that will be invoked when user space calls it
+ * via netlink.
+ */
+int acpi_genl_dsm_add_handle(struct acpi_genl_dsm_handle *handle)
+{
+ if (!handle)
+ return -EINVAL;
+ u32 id_hash = acpi_genl_dsm_hash_key(handle->id);
+
+ hash_add_rcu(acpi_dsm_tbl, &handle->entry, id_hash);
+ return 0;
+}
+EXPORT_SYMBOL(acpi_genl_dsm_add_handle);
+
+/**
+ * acpi_genl_dsm_get_handle - find netlink handle to a _DSM method.
+ * @id: _DSM identifier
+ *
+ * Find netlink handle to _DSM method by _DSM identifier.
+ */
+struct acpi_genl_dsm_handle *
+acpi_genl_dsm_get_handle(const struct acpi_genl_dsm_id *id)
+{
+ struct acpi_genl_dsm_handle *cur_obj;
+
+ if (!id)
+ return NULL;
+ u32 id_hash = acpi_genl_dsm_hash_key(*id);
+
+ hash_for_each_possible_rcu(acpi_dsm_tbl, cur_obj, entry, id_hash) {
+ if (!memcmp(cur_obj, id, sizeof(struct acpi_genl_dsm_id)))
+ return cur_obj;
+ }
+ return NULL;
+}
+EXPORT_SYMBOL(acpi_genl_dsm_get_handle);
+
+/**
+ * acpi_genl_dsm_del_handle - remove netlink handler to a _DSM method.
+ * @id: _DSM identifier
+ *
+ * Remove netlink handler to a _DSM method by _DSM identifier.
+ */
+int acpi_genl_dsm_del_handle(const struct acpi_genl_dsm_id *id)
+{
+ if (!id)
+ return -EINVAL;
+ struct acpi_genl_dsm_handle *handle = acpi_genl_dsm_get_handle(id);
+
+ if (!handle)
+ return -ENOENT;
+ hash_del_rcu(&handle->entry);
+ return 0;
+}
+EXPORT_SYMBOL(acpi_genl_dsm_del_handle);
+
new file mode 100644
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * acpi_genl_dsm.c - exports ACPI _DSM functions via netlink
+ *
+ * Copyright (C) 2024 Wathsala Vithanage <wathsala.vithanage@arm.com>
+ *
+ */
+
+#ifndef __ACPI_GENL_H__
+#define __ACPI_GENL_H__
+
+/* _DSM method identifier */
+struct acpi_genl_dsm_id {
+ guid_t guid; /* ACPI _DSM GUID */
+ u64 rev; /* _DSM method revision ID */
+ u64 func; /* _DSM method index */
+} __packed;
+
+/* _DSM method handle */
+struct acpi_genl_dsm_handle {
+ struct acpi_genl_dsm_id id; /* Unique _DSM method identifier */
+ u16 arg_len; /* Size of arg passed into dsm_fn */
+ u16 ret_len; /* Size of ret passed into dsm_fn */
+ /*
+ * Callback to invoke the _DSM method.
+ */
+ int (*dsm_cb)(struct acpi_genl_dsm_id *arg,
+ struct acpi_genl_dsm_id *ret);
+ int cap; /* Min cap for invoking _DSM from the user space */
+ struct hlist_node entry;
+};
+
+#ifdef CONFIG_ACPI
+
+int acpi_genl_dsm_add_handle(struct acpi_genl_dsm_handle *handle);
+
+struct acpi_genl_dsm_handle *
+acpi_genl_dsm_get_handle(const struct acpi_genl_dsm_id *id);
+
+int acpi_genl_dsm_del_handle(const struct acpi_genl_dsm_id *id);
+
+#else
+
+int acpi_genl_dsm_add_handle(const struct acpi_genl_dsm_handle *handle)
+{
+ return -ENOTSUP;
+}
+
+struct acpi_genl_dsm_handle *
+acpi_genl_dsm_get_handle(const struct acpi_genl_dsm_id *id)
+{
+ return NULL;
+}
+
+int acpi_genl_dsm_del_handle(struct acpi_genl_dsm_id *id)
+{
+ return -ENOTSUP;
+}
+
+#endif //CONFIG_ACPI
+#endif //__ACPI_GENL_H__
@@ -38,6 +38,7 @@ struct irq_domain_ops;
#include <acpi/acpi_drivers.h>
#include <acpi/acpi_io.h>
#include <asm/acpi.h>
+#include <acpi/acpi_genl.h>
#ifdef CONFIG_ACPI_TABLE_LIB
#define EXPORT_SYMBOL_ACPI_LIB(x) EXPORT_SYMBOL_NS_GPL(x, "ACPI")
Applications that bypass the kernel to access devices can benefit from the ability to execute ACPI _DSM methods from the user space. This commit introduces a generic netlink interface for user-space applications to invoke _DSM methods registered by kernel subsystems for the user space. User space applications invoke these _DSMs by sending an ACPI_GENL_CMD_DSM-typed message to an "acpi-event" GENL family socket. Irrespective of the _DSM being invoked, all messages typed ACPI_GENL_CMD_DSM must be prefixed by a struct acpi_genl_dsm_id header to uniquely identify the target _DSM as specified by the ACPI firmware specification. Reminder of the message is specific to the _DSM being invoked. Data from the _DSM method will be returned to the application as a response with the same message header. In addition, the calling user application needs the minimal capabilities and message lengths (including the header) specified by the kernel subsystem that registers the callback for the _DSM method. The GENL message header for _DSM (acpi_genl_dsm_id header) has the following format. 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + _DSM GUID + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | _DSM REVISION | _DSM FUNCTION INDEX | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signed-off-by: Wathsala Vithanage <wathsala.vithanage@arm.com> --- drivers/acpi/Makefile | 3 +- drivers/acpi/{event.c => acpi_genl.c} | 110 +++++++++++++++++++++++++- drivers/acpi/acpi_genl_dsm.c | 79 ++++++++++++++++++ include/acpi/acpi_genl.h | 61 ++++++++++++++ include/linux/acpi.h | 1 + 5 files changed, 249 insertions(+), 5 deletions(-) rename drivers/acpi/{event.c => acpi_genl.c} (63%) create mode 100644 drivers/acpi/acpi_genl_dsm.c create mode 100644 include/acpi/acpi_genl.h