1
0
mirror of https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2026-01-12 01:20:14 +00:00

platform/x86: Add Uniwill laptop driver

Add a new driver for Uniwill laptops. The driver uses a ACPI
interface to talk with the embedded controller, but relies on a
ACPI WMI interface for receiving event notifications.

The driver is reverse-engineered based on the following information:
- OEM software from intel
- https://github.com/pobrn/qc71_laptop
- https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers
- https://github.com/tuxedocomputers/tuxedo-control-center

The underlying EC supports various features, including hwmon sensors,
battery charge limiting, a RGB lightbar and keyboard-related controls.

Reported-by: cyear <chumuzero@gmail.com>
Closes: https://github.com/lm-sensors/lm-sensors/issues/508
Closes: https://github.com/Wer-Wolf/uniwill-laptop/issues/3
Tested-by: Werner Sembach <wse@tuxedocomputers.com>
Signed-off-by: Armin Wolf <W_Armin@gmx.de>
Link: https://patch.msgid.link/20251102172942.17879-2-W_Armin@gmx.de
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
This commit is contained in:
Armin Wolf 2025-11-02 18:29:41 +01:00 committed by Ilpo Järvinen
parent 90430ea98f
commit d050479693
No known key found for this signature in database
GPG Key ID: 59AC4F6153E5CE31
10 changed files with 2081 additions and 0 deletions

View File

@ -0,0 +1,53 @@
What: /sys/bus/platform/devices/INOU0000:XX/fn_lock_toggle_enable
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Allows userspace applications to enable/disable the FN lock feature
of the integrated keyboard by writing "1"/"0" into this file.
Reading this file returns the current enable status of the FN lock functionality.
What: /sys/bus/platform/devices/INOU0000:XX/super_key_toggle_enable
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Allows userspace applications to enable/disable the super key functionality
of the integrated keyboard by writing "1"/"0" into this file.
Reading this file returns the current enable status of the super key functionality.
What: /sys/bus/platform/devices/INOU0000:XX/touchpad_toggle_enable
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Allows userspace applications to enable/disable the touchpad toggle functionality
of the integrated touchpad by writing "1"/"0" into this file.
Reading this file returns the current enable status of the touchpad toggle
functionality.
What: /sys/bus/platform/devices/INOU0000:XX/rainbow_animation
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Forces the integrated lightbar to display a rainbow animation when the machine
is not suspended. Writing "1"/"0" into this file enables/disables this
functionality.
Reading this file returns the current status of the rainbow animation functionality.
What: /sys/bus/platform/devices/INOU0000:XX/breathing_in_suspend
Date: November 2025
KernelVersion: 6.19
Contact: Armin Wolf <W_Armin@gmx.de>
Description:
Causes the integrated lightbar to display a breathing animation when the machine
has been suspended and is running on AC power. Writing "1"/"0" into this file
enables/disables this functionality.
Reading this file returns the current status of the breathing animation
functionality.

View File

@ -0,0 +1,198 @@
.. SPDX-License-Identifier: GPL-2.0-or-later
========================================
Uniwill Notebook driver (uniwill-laptop)
========================================
Introduction
============
Many notebooks manufactured by Uniwill (either directly or as ODM) provide a EC interface
for controlling various platform settings like sensors and fan control. This interface is
used by the ``uniwill-laptop`` driver to map those features onto standard kernel interfaces.
EC WMI interface description
============================
The EC WMI interface description can be decoded from the embedded binary MOF (bmof)
data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
Description("Class used to operate methods on a ULong"),
guid("{ABBC0F6F-8EA1-11d1-00A0-C90629100000}")]
class AcpiTest_MULong {
[key, read] string InstanceName;
[read] boolean Active;
[WmiMethodId(1), Implemented, read, write, Description("Return the contents of a ULong")]
void GetULong([out, Description("Ulong Data")] uint32 Data);
[WmiMethodId(2), Implemented, read, write, Description("Set the contents of a ULong")]
void SetULong([in, Description("Ulong Data")] uint32 Data);
[WmiMethodId(3), Implemented, read, write,
Description("Generate an event containing ULong data")]
void FireULong([in, Description("WMI requires a parameter")] uint32 Hack);
[WmiMethodId(4), Implemented, read, write, Description("Get and Set the contents of a ULong")]
void GetSetULong([in, Description("Ulong Data")] uint64 Data,
[out, Description("Ulong Data")] uint32 Return);
[WmiMethodId(5), Implemented, read, write,
Description("Get and Set the contents of a ULong for Dollby button")]
void GetButton([in, Description("Ulong Data")] uint64 Data,
[out, Description("Ulong Data")] uint32 Return);
};
Most of the WMI-related code was copied from the Windows driver samples, which unfortunately means
that the WMI-GUID is not unique. This makes the WMI-GUID unusable for autoloading.
WMI method GetULong()
---------------------
This WMI method was copied from the Windows driver samples and has no function.
WMI method SetULong()
---------------------
This WMI method was copied from the Windows driver samples and has no function.
WMI method FireULong()
----------------------
This WMI method allows to inject a WMI event with a 32-bit payload. Its primary purpose seems
to be debugging.
WMI method GetSetULong()
------------------------
This WMI method is used to communicate with the EC. The ``Data`` argument holds the following
information (starting with the least significant byte):
1. 16-bit address
2. 16-bit data (set to ``0x0000`` when reading)
3. 16-bit operation (``0x0100`` for reading and ``0x0000`` for writing)
4. 16-bit reserved (set to ``0x0000``)
The first 8 bits of the ``Return`` value contain the data returned by the EC when reading.
The special value ``0xFEFEFEFE`` is used to indicate a communication failure with the EC.
WMI method GetButton()
----------------------
This WMI method is not implemented on all machines and has an unknown purpose.
Reverse-Engineering the EC WMI interface
========================================
.. warning:: Randomly poking the EC can potentially cause damage to the machine and other unwanted
side effects, please be careful.
The EC behind the ``GetSetULong`` method is used by the OEM software supplied by the manufacturer.
Reverse-engineering of this software is difficult since it uses an obfuscator, however some parts
are not obfuscated. In this case `dnSpy <https://github.com/dnSpy/dnSpy>`_ could also be helpful.
The EC can be accessed under Windows using powershell (requires admin privileges):
::
> $obj = Get-CimInstance -Namespace root/wmi -ClassName AcpiTest_MULong | Select-Object -First 1
> Invoke-CimMethod -InputObject $obj -MethodName GetSetULong -Arguments @{Data = <input>}
WMI event interface description
===============================
The WMI interface description can also be decoded from the embedded binary MOF (bmof)
data:
::
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"),
Description("Class containing event generated ULong data"),
guid("{ABBC0F72-8EA1-11d1-00A0-C90629100000}")]
class AcpiTest_EventULong : WmiEvent {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, write, Description("ULong Data")] uint32 ULong;
};
Most of the WMI-related code was again copied from the Windows driver samples, causing this WMI
interface to suffer from the same restrictions as the EC WMI interface described above.
WMI event data
--------------
The WMI event data contains a single 32-bit value which is used to indicate various platform events.
Reverse-Engineering the Uniwill WMI event interface
===================================================
The driver logs debug messages when receiving a WMI event. Thus enabling debug messages will be
useful for finding unknown event codes.
EC ACPI interface description
=============================
The ``INOU0000`` ACPI device is a virtual device used to access various hardware registers
available on notebooks manufactured by Uniwill. Reading and writing those registers happens
by calling ACPI control methods. The ``uniwill-laptop`` driver uses this device to communicate
with the EC because the ACPI control methods are faster than the WMI methods described above.
ACPI control methods used for reading registers take a single ACPI integer containing the address
of the register to read and return a ACPI integer containing the data inside said register. ACPI
control methods used for writing registers however take two ACPI integers, with the additional
ACPI integer containing the data to be written into the register. Such ACPI control methods return
nothing.
System memory
-------------
System memory can be accessed with a granularity of either a single byte (``MMRB`` for reading and
``MMWB`` for writing) or four bytes (``MMRD`` for reading and ``MMWD`` for writing). Those ACPI
control methods are unused because they provide no benefit when compared to the native memory
access functions provided by the kernel.
EC RAM
------
The internal RAM of the EC can be accessed with a granularity of a single byte using the ``ECRR``
(read) and ``ECRW`` (write) ACPI control methods, with the maximum register address being ``0xFFF``.
The OEM software waits 6 ms after calling one of those ACPI control methods, likely to avoid
overwhelming the EC when being connected over LPC.
PCI config space
----------------
The PCI config space can be accessed with a granularity of four bytes using the ``PCRD`` (read) and
``PCWD`` (write) ACPI control methods. The exact address format is unknown, and poking random PCI
devices might confuse the PCI subsystem. Because of this those ACPI control methods are not used.
IO ports
--------
IO ports can be accessed with a granularity of four bytes using the ``IORD`` (read) and ``IOWD``
(write) ACPI control methods. Those ACPI control methods are unused because they provide no benefit
when compared to the native IO port access functions provided by the kernel.
CMOS RAM
--------
The CMOS RAM can be accessed with a granularity of a single byte using the ``RCMS`` (read) and
``WCMS`` ACPI control methods. Using those ACPI methods might interfere with the native CMOS RAM
access functions provided by the kernel due to the usage of indexed IO, so they are unused.
Indexed IO
----------
Indexed IO with IO ports with a granularity of a single byte can be performed using the ``RIOP``
(read) and ``WIOP`` (write) ACPI control methods. Those ACPI methods are unused because they
provide no benifit when compared to the native IO port access functions provided by the kernel.
Special thanks go to github user `pobrn` which developed the
`qc71_laptop <https://github.com/pobrn/qc71_laptop>`_ driver on which this driver is partly based.
The same is true for Tuxedo Computers, which developed the
`tuxedo-drivers <https://gitlab.com/tuxedocomputers/development/packages/tuxedo-drivers>`_ package
which also served as a foundation for this driver.

View File

@ -26376,6 +26376,16 @@ L: linux-scsi@vger.kernel.org
S: Maintained
F: drivers/ufs/host/ufs-renesas.c
UNIWILL LAPTOP DRIVER
M: Armin Wolf <W_Armin@gmx.de>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-driver-uniwill-laptop
F: Documentation/wmi/devices/uniwill-laptop.rst
F: drivers/platform/x86/uniwill/uniwill-acpi.c
F: drivers/platform/x86/uniwill/uniwill-wmi.c
F: drivers/platform/x86/uniwill/uniwill-wmi.h
UNSORTED BLOCK IMAGES (UBI)
M: Richard Weinberger <richard@nod.at>
R: Zhihao Cheng <chengzhihao1@huawei.com>

View File

@ -74,6 +74,8 @@ config HUAWEI_WMI
To compile this driver as a module, choose M here: the module
will be called huawei-wmi.
source "drivers/platform/x86/uniwill/Kconfig"
config UV_SYSFS
tristate "Sysfs structure for UV systems"
depends on X86_UV

View File

@ -110,6 +110,9 @@ obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o
# before toshiba_acpi initializes
obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
# Uniwill
obj-y += uniwill/
# Inspur
obj-$(CONFIG_INSPUR_PLATFORM_PROFILE) += inspur_platform_profile.o

View File

@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Uniwill X86 Platform Specific Drivers
#
menuconfig X86_PLATFORM_DRIVERS_UNIWILL
bool "Uniwill X86 Platform Specific Device Drivers"
depends on X86_PLATFORM_DEVICES
help
Say Y here to see options for device drivers for various
Uniwill x86 platforms, including many OEM laptops originally
manufactured by Uniwill.
This option alone does not add any kernel code.
If you say N, all options in this submenu will be skipped and disabled.
if X86_PLATFORM_DRIVERS_UNIWILL
config UNIWILL_LAPTOP
tristate "Uniwill Laptop Extras"
default m
depends on ACPI
depends on ACPI_WMI
depends on ACPI_BATTERY
depends on HWMON
depends on INPUT
depends on LEDS_CLASS_MULTICOLOR
depends on DMI
select REGMAP
select INPUT_SPARSEKMAP
help
This driver adds support for various extra features found on Uniwill laptops,
like the lightbar, hwmon sensors and hotkeys. It also supports many OEM laptops
originally manufactured by Uniwill.
If you have such a laptop, say Y or M here.
endif

View File

@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Makefile for linux/drivers/platform/x86/uniwill
# Uniwill X86 Platform Specific Drivers
#
obj-$(CONFIG_UNIWILL_LAPTOP) += uniwill-laptop.o
uniwill-laptop-y := uniwill-acpi.o uniwill-wmi.o

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Linux hotkey driver for Uniwill notebooks.
*
* Special thanks go to Pőcze Barnabás, Christoffer Sandberg and Werner Sembach
* for supporting the development of this driver either through prior work or
* by answering questions regarding the underlying WMI interface.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/notifier.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/wmi.h>
#include "uniwill-wmi.h"
#define DRIVER_NAME "uniwill-wmi"
#define UNIWILL_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000"
static BLOCKING_NOTIFIER_HEAD(uniwill_wmi_chain_head);
static void devm_uniwill_wmi_unregister_notifier(void *data)
{
struct notifier_block *nb = data;
blocking_notifier_chain_unregister(&uniwill_wmi_chain_head, nb);
}
int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb)
{
int ret;
ret = blocking_notifier_chain_register(&uniwill_wmi_chain_head, nb);
if (ret < 0)
return ret;
return devm_add_action_or_reset(dev, devm_uniwill_wmi_unregister_notifier, nb);
}
static void uniwill_wmi_notify(struct wmi_device *wdev, union acpi_object *obj)
{
u32 value;
if (obj->type != ACPI_TYPE_INTEGER)
return;
value = obj->integer.value;
dev_dbg(&wdev->dev, "Received WMI event %u\n", value);
blocking_notifier_call_chain(&uniwill_wmi_chain_head, value, NULL);
}
/*
* We cannot fully trust this GUID since Uniwill just copied the WMI GUID
* from the Windows driver example, and others probably did the same.
*
* Because of this we cannot use this WMI GUID for autoloading. Instead the
* associated driver will be registered manually after matching a DMI table.
*/
static const struct wmi_device_id uniwill_wmi_id_table[] = {
{ UNIWILL_EVENT_GUID, NULL },
{ }
};
static struct wmi_driver uniwill_wmi_driver = {
.driver = {
.name = DRIVER_NAME,
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
.id_table = uniwill_wmi_id_table,
.notify = uniwill_wmi_notify,
.no_singleton = true,
};
int __init uniwill_wmi_register_driver(void)
{
return wmi_driver_register(&uniwill_wmi_driver);
}
void __exit uniwill_wmi_unregister_driver(void)
{
wmi_driver_unregister(&uniwill_wmi_driver);
}

View File

@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Linux hotkey driver for Uniwill notebooks.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#ifndef UNIWILL_WMI_H
#define UNIWILL_WMI_H
#include <linux/init.h>
#define UNIWILL_OSD_CAPSLOCK 0x01
#define UNIWILL_OSD_NUMLOCK 0x02
#define UNIWILL_OSD_SCROLLLOCK 0x03
#define UNIWILL_OSD_TOUCHPAD_ON 0x04
#define UNIWILL_OSD_TOUCHPAD_OFF 0x05
#define UNIWILL_OSD_SILENT_MODE_ON 0x06
#define UNIWILL_OSD_SILENT_MODE_OFF 0x07
#define UNIWILL_OSD_WLAN_ON 0x08
#define UNIWILL_OSD_WLAN_OFF 0x09
#define UNIWILL_OSD_WIMAX_ON 0x0A
#define UNIWILL_OSD_WIMAX_OFF 0x0B
#define UNIWILL_OSD_BLUETOOTH_ON 0x0C
#define UNIWILL_OSD_BLUETOOTH_OFF 0x0D
#define UNIWILL_OSD_RF_ON 0x0E
#define UNIWILL_OSD_RF_OFF 0x0F
#define UNIWILL_OSD_3G_ON 0x10
#define UNIWILL_OSD_3G_OFF 0x11
#define UNIWILL_OSD_WEBCAM_ON 0x12
#define UNIWILL_OSD_WEBCAM_OFF 0x13
#define UNIWILL_OSD_BRIGHTNESSUP 0x14
#define UNIWILL_OSD_BRIGHTNESSDOWN 0x15
#define UNIWILL_OSD_RADIOON 0x1A
#define UNIWILL_OSD_RADIOOFF 0x1B
#define UNIWILL_OSD_POWERSAVE_ON 0x31
#define UNIWILL_OSD_POWERSAVE_OFF 0x32
#define UNIWILL_OSD_MENU 0x34
#define UNIWILL_OSD_MUTE 0x35
#define UNIWILL_OSD_VOLUMEDOWN 0x36
#define UNIWILL_OSD_VOLUMEUP 0x37
#define UNIWILL_OSD_MENU_2 0x38
#define UNIWILL_OSD_LIGHTBAR_ON 0x39
#define UNIWILL_OSD_LIGHTBAR_OFF 0x3A
#define UNIWILL_OSD_KB_LED_LEVEL0 0x3B
#define UNIWILL_OSD_KB_LED_LEVEL1 0x3C
#define UNIWILL_OSD_KB_LED_LEVEL2 0x3D
#define UNIWILL_OSD_KB_LED_LEVEL3 0x3E
#define UNIWILL_OSD_KB_LED_LEVEL4 0x3F
#define UNIWILL_OSD_SUPER_KEY_LOCK_ENABLE 0x40
#define UNIWILL_OSD_SUPER_KEY_LOCK_DISABLE 0x41
#define UNIWILL_OSD_MENU_JP 0x42
#define UNIWILL_OSD_CAMERA_ON 0x90
#define UNIWILL_OSD_CAMERA_OFF 0x91
#define UNIWILL_OSD_RFKILL 0xA4
#define UNIWILL_OSD_SUPER_KEY_LOCK_CHANGED 0xA5
#define UNIWILL_OSD_LIGHTBAR_STATE_CHANGED 0xA6
#define UNIWILL_OSD_FAN_BOOST_STATE_CHANGED 0xA7
#define UNIWILL_OSD_LCD_SW 0xA9
#define UNIWILL_OSD_FAN_OVERTEMP 0xAA
#define UNIWILL_OSD_DC_ADAPTER_CHANGED 0xAB
#define UNIWILL_OSD_BAT_HP_OFF 0xAC
#define UNIWILL_OSD_FAN_DOWN_TEMP 0xAD
#define UNIWILL_OSD_BATTERY_ALERT 0xAE
#define UNIWILL_OSD_TIMAP_HAIERLB_SW 0xAF
#define UNIWILL_OSD_PERFORMANCE_MODE_TOGGLE 0xB0
#define UNIWILL_OSD_KBDILLUMDOWN 0xB1
#define UNIWILL_OSD_KBDILLUMUP 0xB2
#define UNIWILL_OSD_BACKLIGHT_LEVEL_CHANGE 0xB3
#define UNIWILL_OSD_BACKLIGHT_POWER_CHANGE 0xB4
#define UNIWILL_OSD_MIC_MUTE 0xB7
#define UNIWILL_OSD_FN_LOCK 0xB8
#define UNIWILL_OSD_KBDILLUMTOGGLE 0xB9
#define UNIWILL_OSD_BAT_CHARGE_FULL_24_H 0xBE
#define UNIWILL_OSD_BAT_ERM_UPDATE 0xBF
#define UNIWILL_OSD_BENCHMARK_MODE_TOGGLE 0xC0
#define UNIWILL_OSD_KBD_BACKLIGHT_CHANGED 0xF0
struct device;
struct notifier_block;
int devm_uniwill_wmi_register_notifier(struct device *dev, struct notifier_block *nb);
int __init uniwill_wmi_register_driver(void);
void __exit uniwill_wmi_unregister_driver(void);
#endif /* UNIWILL_WMI_H */