Merge tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform driver updates from Ilpo Järvinen:
 "Highlights:

   - amd/pmf:
      - Avoid overwriting BIOS input values when events occur rapidly
      - Fix PMF driver issues related to S4 (in part on crypto/ccp side)
      - Add NPU metrics API (for accel side consumers)
      - Allow disabling Smart PC function through a module parameter

   - asus-wmi & HID/asus:
      - Unification of backlight control (replaces quirks)
      - Support multiple interfaces for controlling keyboard/RGB brightness
      - Simplify init sequence

   - hp-wmi:
      - Add manual fan control for Victus S models
      - Add fan mode keep-alive
      - Fix platform profile values for Omen 16-wf1xxx
      - Add EC offset to get the thermal profile

   - intel/pmc: Show substate residencies also for non-primary PMCs

   - intel/ISST:
      - Store and restore data for all domains
      - Write interface improvements

   - lenovo-wmi:
      - Support multiple Capability Data
      - Add HWMON reporting and tuning support

   - mellanox/mlx-platform: Add HI173 & HI174 support

   - surface/aggregator_registry: Add Surface Pro 11 (QCOM)

   - thinkpad_acpi: Add support for HW damage detection capability

   - uniwill: Implement cTGP setting

   - wmi:
      - Introduce marshalling support
      - Convert a few drivers to use the new buffer-based WMI API

   - tools/power/x86/intel-speed-select: Allow read operations for non-root

   - Miscellaneous cleanups / refactoring / improvements"

* tag 'platform-drivers-x86-v7.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (68 commits)
  platform/x86: lenovo-wmi-{capdata,other}: Fix HWMON channel visibility
  platform/x86: hp-wmi: Add EC offsets to read Victus S thermal profile
  platform: mellanox: mlx-platform: Add support DGX flavor of next-generation 800GB/s ethernet switch.
  platform: mellanox: mlx-platform: Add support for new Nvidia DGX system based on class VMOD0010
  HID: asus: add support for the asus-wmi brightness handler
  platform/x86: asus-wmi: add keyboard brightness event handler
  platform/x86: asus-wmi: remove unused keyboard backlight quirk
  HID: asus: listen to the asus-wmi brightness device instead of creating one
  platform/x86: asus-wmi: Add support for multiple kbd led handlers
  HID: asus: early return for ROG devices
  HID: asus: move vendor initialization to probe
  HID: asus: fortify keyboard handshake
  HID: asus: use same report_id in response
  HID: asus: initialize additional endpoints only for certain devices
  HID: asus: simplify RGB init sequence
  platform/wmi: string-kunit: Add missing oversized string test case
  platform/x86/amd/pmf: Added a module parameter to disable the Smart PC function
  platform/x86/uniwill: Implement cTGP setting
  platform/x86: uniwill-laptop: Introduce device descriptor system
  platform/x86/amd: Use scope-based cleanup for wbrf_record()
  ...
This commit is contained in:
Linus Torvalds
2026-02-13 15:39:15 -08:00
61 changed files with 5394 additions and 957 deletions
@@ -54,6 +54,7 @@ detailed description):
- Setting keyboard language
- WWAN Antenna type
- Auxmac
- Hardware damage detection capability
A compatibility table by model and feature is maintained on the web
site, http://ibm-acpi.sf.net/. I appreciate any success or failure
@@ -1576,6 +1577,42 @@ percentage level, above which charging will stop.
The exact semantics of the attributes may be found in
Documentation/ABI/testing/sysfs-class-power.
Hardware damage detection capability
------------------------------------
sysfs attributes: hwdd_status, hwdd_detail
Thinkpads are adding the ability to detect and report hardware damage.
Add new sysfs interface to identify the damaged device status.
Initial support is available for the USB-C replaceable connector.
The command to check device damaged status is::
cat /sys/devices/platform/thinkpad_acpi/hwdd_status
This value displays status of device damaged.
- 0 = Not Damaged
- 1 = Damaged
The command to check location of damaged device is::
cat /sys/devices/platform/thinkpad_acpi/hwdd_detail
This value displays location of damaged device having 1 line per damaged "item".
For example:
if no damage is detected:
- No damage detected
if damage detected:
- TYPE-C: Base, Right side, Center port
The property is read-only. If feature is not supported then sysfs
attribute is not created.
Multiple Commands, Module Parameters
------------------------------------
+3
View File
@@ -16,5 +16,8 @@ which will be bound to compatible WMI devices by the driver core.
.. kernel-doc:: include/linux/wmi.h
:internal:
.. kernel-doc:: drivers/platform/wmi/string.c
:export:
.. kernel-doc:: drivers/platform/wmi/core.c
:export:
+68
View File
@@ -104,3 +104,71 @@ holding the notification ID of the event. This method should be evaluated every
time an ACPI notification is received, since some ACPI implementations use a
queue to store WMI event data items. This queue will overflow after a couple
of WMI events are received without retrieving the associated WMI event data.
Conversion rules for ACPI data types
------------------------------------
Consumers of the ACPI-WMI interface use binary buffers to exchange data with the WMI driver core,
with the internal structure of the buffer being only know to the consumers. The WMI driver core is
thus responsible for converting the data inside the buffer into an appropriate ACPI data type for
consumption by the ACPI firmware. Additionally, any data returned by the various ACPI methods needs
to be converted back into a binary buffer.
The layout of said buffers is defined by the MOF description of the WMI method or data block in
question [1]_:
=============== ======================================================================= =========
Data Type Layout Alignment
=============== ======================================================================= =========
``string`` Starts with an unsigned 16-bit little endian integer specifying 2 bytes
the length of the string data in bytes, followed by the string data
encoded as UTF-16LE with **optional** NULL termination and padding.
Keep in mind that some firmware implementations might depend on the
terminating NULL character to be present. Also the padding should
always be performed with NULL characters.
``boolean`` Single byte where 0 means ``false`` and nonzero means ``true``. 1 byte
``sint8`` Signed 8-bit integer. 1 byte
``uint8`` Unsigned 8-bit integer. 1 byte
``sint16`` Signed 16-bit little endian integer. 2 bytes
``uint16`` Unsigned 16-bit little endian integer. 2 bytes
``sint32`` Signed 32-bit little endian integer. 4 bytes
``uint32`` Unsigned 32-bit little endian integer. 4 bytes
``sint64`` Signed 64-bit little endian integer. 8 bytes
``uint64`` Unsigned 64-bit little endian integer. 8 bytes
``datetime`` A fixed-length 25-character UTF-16LE string with the format 2 bytes
*yyyymmddhhmmss.mmmmmmsutc* where *yyyy* is the 4-digit year, *mm* is
the 2-digit month, *dd* is the 2-digit day, *hh* is the 2-digit hour
based on a 24-hour clock, *mm* is the 2-digit minute, *ss* is the
2-digit second, *mmmmmm* is the 6-digit microsecond, *s* is a plus or
minus character depending on whether *utc* is a positive or negative
offset from UTC (or a colon if the date is an interval). Unpopulated
fields should be filled with asterisks.
=============== ======================================================================= =========
Arrays should be aligned based on the alignment of their base type, while objects should be
aligned based on the largest alignment of an element inside them.
All buffers returned by the WMI driver core are 8-byte aligned. When converting ACPI data types
into such buffers the following conversion rules apply:
=============== ============================================================
ACPI Data Type Converted into
=============== ============================================================
Buffer Copied as-is.
Integer Converted into a ``uint32``.
String Converted into a ``string`` with a terminating NULL character
to match the behavior the of the Windows driver.
Package Each element inside the package is converted with alignment
of the resulting data types being respected. Nested packages
are not allowed.
=============== ============================================================
The Windows driver does attempt to handle nested packages, but this results in internal data
structures (``_ACPI_METHOD_ARGUMENT_V1``) erroneously being copied into the resulting buffer.
ACPI firmware implementations should thus not return nested packages from ACPI methods
associated with the ACPI-WMI interface.
References
==========
.. [1] https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/driver-defined-wmi-data-items
+43 -3
View File
@@ -31,13 +31,32 @@ under the following path:
/sys/class/firmware-attributes/lenovo-wmi-other/attributes/<attribute>/
Additionally, this driver also exports attributes to HWMON.
LENOVO_CAPABILITY_DATA_00
-------------------------
WMI GUID ``362A3AFE-3D96-4665-8530-96DAD5BB300E``
The LENOVO_CAPABILITY_DATA_00 interface provides various information that
does not rely on the gamezone thermal mode.
The following HWMON attributes are implemented:
- fanX_div: internal RPM divisor
- fanX_input: current RPM
- fanX_target: target RPM (tunable, 0=auto)
Due to the internal RPM divisor, the current/target RPMs are rounded down to
its nearest multiple. The divisor itself is not necessary to be a power of two.
LENOVO_CAPABILITY_DATA_01
-------------------------
WMI GUID ``7A8F5407-CB67-4D6E-B547-39B3BE018154``
The LENOVO_CAPABILITY_DATA_01 interface provides information on various
power limits of integrated CPU and GPU components.
The LENOVO_CAPABILITY_DATA_01 interface provides various information that
relies on the gamezone thermal mode, including power limits of integrated
CPU and GPU components.
Each attribute has the following properties:
- current_value
@@ -48,11 +67,22 @@ Each attribute has the following properties:
- scalar_increment
- type
The following attributes are implemented:
The following firmware-attributes are implemented:
- ppt_pl1_spl: Platform Profile Tracking Sustained Power Limit
- ppt_pl2_sppt: Platform Profile Tracking Slow Package Power Tracking
- ppt_pl3_fppt: Platform Profile Tracking Fast Package Power Tracking
LENOVO_FAN_TEST_DATA
-------------------------
WMI GUID ``B642801B-3D21-45DE-90AE-6E86F164FB21``
The LENOVO_FAN_TEST_DATA interface provides reference data for self-test of
cooling fans.
The following HWMON attributes are implemented:
- fanX_max: maximum RPM
- fanX_min: minimum RPM
WMI interface description
=========================
@@ -106,3 +136,13 @@ data using the `bmfdec <https://github.com/pali/bmfdec>`_ utility:
[WmiDataId(3), read, Description("Data Size.")] uint32 DataSize;
[WmiDataId(4), read, Description("Default Value"), WmiSizeIs("DataSize")] uint8 DefaultValue[];
};
[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Definition of Fan Test Data"), guid("{B642801B-3D21-45DE-90AE-6E86F164FB21}")]
class LENOVO_FAN_TEST_DATA {
[key, read] string InstanceName;
[read] boolean Active;
[WmiDataId(1), read, Description("Mode.")] uint32 NumOfFans;
[WmiDataId(2), read, Description("Fan ID."), WmiSizeIs("NumOfFans")] uint32 FanId[];
[WmiDataId(3), read, Description("Maximum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMaxSpeed[];
[WmiDataId(4), read, Description("Minumum Fan Speed."), WmiSizeIs("NumOfFans")] uint32 FanMinSpeed[];
};
+53 -23
View File
@@ -70,7 +70,7 @@ to matching WMI devices using a struct wmi_device_id table:
.probe = foo_probe,
.remove = foo_remove, /* optional, devres is preferred */
.shutdown = foo_shutdown, /* optional, called during shutdown */
.notify = foo_notify, /* optional, for event handling */
.notify_new = foo_notify, /* optional, for event handling */
.no_notify_data = true, /* optional, enables events containing no additional data */
.no_singleton = true, /* required for new WMI drivers */
};
@@ -90,9 +90,9 @@ the WMI device and put it in a well-known state for the WMI driver to pick up la
or kexec. Most WMI drivers need no special shutdown handling and can thus omit this callback.
Please note that new WMI drivers are required to be able to be instantiated multiple times,
and are forbidden from using any deprecated GUID-based WMI functions. This means that the
WMI driver should be prepared for the scenario that multiple matching WMI devices are present
on a given machine.
and are forbidden from using any deprecated GUID-based or ACPI-based WMI functions. This means
that the WMI driver should be prepared for the scenario that multiple matching WMI devices are
present on a given machine.
Because of this, WMI drivers should use the state container design pattern as described in
Documentation/driver-api/driver-model/design-patterns.rst.
@@ -104,38 +104,37 @@ Documentation/driver-api/driver-model/design-patterns.rst.
WMI method drivers
------------------
WMI drivers can call WMI device methods using wmidev_evaluate_method(), the
structure of the ACPI buffer passed to this function is device-specific and usually
needs some tinkering to get right. Looking at the ACPI tables containing the WMI
device usually helps here. The method id and instance number passed to this function
are also device-specific, looking at the decoded Binary MOF is usually enough to
find the right values.
WMI drivers can call WMI device methods using wmidev_invoke_method(). For each WMI method
invocation the WMI driver needs to provide the instance number and the method ID, as well as
a buffer with the method arguments and optionally a buffer for the results.
The maximum instance number can be retrieved during runtime using wmidev_instance_count().
The layout of said buffers is device-specific and described by the Binary MOF data associated
with a given WMI device. Said Binary MOF data also describes the method ID of a given WMI method
with the ``WmiMethodId`` qualifier. WMI devices exposing WMI methods usually expose only a single
instance (instance number 0), but in theory can expose multiple instances as well. In such a case
the number of instances can be retrieved using wmidev_instance_count().
Take a look at drivers/platform/x86/inspur_platform_profile.c for an example WMI method driver.
Take a look at drivers/platform/x86/intel/wmi/thunderbolt.c for an example WMI method driver.
WMI data block drivers
----------------------
WMI drivers can query WMI device data blocks using wmidev_block_query(), the
structure of the returned ACPI object is again device-specific. Some WMI devices
also allow for setting data blocks using wmidev_block_set().
WMI drivers can query WMI data blocks using wmidev_query_block(), the layout of the returned
buffer is again device-specific and described by the Binary MOF data. Some WMI data blocks are
also writeable and can be set using wmidev_set_block(). The number of data block instances can
again be retrieved using wmidev_instance_count().
The maximum instance number can also be retrieved using wmidev_instance_count().
Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example
WMI data block driver.
Take a look at drivers/platform/x86/intel/wmi/sbl-fw-update.c for an example WMI data block driver.
WMI event drivers
-----------------
WMI drivers can receive WMI events via the notify() callback inside the struct wmi_driver.
WMI drivers can receive WMI events via the notify_new() callback inside the struct wmi_driver.
The WMI subsystem will then take care of setting up the WMI event accordingly. Please note that
the structure of the ACPI object passed to this callback is device-specific, and freeing the
ACPI object is being done by the WMI subsystem, not the driver.
the layout of the buffer passed to this callback is device-specific, and freeing of the buffer
is done by the WMI subsystem itself, not the driver.
The WMI driver core will take care that the notify() callback will only be called after
The WMI driver core will take care that the notify_new() callback will only be called after
the probe() callback has been called, and that no events are being received by the driver
right before and after calling its remove() or shutdown() callback.
@@ -147,6 +146,36 @@ the ``no_notify_data`` flag inside struct wmi_driver should be set to ``true``.
Take a look at drivers/platform/x86/xiaomi-wmi.c for an example WMI event driver.
Exchanging data with the WMI driver core
----------------------------------------
WMI drivers can exchange data with the WMI driver core using struct wmi_buffer. The internal
structure of those buffers is device-specific and only known by the WMI driver. Because of this
the WMI driver itself is responsible for parsing and validating the data received from its
WMI device.
The structure of said buffers is described by the MOF data associated with the WMI device in
question. When such a buffer contains multiple data items it usually makes sense to define a
C structure and use it during parsing. Since the WMI driver core guarantees that all buffers
received from a WMI device are aligned on an 8-byte boundary, WMI drivers can simply perform
a cast between the WMI buffer data and this C structure.
This however should only be done after the size of the buffer was verified to be large enough
to hold the whole C structure. WMI drivers should reject undersized buffers as they are usually
sent by the WMI device to signal an internal error. Oversized buffers however should be accepted
to emulate the behavior of the Windows WMI implementation.
When defining a C structure for parsing WMI buffers the alignment of the data items should be
respected. This is especially important for 64-bit integers as those have different alignments
on 64-bit (8-byte alignment) and 32-bit (4-byte alignment) architectures. It is thus a good idea
to manually specify the alignment of such data items or mark the whole structure as packed when
appropriate. Integer data items in general are little-endian integers and should be marked as
such using ``__le64`` and friends. When parsing WMI string data items the struct wmi_string should
be used as WMI strings have a different layout than C strings.
See Documentation/wmi/acpi-interface.rst for more information regarding the binary format
of WMI data items.
Handling multiple WMI devices at once
-------------------------------------
@@ -171,6 +200,7 @@ Things to avoid
When developing WMI drivers, there are a couple of things which should be avoided:
- usage of the deprecated GUID-based WMI interface which uses GUIDs instead of WMI device structs
- usage of the deprecated ACPI-based WMI interface which uses ACPI objects instead of plain buffers
- bypassing of the WMI subsystem when talking to WMI devices
- WMI drivers which cannot be instantiated multiple times.
+11
View File
@@ -351,6 +351,17 @@ struct psp_device *psp_get_master_device(void)
return sp ? sp->psp_data : NULL;
}
int psp_restore(struct sp_device *sp)
{
struct psp_device *psp = sp->psp_data;
int ret = 0;
if (psp->tee_data)
ret = tee_restore(psp);
return ret;
}
void psp_pci_init(void)
{
psp_master = psp_get_master_device();
+12
View File
@@ -230,6 +230,18 @@ int sp_resume(struct sp_device *sp)
return 0;
}
int sp_restore(struct sp_device *sp)
{
if (sp->psp_data) {
int ret = psp_restore(sp);
if (ret)
return ret;
}
return sp_resume(sp);
}
struct sp_device *sp_get_psp_master_device(void)
{
struct sp_device *i, *ret = NULL;
+3
View File
@@ -141,6 +141,7 @@ void sp_destroy(struct sp_device *sp);
int sp_suspend(struct sp_device *sp);
int sp_resume(struct sp_device *sp);
int sp_restore(struct sp_device *sp);
int sp_request_ccp_irq(struct sp_device *sp, irq_handler_t handler,
const char *name, void *data);
void sp_free_ccp_irq(struct sp_device *sp, void *data);
@@ -174,6 +175,7 @@ int psp_dev_init(struct sp_device *sp);
void psp_pci_init(void);
void psp_dev_destroy(struct sp_device *sp);
void psp_pci_exit(void);
int psp_restore(struct sp_device *sp);
#else /* !CONFIG_CRYPTO_DEV_SP_PSP */
@@ -181,6 +183,7 @@ static inline int psp_dev_init(struct sp_device *sp) { return 0; }
static inline void psp_pci_init(void) { }
static inline void psp_dev_destroy(struct sp_device *sp) { }
static inline void psp_pci_exit(void) { }
static inline int psp_restore(struct sp_device *sp) { return 0; }
#endif /* CONFIG_CRYPTO_DEV_SP_PSP */
+15 -1
View File
@@ -353,6 +353,13 @@ static int __maybe_unused sp_pci_resume(struct device *dev)
return sp_resume(sp);
}
static int __maybe_unused sp_pci_restore(struct device *dev)
{
struct sp_device *sp = dev_get_drvdata(dev);
return sp_restore(sp);
}
#ifdef CONFIG_CRYPTO_DEV_SP_PSP
static const struct sev_vdata sevv1 = {
.cmdresp_reg = 0x10580, /* C2PMSG_32 */
@@ -563,7 +570,14 @@ static const struct pci_device_id sp_pci_table[] = {
};
MODULE_DEVICE_TABLE(pci, sp_pci_table);
static SIMPLE_DEV_PM_OPS(sp_pci_pm_ops, sp_pci_suspend, sp_pci_resume);
static const struct dev_pm_ops sp_pci_pm_ops = {
.suspend = pm_sleep_ptr(sp_pci_suspend),
.resume = pm_sleep_ptr(sp_pci_resume),
.freeze = pm_sleep_ptr(sp_pci_suspend),
.thaw = pm_sleep_ptr(sp_pci_resume),
.poweroff = pm_sleep_ptr(sp_pci_suspend),
.restore_early = pm_sleep_ptr(sp_pci_restore),
};
static struct pci_driver sp_pci_driver = {
.name = "ccp",
+44 -12
View File
@@ -86,10 +86,34 @@ static inline void tee_free_cmd_buffer(struct tee_init_ring_cmd *cmd)
kfree(cmd);
}
static bool tee_send_destroy_cmd(struct psp_tee_device *tee)
{
unsigned int reg;
int ret;
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
psp_dead = true;
return false;
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
psp_dead = true;
return false;
}
return true;
}
static int tee_init_ring(struct psp_tee_device *tee)
{
int ring_size = MAX_RING_BUFFER_ENTRIES * sizeof(struct tee_ring_cmd);
struct tee_init_ring_cmd *cmd;
bool retry = false;
unsigned int reg;
int ret;
@@ -112,6 +136,7 @@ static int tee_init_ring(struct psp_tee_device *tee)
/* Send command buffer details to Trusted OS by writing to
* CPU-PSP message registers
*/
retry_init:
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_INIT, cmd,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
@@ -122,9 +147,22 @@ static int tee_init_ring(struct psp_tee_device *tee)
}
if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
/*
* During the hibernate resume sequence driver may have gotten loaded
* but the ring not properly destroyed. If the ring doesn't work, try
* to destroy and re-init once.
*/
if (!retry && FIELD_GET(PSP_CMDRESP_STS, reg) == PSP_TEE_STS_RING_BUSY) {
dev_info(tee->dev, "tee: ring init command failed with busy status, retrying\n");
if (tee_send_destroy_cmd(tee)) {
retry = true;
goto retry_init;
}
}
dev_err(tee->dev, "tee: ring init command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
tee_free_ring(tee);
psp_dead = true;
ret = -EIO;
}
@@ -136,24 +174,13 @@ free_buf:
static void tee_destroy_ring(struct psp_tee_device *tee)
{
unsigned int reg;
int ret;
if (!tee->rb_mgr.ring_start)
return;
if (psp_dead)
goto free_ring;
ret = psp_mailbox_command(tee->psp, PSP_CMD_TEE_RING_DESTROY, NULL,
TEE_DEFAULT_CMD_TIMEOUT, &reg);
if (ret) {
dev_err(tee->dev, "tee: ring destroy command timed out, disabling TEE support\n");
psp_dead = true;
} else if (FIELD_GET(PSP_CMDRESP_STS, reg)) {
dev_err(tee->dev, "tee: ring destroy command failed (%#010lx)\n",
FIELD_GET(PSP_CMDRESP_STS, reg));
}
tee_send_destroy_cmd(tee);
free_ring:
tee_free_ring(tee);
@@ -365,3 +392,8 @@ int psp_check_tee_status(void)
return 0;
}
EXPORT_SYMBOL(psp_check_tee_status);
int tee_restore(struct psp_device *psp)
{
return tee_init_ring(psp->tee_data);
}
+1
View File
@@ -111,5 +111,6 @@ struct tee_ring_cmd {
int tee_dev_init(struct psp_device *psp);
void tee_dev_destroy(struct psp_device *psp);
int tee_restore(struct psp_device *psp);
#endif /* __TEE_DEV_H__ */
+108 -121
View File
@@ -28,7 +28,6 @@
#include <linux/hid.h>
#include <linux/module.h>
#include <linux/platform_data/x86/asus-wmi.h>
#include <linux/platform_data/x86/asus-wmi-leds-ids.h>
#include <linux/input/mt.h>
#include <linux/usb.h> /* For to_usb_interface for T100 touchpad intf check */
#include <linux/power_supply.h>
@@ -50,7 +49,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define FEATURE_REPORT_ID 0x0d
#define INPUT_REPORT_ID 0x5d
#define FEATURE_KBD_REPORT_ID 0x5a
#define FEATURE_KBD_REPORT_SIZE 16
#define FEATURE_KBD_REPORT_SIZE 64
#define FEATURE_KBD_LED_REPORT_ID1 0x5d
#define FEATURE_KBD_LED_REPORT_ID2 0x5e
@@ -99,9 +98,10 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define QUIRK_T90CHI BIT(9)
#define QUIRK_MEDION_E1239T BIT(10)
#define QUIRK_ROG_NKEY_KEYBOARD BIT(11)
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
#define QUIRK_ROG_ALLY_XPAD BIT(13)
#define QUIRK_HID_FN_LOCK BIT(14)
#define QUIRK_ROG_NKEY_ID1ID2_INIT BIT(15)
#define I2C_KEYBOARD_QUIRKS (QUIRK_FIX_NOTEBOOK_REPORT | \
QUIRK_NO_INIT_REPORTS | \
@@ -113,7 +113,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
#define TRKID_SGN ((TRKID_MAX + 1) >> 1)
struct asus_kbd_leds {
struct led_classdev cdev;
struct asus_hid_listener listener;
struct hid_device *hdev;
struct work_struct work;
unsigned int brightness;
@@ -138,7 +138,6 @@ struct asus_drvdata {
struct input_dev *tp_kbd_input;
struct asus_kbd_leds *kbd_backlight;
const struct asus_touchpad_info *tp;
bool enable_backlight;
struct power_supply *battery;
struct power_supply_desc battery_desc;
int battery_capacity;
@@ -363,10 +362,21 @@ static int asus_event(struct hid_device *hdev, struct hid_field *field,
usage->hid & HID_USAGE);
}
if (drvdata->quirks & QUIRK_HID_FN_LOCK &&
usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) {
drvdata->fn_lock = !drvdata->fn_lock;
schedule_work(&drvdata->fn_lock_sync_work);
if (usage->type == EV_KEY && value) {
switch (usage->code) {
case KEY_KBDILLUMUP:
return !asus_hid_event(ASUS_EV_BRTUP);
case KEY_KBDILLUMDOWN:
return !asus_hid_event(ASUS_EV_BRTDOWN);
case KEY_KBDILLUMTOGGLE:
return !asus_hid_event(ASUS_EV_BRTTOGGLE);
case KEY_FN_ESC:
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = !drvdata->fn_lock;
schedule_work(&drvdata->fn_lock_sync_work);
}
break;
}
}
return 0;
@@ -476,15 +486,41 @@ static int asus_kbd_set_report(struct hid_device *hdev, const u8 *buf, size_t bu
static int asus_kbd_init(struct hid_device *hdev, u8 report_id)
{
/*
* The handshake is first sent as a set_report, then retrieved
* from a get_report. They should be equal.
*/
const u8 buf[] = { report_id, 0x41, 0x53, 0x55, 0x53, 0x20, 0x54,
0x65, 0x63, 0x68, 0x2e, 0x49, 0x6e, 0x63, 0x2e, 0x00 };
int ret;
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0)
hid_err(hdev, "Asus failed to send init command: %d\n", ret);
if (ret < 0) {
hid_err(hdev, "Asus handshake %02x failed to send: %d\n",
report_id, ret);
return ret;
}
return ret;
u8 *readbuf __free(kfree) = kzalloc(FEATURE_KBD_REPORT_SIZE, GFP_KERNEL);
if (!readbuf)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
hid_warn(hdev, "Asus handshake %02x failed to receive ack: %d\n",
report_id, ret);
} else if (memcmp(readbuf, buf, sizeof(buf)) != 0) {
hid_warn(hdev, "Asus handshake %02x returned invalid response: %*ph\n",
report_id, FEATURE_KBD_REPORT_SIZE, readbuf);
}
/*
* Do not return error if handshake is wrong until this is
* verified to work for all devices.
*/
return 0;
}
static int asus_kbd_get_functions(struct hid_device *hdev,
@@ -505,7 +541,7 @@ static int asus_kbd_get_functions(struct hid_device *hdev,
if (!readbuf)
return -ENOMEM;
ret = hid_hw_raw_request(hdev, FEATURE_KBD_REPORT_ID, readbuf,
ret = hid_hw_raw_request(hdev, report_id, readbuf,
FEATURE_KBD_REPORT_SIZE, HID_FEATURE_REPORT,
HID_REQ_GET_REPORT);
if (ret < 0) {
@@ -565,11 +601,11 @@ static void asus_schedule_work(struct asus_kbd_leds *led)
spin_unlock_irqrestore(&led->lock, flags);
}
static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
static void asus_kbd_backlight_set(struct asus_hid_listener *listener,
int brightness)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
struct asus_kbd_leds *led = container_of(listener, struct asus_kbd_leds,
listener);
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
@@ -579,20 +615,6 @@ static void asus_kbd_backlight_set(struct led_classdev *led_cdev,
asus_schedule_work(led);
}
static enum led_brightness asus_kbd_backlight_get(struct led_classdev *led_cdev)
{
struct asus_kbd_leds *led = container_of(led_cdev, struct asus_kbd_leds,
cdev);
enum led_brightness brightness;
unsigned long flags;
spin_lock_irqsave(&led->lock, flags);
brightness = led->brightness;
spin_unlock_irqrestore(&led->lock, flags);
return brightness;
}
static void asus_kbd_backlight_work(struct work_struct *work)
{
struct asus_kbd_leds *led = container_of(work, struct asus_kbd_leds, work);
@@ -609,34 +631,6 @@ static void asus_kbd_backlight_work(struct work_struct *work)
hid_err(led->hdev, "Asus failed to set keyboard backlight: %d\n", ret);
}
/* WMI-based keyboard backlight LED control (via asus-wmi driver) takes
* precedence. We only activate HID-based backlight control when the
* WMI control is not available.
*/
static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev)
{
struct asus_drvdata *drvdata = hid_get_drvdata(hdev);
u32 value;
int ret;
if (!IS_ENABLED(CONFIG_ASUS_WMI))
return false;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD &&
dmi_check_system(asus_use_hid_led_dmi_ids)) {
hid_info(hdev, "using HID for asus::kbd_backlight\n");
return false;
}
ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS,
ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value);
hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value);
if (ret)
return false;
return !!(value & ASUS_WMI_DSTS_PRESENCE_BIT);
}
/*
* We don't care about any other part of the string except the version section.
* Example strings: FGA80100.RC72LA.312_T01, FGA80100.RC71LS.318_T01
@@ -736,48 +730,35 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
unsigned char kbd_func;
int ret;
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD) {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Check for backlight support */
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
return -ENODEV;
if (drvdata->quirks & QUIRK_ROG_NKEY_ID1ID2_INIT) {
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
}
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
}
/* The LED endpoint is initialised in two HID */
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID1);
if (ret < 0)
return ret;
ret = asus_kbd_init(hdev, FEATURE_KBD_LED_REPORT_ID2);
if (ret < 0)
return ret;
if (dmi_match(DMI_PRODUCT_FAMILY, "ProArt P16")) {
ret = asus_kbd_disable_oobe(hdev);
if (ret < 0)
return ret;
}
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
} else {
/* Initialize keyboard */
ret = asus_kbd_init(hdev, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Get keyboard functions */
ret = asus_kbd_get_functions(hdev, &kbd_func, FEATURE_KBD_REPORT_ID);
if (ret < 0)
return ret;
/* Check for backlight support */
if (!(kbd_func & SUPPORT_KBD_BACKLIGHT))
return -ENODEV;
if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) {
intf = to_usb_interface(hdev->dev.parent);
udev = interface_to_usbdev(intf);
validate_mcu_fw_version(hdev,
le16_to_cpu(udev->descriptor.idProduct));
}
drvdata->kbd_backlight = devm_kzalloc(&hdev->dev,
@@ -789,14 +770,11 @@ static int asus_kbd_register_leds(struct hid_device *hdev)
drvdata->kbd_backlight->removed = false;
drvdata->kbd_backlight->brightness = 0;
drvdata->kbd_backlight->hdev = hdev;
drvdata->kbd_backlight->cdev.name = "asus::kbd_backlight";
drvdata->kbd_backlight->cdev.max_brightness = 3;
drvdata->kbd_backlight->cdev.brightness_set = asus_kbd_backlight_set;
drvdata->kbd_backlight->cdev.brightness_get = asus_kbd_backlight_get;
drvdata->kbd_backlight->listener.brightness_set = asus_kbd_backlight_set;
INIT_WORK(&drvdata->kbd_backlight->work, asus_kbd_backlight_work);
spin_lock_init(&drvdata->kbd_backlight->lock);
ret = devm_led_classdev_register(&hdev->dev, &drvdata->kbd_backlight->cdev);
ret = asus_hid_register_listener(&drvdata->kbd_backlight->listener);
if (ret < 0) {
/* No need to have this still around */
devm_kfree(&hdev->dev, drvdata->kbd_backlight);
@@ -1021,11 +999,6 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
drvdata->input = input;
if (drvdata->enable_backlight &&
!asus_kbd_wmi_led_control_present(hdev) &&
asus_kbd_register_leds(hdev))
hid_warn(hdev, "Failed to initialize backlight.\n");
if (drvdata->quirks & QUIRK_HID_FN_LOCK) {
drvdata->fn_lock = true;
INIT_WORK(&drvdata->fn_lock_sync_work, asus_sync_fn_lock);
@@ -1104,15 +1077,6 @@ static int asus_input_mapping(struct hid_device *hdev,
return -1;
}
/*
* Check and enable backlight only on devices with UsagePage ==
* 0xff31 to avoid initializing the keyboard firmware multiple
* times on devices with multiple HID descriptors but same
* PID/VID.
*/
if (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT)
drvdata->enable_backlight = true;
set_bit(EV_REP, hi->input->evbit);
return 1;
}
@@ -1205,7 +1169,7 @@ static int __maybe_unused asus_resume(struct hid_device *hdev) {
if (drvdata->kbd_backlight) {
const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4,
drvdata->kbd_backlight->cdev.brightness };
drvdata->kbd_backlight->brightness };
ret = asus_kbd_set_report(hdev, buf, sizeof(buf));
if (ret < 0) {
hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret);
@@ -1229,8 +1193,11 @@ static int __maybe_unused asus_reset_resume(struct hid_device *hdev)
static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
int ret;
struct hid_report_enum *rep_enum;
struct asus_drvdata *drvdata;
struct hid_report *rep;
bool is_vendor = false;
int ret;
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL) {
@@ -1314,12 +1281,30 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id)
return ret;
}
/* Check for vendor for RGB init and handle generic devices properly. */
rep_enum = &hdev->report_enum[HID_INPUT_REPORT];
list_for_each_entry(rep, &rep_enum->report_list, list) {
if ((rep->application & HID_USAGE_PAGE) == HID_UP_ASUSVENDOR)
is_vendor = true;
}
ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (ret) {
hid_err(hdev, "Asus hw start failed: %d\n", ret);
return ret;
}
if (is_vendor && (drvdata->quirks & QUIRK_USE_KBD_BACKLIGHT) &&
asus_kbd_register_leds(hdev))
hid_warn(hdev, "Failed to initialize backlight.\n");
/*
* For ROG keyboards, skip rename for consistency and ->input check as
* some devices do not have inputs.
*/
if (drvdata->quirks & QUIRK_ROG_NKEY_KEYBOARD)
return 0;
/*
* Check that input registration succeeded. Checking that
* HID_CLAIMED_INPUT is set prevents a UAF when all input devices
@@ -1356,6 +1341,8 @@ static void asus_remove(struct hid_device *hdev)
unsigned long flags;
if (drvdata->kbd_backlight) {
asus_hid_unregister_listener(&drvdata->kbd_backlight->listener);
spin_lock_irqsave(&drvdata->kbd_backlight->lock, flags);
drvdata->kbd_backlight->removed = true;
spin_unlock_irqrestore(&drvdata->kbd_backlight->lock, flags);
@@ -1490,10 +1477,10 @@ static const struct hid_device_id asus_devices[] = {
QUIRK_USE_KBD_BACKLIGHT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK },
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD | QUIRK_HID_FN_LOCK | QUIRK_ROG_NKEY_ID1ID2_INIT },
{ HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK,
USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR),
QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD },
File diff suppressed because it is too large Load Diff
@@ -406,6 +406,22 @@ static const struct software_node *ssam_node_group_sp9_5g[] = {
NULL,
};
/* Devices for Surface Pro 11 (ARM/QCOM) */
static const struct software_node *ssam_node_group_sp11[] = {
&ssam_node_root,
&ssam_node_hub_kip,
&ssam_node_bat_ac,
&ssam_node_bat_main,
&ssam_node_tmp_sensors,
&ssam_node_hid_kip_keyboard,
&ssam_node_hid_kip_penstash,
&ssam_node_hid_kip_touchpad,
&ssam_node_hid_kip_fwupd,
&ssam_node_hid_sam_sensors,
&ssam_node_kip_tablet_switch,
NULL,
};
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
static const struct acpi_device_id ssam_platform_hub_acpi_match[] = {
@@ -482,6 +498,8 @@ MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_acpi_match);
static const struct of_device_id ssam_platform_hub_of_match[] __maybe_unused = {
/* Surface Pro 9 5G (ARM/QCOM) */
{ .compatible = "microsoft,arcata", (void *)ssam_node_group_sp9_5g },
/* Surface Pro 11 (ARM/QCOM) */
{ .compatible = "microsoft,denali", (void *)ssam_node_group_sp11 },
/* Surface Laptop 7 */
{ .compatible = "microsoft,romulus13", (void *)ssam_node_group_sl7 },
{ .compatible = "microsoft,romulus15", (void *)ssam_node_group_sl7 },
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/input.h>
#include <linux/acpi.h>
@@ -189,7 +190,6 @@ static int surface_button_add(struct acpi_device *device)
struct surface_button *button;
struct input_dev *input;
const char *hid = acpi_device_hid(device);
char *name;
int error;
if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME,
@@ -210,11 +210,10 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_button;
}
name = acpi_device_name(device);
strcpy(name, SURFACE_BUTTON_DEVICE_NAME);
strscpy(acpi_device_name(device), SURFACE_BUTTON_DEVICE_NAME);
snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid);
input->name = name;
input->name = acpi_device_name(device);
input->phys = button->phys;
input->id.bustype = BUS_HOST;
input->dev.parent = &device->dev;
@@ -228,8 +227,8 @@ static int surface_button_add(struct acpi_device *device)
goto err_free_input;
device_init_wakeup(&device->dev, true);
dev_info(&device->dev,
"%s [%s]\n", name, acpi_device_bid(device));
dev_info(&device->dev, "%s [%s]\n", acpi_device_name(device),
acpi_device_bid(device));
return 0;
err_free_input:
+3
View File
@@ -6,6 +6,7 @@
menuconfig ACPI_WMI
tristate "ACPI-WMI support"
depends on ACPI && X86
select NLS
help
This option enables support for the ACPI-WMI driver core.
@@ -31,4 +32,6 @@ config ACPI_WMI_LEGACY_DEVICE_NAMES
userspace applications but will cause the registration of WMI devices with
the same GUID to fail in some corner cases.
source "drivers/platform/wmi/tests/Kconfig"
endif # ACPI_WMI
+4 -1
View File
@@ -4,5 +4,8 @@
# ACPI WMI core
#
wmi-y := core.o
wmi-y := core.o marshalling.o string.o
obj-$(CONFIG_ACPI_WMI) += wmi.o
# Unit tests
obj-y += tests/
+156 -4
View File
@@ -23,6 +23,7 @@
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/rwsem.h>
@@ -33,6 +34,8 @@
#include <linux/wmi.h>
#include <linux/fs.h>
#include "internal.h"
MODULE_AUTHOR("Carlos Corbacho");
MODULE_DESCRIPTION("ACPI-WMI Mapping Driver");
MODULE_LICENSE("GPL");
@@ -302,7 +305,7 @@ acpi_status wmi_evaluate_method(const char *guid_string, u8 instance, u32 method
EXPORT_SYMBOL_GPL(wmi_evaluate_method);
/**
* wmidev_evaluate_method - Evaluate a WMI method
* wmidev_evaluate_method - Evaluate a WMI method (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @method_id: Method ID to call
@@ -360,6 +363,70 @@ acpi_status wmidev_evaluate_method(struct wmi_device *wdev, u8 instance, u32 met
}
EXPORT_SYMBOL_GPL(wmidev_evaluate_method);
/**
* wmidev_invoke_method - Invoke a WMI method
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @method_id: Method ID to call
* @in: Mandatory WMI buffer containing input for the method call
* @out: Optional WMI buffer to return the method results
*
* Invoke a WMI method, the caller must free the resulting data inside @out.
* Said data is guaranteed to be aligned on a 8-byte boundary.
*
* Return: 0 on success or negative error code on failure.
*/
int wmidev_invoke_method(struct wmi_device *wdev, u8 instance, u32 method_id,
const struct wmi_buffer *in, struct wmi_buffer *out)
{
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
struct acpi_buffer aout = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer ain;
union acpi_object *obj;
acpi_status status;
int ret;
if (wblock->gblock.flags & ACPI_WMI_STRING) {
ret = wmi_marshal_string(in, &ain);
if (ret < 0)
return ret;
} else {
if (in->length > U32_MAX)
return -E2BIG;
ain.length = in->length;
ain.pointer = in->data;
}
if (out)
status = wmidev_evaluate_method(wdev, instance, method_id, &ain, &aout);
else
status = wmidev_evaluate_method(wdev, instance, method_id, &ain, NULL);
if (wblock->gblock.flags & ACPI_WMI_STRING)
kfree(ain.pointer);
if (ACPI_FAILURE(status))
return -EIO;
if (!out)
return 0;
obj = aout.pointer;
if (!obj) {
out->length = 0;
out->data = ZERO_SIZE_PTR;
return 0;
}
ret = wmi_unmarshal_acpi_object(obj, out);
kfree(obj);
return ret;
}
EXPORT_SYMBOL_GPL(wmidev_invoke_method);
static acpi_status __query_block(struct wmi_block *wblock, u8 instance,
struct acpi_buffer *out)
{
@@ -432,7 +499,7 @@ acpi_status wmi_query_block(const char *guid_string, u8 instance,
EXPORT_SYMBOL_GPL(wmi_query_block);
/**
* wmidev_block_query - Return contents of a WMI block
* wmidev_block_query - Return contents of a WMI block (deprectated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
*
@@ -452,6 +519,33 @@ union acpi_object *wmidev_block_query(struct wmi_device *wdev, u8 instance)
}
EXPORT_SYMBOL_GPL(wmidev_block_query);
/**
* wmidev_query_block - Return contents of a WMI data block
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @out: WMI buffer to fill
*
* Query a WMI data block, the caller must free the resulting data inside @out.
* Said data is guaranteed to be aligned on a 8-byte boundary.
*
* Return: 0 on success or a negative error code on failure.
*/
int wmidev_query_block(struct wmi_device *wdev, u8 instance, struct wmi_buffer *out)
{
union acpi_object *obj;
int ret;
obj = wmidev_block_query(wdev, instance);
if (!obj)
return -EIO;
ret = wmi_unmarshal_acpi_object(obj, out);
kfree(obj);
return ret;
}
EXPORT_SYMBOL_GPL(wmidev_query_block);
/**
* wmi_set_block - Write to a WMI block (deprecated)
* @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
@@ -486,7 +580,7 @@ acpi_status wmi_set_block(const char *guid_string, u8 instance, const struct acp
EXPORT_SYMBOL_GPL(wmi_set_block);
/**
* wmidev_block_set - Write to a WMI block
* wmidev_block_set - Write to a WMI block (deprecated)
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @in: Buffer containing new values for the data block
@@ -535,6 +629,46 @@ acpi_status wmidev_block_set(struct wmi_device *wdev, u8 instance, const struct
}
EXPORT_SYMBOL_GPL(wmidev_block_set);
/**
* wmidev_set_block - Write to a WMI data block
* @wdev: A wmi bus device from a driver
* @instance: Instance index
* @in: WMI buffer containing new values for the data block
*
* Write the content of @in into a WMI data block.
*
* Return: 0 on success or negative error code on failure.
*/
int wmidev_set_block(struct wmi_device *wdev, u8 instance, const struct wmi_buffer *in)
{
struct wmi_block *wblock = container_of(wdev, struct wmi_block, dev);
struct acpi_buffer buffer;
acpi_status status;
int ret;
if (wblock->gblock.flags & ACPI_WMI_STRING) {
ret = wmi_marshal_string(in, &buffer);
if (ret < 0)
return ret;
} else {
if (in->length > U32_MAX)
return -E2BIG;
buffer.length = in->length;
buffer.pointer = in->data;
}
status = wmidev_block_set(wdev, instance, &buffer);
if (wblock->gblock.flags & ACPI_WMI_STRING)
kfree(buffer.pointer);
if (ACPI_FAILURE(status))
return -EIO;
return 0;
}
EXPORT_SYMBOL_GPL(wmidev_set_block);
/**
* wmi_install_notify_handler - Register handler for WMI events (deprecated)
* @guid: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba
@@ -862,7 +996,7 @@ static int wmi_dev_probe(struct device *dev)
return -ENODEV;
}
if (wdriver->notify) {
if (wdriver->notify || wdriver->notify_new) {
if (test_bit(WMI_NO_EVENT_DATA, &wblock->flags) && !wdriver->no_notify_data)
return -ENODEV;
}
@@ -1221,6 +1355,8 @@ static int wmi_get_notify_data(struct wmi_block *wblock, union acpi_object **obj
static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
{
struct wmi_driver *driver = to_wmi_driver(wblock->dev.dev.driver);
struct wmi_buffer buffer;
int ret;
if (!obj && !driver->no_notify_data) {
dev_warn(&wblock->dev.dev, "Event contains no event data\n");
@@ -1229,6 +1365,22 @@ static void wmi_notify_driver(struct wmi_block *wblock, union acpi_object *obj)
if (driver->notify)
driver->notify(&wblock->dev, obj);
if (driver->notify_new) {
if (!obj) {
driver->notify_new(&wblock->dev, NULL);
return;
}
ret = wmi_unmarshal_acpi_object(obj, &buffer);
if (ret < 0) {
dev_warn(&wblock->dev.dev, "Failed to unmarshal event data: %d\n", ret);
return;
}
driver->notify_new(&wblock->dev, &buffer);
kfree(buffer.data);
}
}
static int wmi_notify_device(struct device *dev, void *data)
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Internal interfaces used by the WMI core.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#ifndef _WMI_INTERNAL_H_
#define _WMI_INTERNAL_H_
union acpi_object;
struct wmi_buffer;
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer);
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out);
#endif /* _WMI_INTERNAL_H_ */
+247
View File
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* ACPI-WMI buffer marshalling.
*
* Copyright (C) 2025 Armin Wolf <W_Armin@gmx.de>
*/
#include <linux/acpi.h>
#include <linux/align.h>
#include <linux/math.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/unaligned.h>
#include <linux/wmi.h>
#include <kunit/visibility.h>
#include "internal.h"
static int wmi_adjust_buffer_length(size_t *length, const union acpi_object *obj)
{
size_t alignment, size;
switch (obj->type) {
case ACPI_TYPE_INTEGER:
/*
* Integers are threated as 32 bit even if the ACPI DSDT
* declares 64 bit integer width.
*/
alignment = 4;
size = sizeof(u32);
break;
case ACPI_TYPE_STRING:
/*
* Strings begin with a single little-endian 16-bit field containing
* the string length in bytes and are encoded as UTF-16LE with a terminating
* nul character.
*/
if (obj->string.length + 1 > U16_MAX / 2)
return -EOVERFLOW;
alignment = 2;
size = struct_size_t(struct wmi_string, chars, obj->string.length + 1);
break;
case ACPI_TYPE_BUFFER:
/*
* Buffers are copied as-is.
*/
alignment = 1;
size = obj->buffer.length;
break;
default:
return -EPROTO;
}
*length = size_add(ALIGN(*length, alignment), size);
return 0;
}
static int wmi_obj_get_buffer_length(const union acpi_object *obj, size_t *length)
{
size_t total = 0;
int ret;
if (obj->type == ACPI_TYPE_PACKAGE) {
for (int i = 0; i < obj->package.count; i++) {
ret = wmi_adjust_buffer_length(&total, &obj->package.elements[i]);
if (ret < 0)
return ret;
}
} else {
ret = wmi_adjust_buffer_length(&total, obj);
if (ret < 0)
return ret;
}
*length = total;
return 0;
}
static int wmi_obj_transform_simple(const union acpi_object *obj, u8 *buffer, size_t *consumed)
{
struct wmi_string *string;
size_t length;
__le32 value;
u8 *aligned;
switch (obj->type) {
case ACPI_TYPE_INTEGER:
aligned = PTR_ALIGN(buffer, 4);
length = sizeof(value);
value = cpu_to_le32(obj->integer.value);
memcpy(aligned, &value, length);
break;
case ACPI_TYPE_STRING:
aligned = PTR_ALIGN(buffer, 2);
string = (struct wmi_string *)aligned;
length = struct_size(string, chars, obj->string.length + 1);
/* We do not have to worry about unaligned accesses here as the WMI
* string will already be aligned on a two-byte boundary.
*/
string->length = cpu_to_le16((obj->string.length + 1) * 2);
for (int i = 0; i < obj->string.length; i++)
string->chars[i] = cpu_to_le16(obj->string.pointer[i]);
/*
* The Windows WMI-ACPI driver always emits a terminating nul character,
* so we emulate this behavior here as well.
*/
string->chars[obj->string.length] = '\0';
break;
case ACPI_TYPE_BUFFER:
aligned = buffer;
length = obj->buffer.length;
memcpy(aligned, obj->buffer.pointer, length);
break;
default:
return -EPROTO;
}
*consumed = (aligned - buffer) + length;
return 0;
}
static int wmi_obj_transform(const union acpi_object *obj, u8 *buffer)
{
size_t consumed;
int ret;
if (obj->type == ACPI_TYPE_PACKAGE) {
for (int i = 0; i < obj->package.count; i++) {
ret = wmi_obj_transform_simple(&obj->package.elements[i], buffer,
&consumed);
if (ret < 0)
return ret;
buffer += consumed;
}
} else {
ret = wmi_obj_transform_simple(obj, buffer, &consumed);
if (ret < 0)
return ret;
}
return 0;
}
int wmi_unmarshal_acpi_object(const union acpi_object *obj, struct wmi_buffer *buffer)
{
size_t length, alloc_length;
u8 *data;
int ret;
ret = wmi_obj_get_buffer_length(obj, &length);
if (ret < 0)
return ret;
if (ARCH_KMALLOC_MINALIGN < 8) {
/*
* kmalloc() guarantees that the alignment of the resulting memory allocation is at
* least the largest power-of-two divisor of the allocation size. The WMI buffer
* data needs to be aligned on a 8 byte boundary to properly support 64-bit WMI
* integers, so we have to round the allocation size to the next multiple of 8.
*/
alloc_length = round_up(length, 8);
} else {
alloc_length = length;
}
data = kzalloc(alloc_length, GFP_KERNEL);
if (!data)
return -ENOMEM;
ret = wmi_obj_transform(obj, data);
if (ret < 0) {
kfree(data);
return ret;
}
buffer->length = length;
buffer->data = data;
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(wmi_unmarshal_acpi_object);
int wmi_marshal_string(const struct wmi_buffer *buffer, struct acpi_buffer *out)
{
const struct wmi_string *string;
u16 length, value;
size_t chars;
char *str;
if (buffer->length < sizeof(*string))
return -ENODATA;
string = buffer->data;
length = get_unaligned_le16(&string->length);
if (buffer->length < sizeof(*string) + length)
return -ENODATA;
/* Each character needs to be 16 bits long */
if (length % 2)
return -EINVAL;
chars = length / 2;
str = kmalloc(chars + 1, GFP_KERNEL);
if (!str)
return -ENOMEM;
for (int i = 0; i < chars; i++) {
value = get_unaligned_le16(&string->chars[i]);
/* ACPI only accepts ASCII strings */
if (value > 0x7F) {
kfree(str);
return -EINVAL;
}
str[i] = value & 0xFF;
/*
* ACPI strings should only contain a single nul character at the end.
* Because of this we must not copy any padding from the WMI string.
*/
if (!value) {
/* ACPICA wants the length of the string without the nul character */
out->length = i;
out->pointer = str;
return 0;
}
}
str[chars] = '\0';
out->length = chars;
out->pointer = str;
return 0;
}
EXPORT_SYMBOL_IF_KUNIT(wmi_marshal_string);

Some files were not shown because too many files have changed in this diff Show More