// 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/-/predator_call * * write: " " e.g. "20 0300ff00010000ff0302000000000000" * read : "ok buf " | "ok int " | "ok none" | "err " * * 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 #include #include #include #if __has_include() #include #endif #include #include #include #include #include #include #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");