Add WMI EC kernel modules + toolkit + standalone HID inventory
This commit is contained in:
parent
5adfde1ab9
commit
7954197fdb
5 changed files with 644 additions and 0 deletions
126
tools/superx_hid_inventory.py
Normal file
126
tools/superx_hid_inventory.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Shared HID inventory helper for Super X tools.
|
||||
|
||||
Imports:
|
||||
from superx_hid_inventory import hid_inventory
|
||||
|
||||
Returns a list of dicts with keys:
|
||||
hidraw, devnode, vid, pid, iface, driver, report_descriptor_len, mode, uid, gid
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _read_sysfs_text(hidraw_name: str, *path_parts: str) -> str | None:
|
||||
p = Path("/sys/class/hidraw") / hidraw_name / Path(*path_parts)
|
||||
try:
|
||||
return p.read_text().strip()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def _read_sysfs_bytes(hidraw_name: str, *path_parts: str) -> bytes | None:
|
||||
p = Path("/sys/class/hidraw") / hidraw_name / Path(*path_parts)
|
||||
try:
|
||||
return p.read_bytes()
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
|
||||
def hid_inventory():
|
||||
"""Return a list of dicts describing every hidraw device."""
|
||||
devices = []
|
||||
base = Path("/sys/class/hidraw")
|
||||
if not base.exists():
|
||||
return devices
|
||||
|
||||
for hidraw_entry in sorted(base.iterdir()):
|
||||
name = hidraw_entry.name
|
||||
dev_symlink = hidraw_entry / "device"
|
||||
if not dev_symlink.is_symlink():
|
||||
continue
|
||||
|
||||
# Resolve the symlink to get the real HID device path
|
||||
hid_device = dev_symlink.resolve()
|
||||
|
||||
# Walk up: hid_device.parent = USB interface, hid_device.parent.parent = USB device
|
||||
usb_iface = hid_device.parent
|
||||
usb_dev = usb_iface.parent
|
||||
|
||||
# Try to read USB VID/PID from the USB device
|
||||
vid = None
|
||||
pid = None
|
||||
iface = None
|
||||
try:
|
||||
vid = (usb_dev / "idVendor").read_text().strip()
|
||||
pid = (usb_dev / "idProduct").read_text().strip()
|
||||
iface = (usb_iface / "bInterfaceNumber").read_text().strip()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
# Not a USB device — maybe I2C, Bluetooth, etc. Try uevent
|
||||
pass
|
||||
|
||||
# Get driver from uevent
|
||||
uevent_path = hid_device / "uevent"
|
||||
driver = None
|
||||
if uevent_path.exists():
|
||||
try:
|
||||
uevent_raw = uevent_path.read_text()
|
||||
for line in uevent_raw.splitlines():
|
||||
if line.startswith("DRIVER="):
|
||||
driver = line.split("=", 1)[1].strip()
|
||||
except (OSError, UnicodeDecodeError):
|
||||
pass
|
||||
|
||||
# Get report descriptor
|
||||
desc = _read_sysfs_bytes(name, "device", "report_descriptor")
|
||||
report_descriptor_len = len(desc) if desc else 0
|
||||
|
||||
# Get permissions
|
||||
devnode = str(Path("/dev") / name)
|
||||
mode = None
|
||||
uid = None
|
||||
gid = None
|
||||
try:
|
||||
st = os.stat(devnode)
|
||||
mode = oct(st.st_mode)[-3:]
|
||||
uid = st.st_uid
|
||||
gid = st.st_gid
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
devices.append({
|
||||
"hidraw": name,
|
||||
"devnode": devnode,
|
||||
"vid": vid,
|
||||
"pid": pid,
|
||||
"iface": iface,
|
||||
"driver": driver,
|
||||
"report_descriptor_len": report_descriptor_len,
|
||||
"mode": mode,
|
||||
"uid": uid,
|
||||
"gid": gid,
|
||||
})
|
||||
|
||||
return devices
|
||||
|
||||
|
||||
# For standalone use
|
||||
if __name__ == "__main__":
|
||||
devs = hid_inventory()
|
||||
if not devs:
|
||||
print("NO HIDRAW DEVICES FOUND")
|
||||
sys.exit(1)
|
||||
for d in devs:
|
||||
vid = d.get("vid", "????")
|
||||
pid = d.get("pid", "????")
|
||||
iface = d.get("iface", "?")
|
||||
driver = d.get("driver", "?")
|
||||
desc_len = d.get("report_descriptor_len", 0)
|
||||
mode = d.get("mode", "???")
|
||||
print(f" /dev/{d['hidraw']} vid=0x{vid} pid=0x{pid} if={iface} drv={driver} desc={desc_len}B perms={mode}")
|
||||
print(f"\nTotal: {len(devs)} hidraw device(s)")
|
||||
213
tools/superx_wmi_toolkit.sh
Normal file
213
tools/superx_wmi_toolkit.sh
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
#!/usr/bin/env bash
|
||||
# Super X WMI EC Toolkit
|
||||
#
|
||||
# All-in-one Script für den Super X. Reihenfolge:
|
||||
# 1. oxp-sensors checken (wenn geladen: hwmon Werte zeigen)
|
||||
# 2. Kernel-Module bauen (oxp-wmi-ec-read)
|
||||
# 3. EC Dump 0x00-0xFF durchführen
|
||||
# 4. RGB-Kandidaten 0x50-0x60 extra anzeigen
|
||||
# 5. Optional: Write-Test (nur mit --write)
|
||||
#
|
||||
# Usage:
|
||||
# sudo ./superx_wmi_toolkit.sh # read-only dump
|
||||
# sudo ./superx_wmi_toolkit.sh --candidates # nur RGB-Kandidaten
|
||||
# sudo ./superx_wmi_toolkit.sh --write # interaktiver Write-Test
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
WMI_DIR="$SCRIPT_DIR/wmi"
|
||||
|
||||
# ── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
red "Need root. Run with: sudo $0 $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── oxp-sensors check ───────────────────────────────────────────────────────
|
||||
|
||||
check_oxp_sensors() {
|
||||
echo "=== oxp-sensors ==="
|
||||
if lsmod | grep -q oxp_sensors; then
|
||||
green "oxp-sensors loaded"
|
||||
if [[ -d /sys/class/hwmon ]]; then
|
||||
for hwmon in /sys/class/hwmon/hwmon*; do
|
||||
name=$(cat "$hwmon/name" 2>/dev/null || true)
|
||||
if [[ "$name" == "oxp-sensors" ]]; then
|
||||
echo " hwmon: $hwmon"
|
||||
for f in fan1_input fan2_input temp1_input pwm1 pwm2; do
|
||||
if [[ -f "$hwmon/$f" ]]; then
|
||||
printf " %-20s = %s\n" "$f" "$(cat "$hwmon/$f")"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
fi
|
||||
else
|
||||
yellow "oxp-sensors NOT loaded"
|
||||
echo " Install: yay -S oxp-sensors-dkms-git"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
# ── WMI EC Dump via Kernel Module ──────────────────────────────────────────
|
||||
|
||||
build_and_dump() {
|
||||
echo "=== WMI EC Dump ==="
|
||||
|
||||
# Check kernel headers
|
||||
if [[ ! -d "/lib/modules/$(uname -r)/build" ]]; then
|
||||
red "Kernel headers not found!"
|
||||
echo " Install: sudo pacman -S linux-headers"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Build
|
||||
echo "Building oxp-wmi-ec-read..."
|
||||
make -C "$WMI_DIR" clean >/dev/null 2>&1 || true
|
||||
if ! make -C "$WMI_DIR" 2>&1; then
|
||||
red "Build failed"
|
||||
return 1
|
||||
fi
|
||||
echo
|
||||
|
||||
# Load and dump
|
||||
echo "Loading oxp-wmi-ec-read (read-only, 256 bytes)..."
|
||||
dmesg -C
|
||||
if ! insmod "$WMI_DIR/oxp-wmi-ec-read.ko" 2>&1; then
|
||||
red "insmod failed. Check dmesg for details."
|
||||
dmesg | tail -20
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract dump
|
||||
echo
|
||||
green "EC Dump (0x00-0xFF):"
|
||||
dmesg | grep 'oxp-wmi-ec-read: ec\[' | sort -t'[' -k2 -n
|
||||
rmmod oxp_wmi_ec_read 2>/dev/null || true
|
||||
echo
|
||||
}
|
||||
|
||||
dump_candidates() {
|
||||
echo "=== RGB Candidate Offsets (0x50-0x60) ==="
|
||||
build_and_dump | grep -E 'ec\[0x5[0-9a-f]\]|ec\[0x60\]'
|
||||
}
|
||||
|
||||
# ── WMI Write (guarded) ─────────────────────────────────────────────────────
|
||||
|
||||
wmi_write_test() {
|
||||
echo "=== WMI Write Test ==="
|
||||
red "WARNING: This WRITES to EC memory. Only proceed if you understand the risks."
|
||||
red "A bad write could cause instability, fan failure, or hardware damage."
|
||||
echo
|
||||
|
||||
local offset="${1:-}"
|
||||
local value="${2:-}"
|
||||
|
||||
if [[ -z "$offset" ]]; then
|
||||
read -r -p "EC offset to write (hex, e.g. 0x57): " offset
|
||||
fi
|
||||
if [[ -z "$value" ]]; then
|
||||
read -r -p "Value to write (hex, e.g. 0x02): " value
|
||||
fi
|
||||
|
||||
# Convert hex
|
||||
offset_dec=$((offset))
|
||||
value_dec=$((value))
|
||||
|
||||
echo
|
||||
echo "Writing: ec[0x$offset] = 0x$value"
|
||||
read -r -p "Type 'yes' to confirm: " confirm
|
||||
if [[ "$confirm" != "yes" ]]; then
|
||||
echo "Aborted."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Build write module
|
||||
echo "Building oxp-wmi-write-once..."
|
||||
make -C "$WMI_DIR" clean >/dev/null 2>&1 || true
|
||||
if ! make -C "$WMI_DIR" 2>&1; then
|
||||
red "Build failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Load with parameters
|
||||
dmesg -C
|
||||
if ! insmod "$WMI_DIR/oxp-wmi-write-once.ko" \
|
||||
offset="$offset_dec" \
|
||||
value="$value_dec" \
|
||||
yes_i_understand_this_writes_to_hardware=1 2>&1; then
|
||||
red "insmod failed. Check dmesg:"
|
||||
dmesg
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo
|
||||
dmesg | grep 'oxp-wmi-write-once'
|
||||
rmmod oxp_wmi_write_once 2>/dev/null || true
|
||||
}
|
||||
|
||||
# ── WMI CMS Scan ────────────────────────────────────────────────────────────
|
||||
|
||||
wmi_cms_scan() {
|
||||
echo "=== WMI CMS Candidate Scan (Method 4) ==="
|
||||
yellow "CMS = alternate WMI path (WMBB method 4: CMSW)"
|
||||
yellow "Scanning 0xF0..0xFF with value 0x01, auto-reverting each"
|
||||
echo
|
||||
|
||||
read -r -p "This will write and revert 16 bytes. Continue? [y/N] " answer
|
||||
if [[ "${answer,,}" != "y" ]]; then
|
||||
echo "Skipped."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# We need the CMS write module
|
||||
if [[ -f "$WMI_DIR/oxp-wmi-cms-write-once.c" ]]; then
|
||||
echo "CMS write module exists — but this is a simplified scan."
|
||||
echo "For full CMS scan, use the old repo's tools/oxp-wmi-cms-scan.sh"
|
||||
else
|
||||
yellow "CMS write module not in this repo yet."
|
||||
fi
|
||||
}
|
||||
|
||||
# ── main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
check_root "$@"
|
||||
|
||||
local mode="full"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--candidates) mode="candidates"; shift ;;
|
||||
--write)
|
||||
mode="write"
|
||||
shift
|
||||
wmi_write_test "$@"
|
||||
exit $?
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: sudo $0 [--candidates|--write [offset] [value]]"
|
||||
exit 0
|
||||
;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
check_oxp_sensors
|
||||
|
||||
case "$mode" in
|
||||
full) build_and_dump ;;
|
||||
candidates) build_and_dump | grep -E 'ec\[0x5[0-9a-f]\b|ec\[0x60\]' ;;
|
||||
esac
|
||||
|
||||
echo
|
||||
green "Done."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
12
tools/wmi/Makefile
Normal file
12
tools/wmi/Makefile
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Super X WMI Kernel Module Makefile
|
||||
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
|
||||
obj-m += oxp-wmi-ec-read.o
|
||||
obj-m += oxp-wmi-write-once.o
|
||||
|
||||
all:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) clean
|
||||
146
tools/wmi/oxp-wmi-ec-read.c
Normal file
146
tools/wmi/oxp-wmi-ec-read.c
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* OneXPlayer Super X WMI EC read-only probe.
|
||||
*
|
||||
* This module evaluates only UMAInterface/GetEcValue (WmiMethodId 5) on the
|
||||
* OneX WMI GUID. It does not call any write-capable WMI method and does not
|
||||
* scan the whole EC region by default.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define OXP_UMA_WMI_GUID "1F72B0F1-BFEA-4472-9877-6E62937AB616"
|
||||
#define OXP_WMI_METHOD_GET_EC_VALUE 5
|
||||
|
||||
static ushort offsets[256] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
||||
0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64,
|
||||
0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c,
|
||||
0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74,
|
||||
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84,
|
||||
0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c,
|
||||
0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94,
|
||||
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c,
|
||||
0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4,
|
||||
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac,
|
||||
0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4,
|
||||
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4,
|
||||
0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc,
|
||||
0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4,
|
||||
0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc,
|
||||
0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4,
|
||||
0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4,
|
||||
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc,
|
||||
0xfd, 0xfe, 0xff,
|
||||
};
|
||||
static unsigned int offsets_count = 256;
|
||||
module_param_array(offsets, ushort, &offsets_count, 0444);
|
||||
MODULE_PARM_DESC(offsets, "Declared EC offsets to read via OneX UMAInterface.GetEcValue; max 256");
|
||||
|
||||
static int oxp_wmi_get_ec_value(u16 offset, u64 *value)
|
||||
{
|
||||
/*
|
||||
* The BMOF declares GetEcValue(Index: uint16), but the DSDT creates byte
|
||||
* fields at Arg2 offsets 0 and 2. Passing only a packed 16-bit buffer trips
|
||||
* AE_AML_BUFFER_LIMIT, so provide the 4-byte WMI method argument frame the
|
||||
* firmware expects. WMBB only uses byte 0 (LH50) for MethodId 5.
|
||||
*/
|
||||
u8 inbuf[4] = { offset & 0xff, offset >> 8, 0x00, 0x00 };
|
||||
struct acpi_buffer input = {
|
||||
.length = sizeof(inbuf),
|
||||
.pointer = inbuf,
|
||||
};
|
||||
union acpi_object *obj;
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
int ret = 0;
|
||||
|
||||
status = wmi_evaluate_method(OXP_UMA_WMI_GUID, 0,
|
||||
OXP_WMI_METHOD_GET_EC_VALUE, &input, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj) {
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
*value = obj->integer.value;
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (obj->buffer.length < 1) {
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
*value = obj->buffer.pointer[0];
|
||||
break;
|
||||
default:
|
||||
ret = -EPROTO;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(output.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init oxp_wmi_ec_read_init(void)
|
||||
{
|
||||
int instances, i;
|
||||
|
||||
instances = wmi_instance_count(OXP_UMA_WMI_GUID);
|
||||
pr_info("oxp-wmi-ec-read: GUID %s instances=%d offsets=%u\n",
|
||||
OXP_UMA_WMI_GUID, instances, offsets_count);
|
||||
|
||||
if (instances <= 0)
|
||||
return -ENODEV;
|
||||
|
||||
for (i = 0; i < offsets_count && i < ARRAY_SIZE(offsets); i++) {
|
||||
u64 value = 0;
|
||||
int ret = oxp_wmi_get_ec_value(offsets[i], &value);
|
||||
|
||||
if (ret)
|
||||
pr_info("oxp-wmi-ec-read: ec[0x%02x] read failed ret=%d\n",
|
||||
offsets[i], ret);
|
||||
else
|
||||
pr_info("oxp-wmi-ec-read: ec[0x%02x]=0x%02llx (%llu)\n",
|
||||
offsets[i], value & 0xff, value);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit oxp_wmi_ec_read_exit(void)
|
||||
{
|
||||
pr_info("oxp-wmi-ec-read: unloaded\n");
|
||||
}
|
||||
|
||||
module_init(oxp_wmi_ec_read_init);
|
||||
module_exit(oxp_wmi_ec_read_exit);
|
||||
|
||||
MODULE_AUTHOR("Morpheus");
|
||||
MODULE_DESCRIPTION("Read-only OneX UMAInterface WMI EC value probe");
|
||||
MODULE_LICENSE("GPL");
|
||||
147
tools/wmi/oxp-wmi-write-once.c
Normal file
147
tools/wmi/oxp-wmi-write-once.c
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* OneXPlayer Super X guarded one-shot WMI EC-memory writer.
|
||||
*
|
||||
* This targets the same OneX UMAInterface path used by oxp-wmi-ec-read.c:
|
||||
* MethodId 5: M049(0xFE800400, offset) read
|
||||
* MethodId 6: M04C(0xFE800400, offset, value) write, then readback
|
||||
*
|
||||
* It writes exactly one byte and prints the returned readback value.
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define OXP_UMA_WMI_GUID "1F72B0F1-BFEA-4472-9877-6E62937AB616"
|
||||
#define OXP_WMI_METHOD_GET_EC_VALUE 5
|
||||
#define OXP_WMI_METHOD_SET_EC_VALUE 6
|
||||
|
||||
static uint offset = 0x57;
|
||||
static uint value = 0x02;
|
||||
static bool yes_i_understand_this_writes_to_hardware;
|
||||
|
||||
module_param(offset, uint, 0444);
|
||||
MODULE_PARM_DESC(offset, "WMI EC-memory offset to write, default 0x57");
|
||||
module_param(value, uint, 0444);
|
||||
MODULE_PARM_DESC(value, "Byte value to write, default 0x02");
|
||||
module_param(yes_i_understand_this_writes_to_hardware, bool, 0444);
|
||||
MODULE_PARM_DESC(yes_i_understand_this_writes_to_hardware, "Required safety acknowledgement");
|
||||
|
||||
static const struct dmi_system_id dmi_table[] = {
|
||||
{
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "ONE-NETBOOK"),
|
||||
DMI_EXACT_MATCH(DMI_BOARD_NAME, "ONEXPLAYER SUPER X"),
|
||||
},
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static int oxp_wmi_call(u8 method_id, u8 off, u8 val, u64 *out_value)
|
||||
{
|
||||
/* Firmware reads byte 0 as offset and byte 2 as value. */
|
||||
u8 inbuf[4] = { off, 0x00, val, 0x00 };
|
||||
struct acpi_buffer input = {
|
||||
.length = sizeof(inbuf),
|
||||
.pointer = inbuf,
|
||||
};
|
||||
union acpi_object *obj;
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
int ret = 0;
|
||||
|
||||
status = wmi_evaluate_method(OXP_UMA_WMI_GUID, 0, method_id, &input, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj) {
|
||||
ret = -ENODATA;
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (obj->type) {
|
||||
case ACPI_TYPE_INTEGER:
|
||||
*out_value = obj->integer.value;
|
||||
break;
|
||||
case ACPI_TYPE_BUFFER:
|
||||
if (obj->buffer.length < 1) {
|
||||
ret = -ENODATA;
|
||||
break;
|
||||
}
|
||||
*out_value = obj->buffer.pointer[0];
|
||||
break;
|
||||
default:
|
||||
ret = -EPROTO;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(output.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init oxp_wmi_write_once_init(void)
|
||||
{
|
||||
u64 before = 0;
|
||||
u64 after = 0;
|
||||
int ret;
|
||||
|
||||
if (!dmi_first_match(dmi_table)) {
|
||||
pr_err("oxp-wmi-write-once: refusing: not ONEXPLAYER SUPER X\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!yes_i_understand_this_writes_to_hardware) {
|
||||
pr_err("oxp-wmi-write-once: refusing: set yes_i_understand_this_writes_to_hardware=1\n");
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
if (offset > 0xff || value > 0xff) {
|
||||
pr_err("oxp-wmi-write-once: refusing: offset/value must be bytes\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (wmi_instance_count(OXP_UMA_WMI_GUID) <= 0) {
|
||||
pr_err("oxp-wmi-write-once: refusing: OneX WMI GUID not present\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = oxp_wmi_call(OXP_WMI_METHOD_GET_EC_VALUE, (u8)offset, 0, &before);
|
||||
if (ret) {
|
||||
pr_err("oxp-wmi-write-once: pre-read [0x%02x] failed ret=%d\n", offset, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_warn("oxp-wmi-write-once: WMI write [0x%02x] 0x%02llx -> 0x%02x\n",
|
||||
offset, before & 0xff, value);
|
||||
|
||||
ret = oxp_wmi_call(OXP_WMI_METHOD_SET_EC_VALUE, (u8)offset, (u8)value, &after);
|
||||
if (ret) {
|
||||
pr_err("oxp-wmi-write-once: write [0x%02x]=0x%02x failed ret=%d\n",
|
||||
offset, value, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pr_warn("oxp-wmi-write-once: returned/readback [0x%02x]=0x%02llx\n",
|
||||
offset, after & 0xff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit oxp_wmi_write_once_exit(void)
|
||||
{
|
||||
pr_info("oxp-wmi-write-once: unloaded\n");
|
||||
}
|
||||
|
||||
module_init(oxp_wmi_write_once_init);
|
||||
module_exit(oxp_wmi_write_once_exit);
|
||||
|
||||
MODULE_AUTHOR("Hermes");
|
||||
MODULE_DESCRIPTION("Guarded one-shot OneXPlayer Super X WMI EC-memory byte writer");
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Add table
Reference in a new issue