Re: [PATCH v4 4/6 RESEND] platform/x86: Add Lenovo Capability Data 01 WMI Driver

From: Armin Wolf
Date: Wed Apr 02 2025 - 21:40:29 EST


Am 02.04.25 um 22:47 schrieb Derek John Clark:

On Wed, Mar 26, 2025 at 6:29 PM Armin Wolf <W_Armin@xxxxxx> wrote:
Am 17.03.25 um 15:43 schrieb Derek J. Clark:

Adds lenovo-wmi-capdata01 driver which provides the
LENOVO_CAPABILITY_DATA_01 WMI data block that comes on "Other Mode"
enabled hardware. Provides an interface for querying if a given
attribute is supported by the hardware, as well as its default_value,
max_value, min_value, and step increment.

Signed-off-by: Derek J. Clark <derekjohn.clark@xxxxxxxxx>
---
v4:
- Make driver data a private struct, remove references from Other Mode
driver.
- Don't cache data at device initialization. Instead, on component bind,
cache the data on a member variable of the Other Mode driver data
passed as a void pointer.
- Add header file for capdata01 structs.
- Add new struct to pass capdata01 array data and array length to Other
Mode.
v3:
- Add as component to lenovo-wmi-other driver.
v2:
- Use devm_kmalloc to ensure driver can be instanced, remove global
reference.
- Ensure reverse Christmas tree for all variable declarations.
- Remove extra whitespace.
- Use guard(mutex) in all mutex instances, global mutex.
- Use pr_fmt instead of adding the driver name to each pr_err.
- Remove noisy pr_info usage.
- Rename capdata_wmi to lenovo_wmi_cd01_priv and cd01_wmi to priv.
- Use list to get the lenovo_wmi_cd01_priv instance in
lenovo_wmi_capdata01_get as none of the data provided by the macros
that will use it can pass a member of the struct for use in
container_of.
---
MAINTAINERS | 2 +
drivers/platform/x86/Kconfig | 4 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/lenovo-wmi-capdata01.c | 136 ++++++++++++++++++++
drivers/platform/x86/lenovo-wmi-capdata01.h | 29 +++++
5 files changed, 172 insertions(+)
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.c
create mode 100644 drivers/platform/x86/lenovo-wmi-capdata01.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 6dde75922aaf..56ead241a053 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -13164,6 +13164,8 @@ L: platform-driver-x86@xxxxxxxxxxxxxxx
S: Maintained
F: Documentation/wmi/devices/lenovo-wmi-gamezone.rst
F: Documentation/wmi/devices/lenovo-wmi-other.rst
+F: drivers/platform/x86/lenovo-wmi-capdata01.c
+F: drivers/platform/x86/lenovo-wmi-capdata01.h
F: drivers/platform/x86/lenovo-wmi-events.c
F: drivers/platform/x86/lenovo-wmi-events.h
F: drivers/platform/x86/lenovo-wmi-helpers.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 13b8f4ac5dc5..64663667f0cb 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -467,6 +467,10 @@ config LENOVO_WMI_HELPERS
tristate
depends on ACPI_WMI

+config LENOVO_WMI_DATA01
+ tristate
+ depends on ACPI_WMI
+
config IDEAPAD_LAPTOP
tristate "Lenovo IdeaPad Laptop Extras"
depends on ACPI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index fc039839286a..7a35c77221b7 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o
obj-$(CONFIG_YT2_1380) += lenovo-yoga-tab2-pro-1380-fastcharger.o
obj-$(CONFIG_LENOVO_WMI_CAMERA) += lenovo-wmi-camera.o
+obj-$(CONFIG_LENOVO_WMI_DATA01) += lenovo-wmi-capdata01.o
obj-$(CONFIG_LENOVO_WMI_EVENTS) += lenovo-wmi-events.o
obj-$(CONFIG_LENOVO_WMI_HELPERS) += lenovo-wmi-helpers.o

diff --git a/drivers/platform/x86/lenovo-wmi-capdata01.c b/drivers/platform/x86/lenovo-wmi-capdata01.c
new file mode 100644
index 000000000000..b6876611ffd9
--- /dev/null
+++ b/drivers/platform/x86/lenovo-wmi-capdata01.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LENOVO_CAPABILITY_DATA_01 WMI data block driver. This interface provides
+ * information on tunable attributes used by the "Other Mode" WMI interface,
+ * including if it is supported by the hardware, the default_value, max_value,
+ * min_value, and step increment.
+ *
+ * Copyright(C) 2025 Derek J. Clark <derekjohn.clark@xxxxxxxxx>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/component.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/gfp_types.h>
+#include <linux/types.h>
+#include <linux/wmi.h>
+#include "lenovo-wmi-capdata01.h"
Hi,

please also include linux/acpi.h, linux/export.h and linux/module.h.

+
+/* Interface GUIDs */
+#define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154"
+
+struct lwmi_cd01_priv {
+ struct wmi_device *wdev;
+};
+
+/*
/* -> /**

+ * lenovo_cd01_component_bind() - On master bind, caches all capability data on
+ * the master device.
+ * @cd01_dev: Pointer to the capability data 01 parent device.
+ * @om_dev: Pointer to the other mode parent device.
+ * @data: capdata01_list object pointer to return the capability data with.
+ *
+ * Returns: 0, or an error.
+ */
+static int lenovo_cd01_component_bind(struct device *cd01_dev,
+ struct device *om_dev, void *data)
+{
+ struct lwmi_cd01_priv *priv = dev_get_drvdata(cd01_dev);
+ int count, idx;
+
+ if (!priv)
+ return -ENODEV;
This check is unnecessary, please drop.

Acked

+
+ count = wmidev_instance_count(priv->wdev);
+
+ if (count == 0)
+ return -EINVAL;
The WMI driver core already ensures that WMI devices with 0 instances are
rejected. Please drop this check.

Good to know, thanks.

+
+ ((struct cd01_list *)data)->count = count;
+ ((struct cd01_list *)data)->data = devm_kmalloc_array(om_dev, count,
+ sizeof(struct capdata01 *),
+ GFP_KERNEL);
Two things:

- using a local variable with a type of struct cd01_list * results in cleaner source code here

- using devres is not possible inside the component callbacks, since the lifetime of the component
device is not necessarily tied to the lifetime of the underlying device.

I suggest you move the whole WMI data querying into lwmi_cd01_probe(), because then you can keep using
devres.

Doing this in probe() puts the list on lwmi_cd01_priv. Should I copy
that data on bind, or pass back a pointer to the cd01 device struct
and use an exported function on cd01 and dev_get_drvdata to access
priv->list->data[idx] when needed? I prefer the latter as this avoids
needing to do devm memory allocation in component/master binds, then I
can check for NULL when accessing and clear the pointer on
master_unbind to avoid calling to a removed device driver.

Passing a pointer is OK, but please only pass a pointer to the struct cd01_list itself,
not the full cd01 device struct. Maybe you can also add a comment explaining that this
pointer will become invalid when unbinding from the component.

Explicitly NULL-ing this pointer upon unbinding seems unnecessary to me.

(snip)

+struct cd01_list {
+ struct capdata01 **data;
+ int count;
+};
In order to save memory you could try something like this:

struct cd01_list {
size_t count;
struct capdata01 data[];
};

This way you

1. Avoid the memory fragmentation resulting from multiple memory allocations.

2. Omit two pointers when accessing the data.

You can use struct_size() from linux/overflow.h to calculate the size of such
an array with a trailing flexible array.

Thanks,
Armin Wolf

I think I have this part working in my branch. Using devm_kzalloc also
allows me to omit manually setting NULL in a few cases, which is
cleaner. Is it preferred to use struct_size() directly in the
devm_kzalloc call, or create a separate `size` variable to set the
result to and pass that into the function?

- Derek

In most cases using struct_size() directly inside the devm_kzalloc() call is preferred.

Thanks,
Armin Wolf

+
+int lwmi_cd01_match(struct device *dev, void *data);
+
+#endif /* !_LENOVO_WMI_CAPDATA01_H_ */