PredatorSense replacement for the Acer Predator Helios 16 (PH16-71) on Linux: - predatord: root service for profiles, fans (incl. temperature curves), keyboard RGB, lightbar and battery health; restores settings at boot and after resume, switches profiles on AC/battery changes - Qt desktop app with dashboard, fan curve editor and per-key RGB studio - Plasma 6 panel widget with quick controls - predatorctl command line tool - predator_wmi kernel module (DKMS) as a minimal WMI method bridge Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
5.0 KiB
C
203 lines
5.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* predator_wmi -- minimal WMI method bridge for Acer Predator laptops.
|
|
*
|
|
* Binds to the Acer gaming WMI interfaces that mainline acer-wmi does not
|
|
* bind as a driver and exposes one root-only sysfs attribute per device:
|
|
*
|
|
* /sys/bus/wmi/devices/<GUID>-<n>/predator_call
|
|
*
|
|
* write: "<method-id> <hex-input-bytes>" e.g. "20 0300ff00010000ff0302000000000000"
|
|
* read : "ok buf <hex>" | "ok int <hex u64>" | "ok none" | "err <errno>"
|
|
*
|
|
* The read returns the result of the most recent write. All protocol
|
|
* knowledge lives in userspace (predator-control daemon); this module only
|
|
* moves bytes between sysfs and wmidev_evaluate_method().
|
|
*
|
|
* Interfaces (PH16-71):
|
|
* 7A4DDFE7-... WMBH AcerGamingFunction (fans, lightbar, misc settings)
|
|
* 79772EC5-... WMBE Battery health (80 % limit, calibration)
|
|
* 61EF69EA-... WMAA APGE action (USB charging, backlight timeout)
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/device.h>
|
|
#include <linux/dmi.h>
|
|
#if __has_include(<linux/hex.h>)
|
|
#include <linux/hex.h>
|
|
#endif
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/wmi.h>
|
|
|
|
#define PWMI_MAX_IN 64
|
|
#define PWMI_MAX_OUT 256
|
|
|
|
struct pwmi_dev {
|
|
struct wmi_device *wdev;
|
|
struct mutex lock;
|
|
int last_err;
|
|
u8 last_type; /* 0 none, 1 buffer, 2 integer */
|
|
size_t last_len;
|
|
u8 last_out[PWMI_MAX_OUT];
|
|
u64 last_int;
|
|
};
|
|
|
|
static ssize_t predator_call_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct pwmi_dev *p = dev_get_drvdata(dev);
|
|
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
struct acpi_buffer in;
|
|
union acpi_object *obj;
|
|
u8 inbuf[PWMI_MAX_IN];
|
|
const char *s = buf, *end = buf + count;
|
|
unsigned int method = 0;
|
|
size_t n = 0;
|
|
acpi_status status;
|
|
int hi, lo;
|
|
|
|
/* method id (decimal) */
|
|
while (s < end && isspace(*s))
|
|
s++;
|
|
if (s >= end || !isdigit(*s))
|
|
return -EINVAL;
|
|
while (s < end && isdigit(*s)) {
|
|
method = method * 10 + (*s - '0');
|
|
if (method > 0xffff)
|
|
return -EINVAL;
|
|
s++;
|
|
}
|
|
while (s < end && isspace(*s))
|
|
s++;
|
|
|
|
/* hex payload */
|
|
while (s + 1 < end && !isspace(*s)) {
|
|
hi = hex_to_bin(s[0]);
|
|
lo = hex_to_bin(s[1]);
|
|
if (hi < 0 || lo < 0 || n >= PWMI_MAX_IN)
|
|
return -EINVAL;
|
|
inbuf[n++] = (hi << 4) | lo;
|
|
s += 2;
|
|
}
|
|
while (s < end && isspace(*s))
|
|
s++;
|
|
if (s != end)
|
|
return -EINVAL;
|
|
|
|
in.length = n;
|
|
in.pointer = inbuf;
|
|
|
|
mutex_lock(&p->lock);
|
|
p->last_type = 0;
|
|
p->last_len = 0;
|
|
p->last_err = 0;
|
|
|
|
status = wmidev_evaluate_method(p->wdev, 0, method, &in, &out);
|
|
if (ACPI_FAILURE(status)) {
|
|
p->last_err = -EIO;
|
|
goto unlock;
|
|
}
|
|
|
|
obj = out.pointer;
|
|
if (obj && obj->type == ACPI_TYPE_BUFFER) {
|
|
p->last_type = 1;
|
|
p->last_len = min_t(size_t, obj->buffer.length, PWMI_MAX_OUT);
|
|
memcpy(p->last_out, obj->buffer.pointer, p->last_len);
|
|
} else if (obj && obj->type == ACPI_TYPE_INTEGER) {
|
|
p->last_type = 2;
|
|
p->last_int = obj->integer.value;
|
|
}
|
|
kfree(out.pointer);
|
|
unlock:
|
|
mutex_unlock(&p->lock);
|
|
return p->last_err ? p->last_err : count;
|
|
}
|
|
|
|
static ssize_t predator_call_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct pwmi_dev *p = dev_get_drvdata(dev);
|
|
ssize_t len;
|
|
|
|
mutex_lock(&p->lock);
|
|
if (p->last_err)
|
|
len = sysfs_emit(buf, "err %d\n", p->last_err);
|
|
else if (p->last_type == 1)
|
|
len = sysfs_emit(buf, "ok buf %*phN\n", (int)p->last_len,
|
|
p->last_out);
|
|
else if (p->last_type == 2)
|
|
len = sysfs_emit(buf, "ok int %016llx\n", p->last_int);
|
|
else
|
|
len = sysfs_emit(buf, "ok none\n");
|
|
mutex_unlock(&p->lock);
|
|
return len;
|
|
}
|
|
static DEVICE_ATTR_ADMIN_RW(predator_call);
|
|
|
|
static struct attribute *pwmi_attrs[] = {
|
|
&dev_attr_predator_call.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(pwmi);
|
|
|
|
static int pwmi_probe(struct wmi_device *wdev, const void *ctx)
|
|
{
|
|
struct pwmi_dev *p;
|
|
|
|
p = devm_kzalloc(&wdev->dev, sizeof(*p), GFP_KERNEL);
|
|
if (!p)
|
|
return -ENOMEM;
|
|
p->wdev = wdev;
|
|
mutex_init(&p->lock);
|
|
dev_set_drvdata(&wdev->dev, p);
|
|
return 0;
|
|
}
|
|
|
|
static const struct wmi_device_id pwmi_id_table[] = {
|
|
{ .guid_string = "7A4DDFE7-5B5D-40B4-8595-4408E0CC7F56" },
|
|
{ .guid_string = "79772EC5-04B1-4BFD-843C-61E7F77B6CC9" },
|
|
{ .guid_string = "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(wmi, pwmi_id_table);
|
|
|
|
static struct wmi_driver pwmi_driver = {
|
|
.driver = {
|
|
.name = "predator_wmi",
|
|
.dev_groups = pwmi_groups,
|
|
},
|
|
.id_table = pwmi_id_table,
|
|
.probe = pwmi_probe,
|
|
.no_singleton = true,
|
|
};
|
|
|
|
static const struct dmi_system_id pwmi_dmi[] = {
|
|
{ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Acer") } },
|
|
{ }
|
|
};
|
|
|
|
static int __init pwmi_init(void)
|
|
{
|
|
if (!dmi_check_system(pwmi_dmi))
|
|
return -ENODEV;
|
|
return wmi_driver_register(&pwmi_driver);
|
|
}
|
|
|
|
static void __exit pwmi_exit(void)
|
|
{
|
|
wmi_driver_unregister(&pwmi_driver);
|
|
}
|
|
|
|
module_init(pwmi_init);
|
|
module_exit(pwmi_exit);
|
|
|
|
MODULE_AUTHOR("predator-control");
|
|
MODULE_DESCRIPTION("WMI method bridge for Acer Predator laptops");
|
|
MODULE_LICENSE("GPL");
|