mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
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:
@@ -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
|
||||
------------------------------------
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[];
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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, ®);
|
||||
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, ®);
|
||||
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, ®);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
|
||||
@@ -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_ */
|
||||
@@ -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
Reference in New Issue
Block a user