Initial release of Predator Control 1.0.0
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>
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
// 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");
|
||||
Reference in New Issue
Block a user