* [PATCH] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
@ 2026-04-25 13:23 Dave Carey
2026-04-28 14:39 ` Ilpo Järvinen
0 siblings, 1 reply; 23+ messages in thread
From: Dave Carey @ 2026-04-25 13:23 UTC (permalink / raw)
To: platform-driver-x86
Cc: linux-kernel, linux-input, Hans de Goede, Ilpo Järvinen,
Dave Carey
The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
that magnetically attaches to the bottom (secondary) screen in one of two
positions. The Embedded Controller tracks the attachment state in a 2-bit
field called BKBD and signals changes via WMI event GUID
806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
device).
The current BKBD state is read via WMI query GUID
E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method), which returns an
8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
[4..7] hold the BKBD value.
BKBD encoding:
0 = keyboard detached
1 = keyboard docked on top half of bottom screen
2 = keyboard docked on bottom half of bottom screen
3 = reserved (not observed in practice)
This driver:
- Registers as a WMI driver on the event GUID.
- Queries BKBD state on probe and on each WMI notification.
- Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
in either position (a physical keyboard is present in both cases).
- Exposes the raw BKBD value via a read-only sysfs attribute
"keyboard_position" for use by userspace (e.g. to distinguish between
the two docked positions for different UI layouts).
Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
.../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
MAINTAINERS | 6 +
drivers/platform/x86/lenovo/Kconfig | 14 ++
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 216 ++++++++++++++++++
5 files changed, 258 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
create mode 100644 MAINTAINERS
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 0000000..bb57690
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,21 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: April 2026
+KernelVersion: 6.10
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == ============================================================
+ 0 detached — keyboard is not docked to any screen
+ 1 top-half — keyboard docked on the top half of the bottom screen
+ 2 bottom-half — keyboard docked on the bottom half of the bottom screen
+ == ============================================================
+
+ The value is formatted as "<n> (<name>)\n", e.g. "1 (top-half)\n".
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..cb765b4
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,6 @@
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 9c48487..938b361 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e..2842d7d 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 0000000..693e287
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device.
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * This driver registers for the WMI event GUID, queries BKBD on probe and on
+ * each event, reports SW_TABLET_MODE=0 when the keyboard is docked (either
+ * position) and SW_TABLET_MODE=1 when detached, and exposes the raw BKBD
+ * value in sysfs as "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+/*
+ * WM10 ACPI device (_UID "GMZN"):
+ * Event GUID — notify ID 0xEB fires on keyboard attachment change.
+ * Query GUID — object "AF", maps to WQAF(); returns 8-byte buffer
+ * {LFID=0x00060000, BKBD[31:0]}.
+ */
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+#define YB9_KBDOCK_QUERY_INSTANCE 0
+
+/* BKBD encoding — keyboard always docks on the bottom screen */
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1 /* docked on top half of bottom screen */
+#define BKBD_BOTTOM_HALF 2 /* docked on bottom half of bottom screen */
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+struct yb9_kbdock_priv {
+ struct input_dev *input_dev;
+ unsigned int bkbd; /* last read BKBD value (0-3) */
+};
+
+/* Read current BKBD state via WQAF. Returns 0-3 or -errno. */
+static int yb9_kbdock_query(struct wmi_device *wdev)
+{
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ u32 bkbd;
+
+ status = wmi_query_block(YB9_KBDOCK_QUERY_GUID,
+ YB9_KBDOCK_QUERY_INSTANCE, &out);
+ if (ACPI_FAILURE(status)) {
+ dev_warn(&wdev->dev, "WQAF query failed: %s\n",
+ acpi_format_exception(status));
+ return -EIO;
+ }
+
+ obj = out.pointer;
+ if (!obj) {
+ dev_warn(&wdev->dev, "WQAF returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
+ * bytes [4..7] = BKBD value. Guard against short buffers.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ bkbd &= 0x3;
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ bkbd = obj->integer.value & 0x3;
+ } else {
+ dev_warn(&wdev->dev,
+ "WQAF: unexpected result type %d len %u\n",
+ obj->type,
+ obj->type == ACPI_TYPE_BUFFER
+ ? obj->buffer.length : 0);
+ kfree(obj);
+ return -EIO;
+ }
+
+ kfree(obj);
+ return (int)bkbd;
+}
+
+static void yb9_kbdock_update(struct wmi_device *wdev)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
+ int bkbd;
+ int tablet_mode;
+
+ bkbd = yb9_kbdock_query(wdev);
+ if (bkbd < 0)
+ return;
+
+ priv->bkbd = bkbd;
+
+ /*
+ * Report tablet mode only when the keyboard is fully detached.
+ * Both docked positions (top-half and bottom-half of the bottom screen)
+ * indicate a physical keyboard is present — report laptop mode.
+ */
+ tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
+ input_sync(priv->input_dev);
+
+ dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
+}
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ yb9_kbdock_update(wdev);
+}
+
+/* sysfs: keyboard_position — exposes raw BKBD value */
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
+ static const char * const names[] = {
+ "detached", "top-half", "bottom-half", "unknown"
+ };
+ unsigned int bkbd = priv->bkbd;
+
+ if (bkbd > 3)
+ bkbd = 3;
+ return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct yb9_kbdock_priv *priv;
+ struct input_dev *input_dev;
+ int err;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table)) {
+ dev_dbg(&wdev->dev, "not a Yoga Book 9, skipping\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&wdev->dev, "failed to register input device: %d\n", err);
+ return err;
+ }
+
+ priv->input_dev = input_dev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ /* Report initial state */
+ yb9_kbdock_update(wdev);
+ return 0;
+}
+
+static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
+
+static struct wmi_driver yb9_kbdock_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ },
+ .id_table = yb9_kbdock_wmi_id_table,
+ .probe = yb9_kbdock_probe,
+ .notify = yb9_kbdock_notify,
+};
+module_wmi_driver(yb9_kbdock_driver);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-04-25 13:23 [PATCH] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
@ 2026-04-28 14:39 ` Ilpo Järvinen
2026-05-17 15:01 ` Dave Carey
2026-05-17 15:02 ` [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Dave Carey
0 siblings, 2 replies; 23+ messages in thread
From: Ilpo Järvinen @ 2026-04-28 14:39 UTC (permalink / raw)
To: Dave Carey, Hans de Goede, Pit Henrich
Cc: platform-driver-x86, LKML, linux-input
[-- Attachment #1: Type: text/plain, Size: 14388 bytes --]
On Sat, 25 Apr 2026, Dave Carey wrote:
> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
> that magnetically attaches to the bottom (secondary) screen in one of two
> positions.
> The Embedded Controller tracks the attachment state in a 2-bit
> field called BKBD and signals changes via WMI event GUID
> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
> device).
Please put this in depth explanation in own paragraph.
> The current BKBD state is read via WMI query GUID
> E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method),
This seems mostly duplicate of what was said previously.
>which returns an
> 8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
> [4..7] hold the BKBD value.
>
> BKBD encoding:
> 0 = keyboard detached
> 1 = keyboard docked on top half of bottom screen
> 2 = keyboard docked on bottom half of bottom screen
> 3 = reserved (not observed in practice)
These two can be combined with the in depth explanation paragraph.
> This driver:
> - Registers as a WMI driver on the event GUID.
Unnecessary / obvious.
> - Queries BKBD state on probe and on each WMI notification.
> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
> in either position (a physical keyboard is present in both cases).
> - Exposes the raw BKBD value via a read-only sysfs attribute
> "keyboard_position" for use by userspace (e.g. to distinguish between
> the two docked positions for different UI layouts).
Please write this without bullet points. Bullet points usually break
relationships between sentences.
> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> ---
> .../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
> MAINTAINERS | 6 +
> drivers/platform/x86/lenovo/Kconfig | 14 ++
> drivers/platform/x86/lenovo/Makefile | 1 +
> drivers/platform/x86/lenovo/yb9-kbdock.c | 216 ++++++++++++++++++
> 5 files changed, 258 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> create mode 100644 MAINTAINERS
> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> new file mode 100644
> index 0000000..bb57690
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> @@ -0,0 +1,21 @@
> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
> +Date: April 2026
> +KernelVersion: 6.10
> +Contact: Dave Carey <carvsdriver@gmail.com>
> +Description:
> + Read-only attribute reporting the current keyboard dock position
> + as reported by the Embedded Controller on the Lenovo Yoga Book 9
> + 14IAH10.
> +
> + Possible values:
> +
> + == ============================================================
> + 0 detached — keyboard is not docked to any screen
> + 1 top-half — keyboard docked on the top half of the bottom screen
> + 2 bottom-half — keyboard docked on the bottom half of the bottom screen
> + == ============================================================
Interesting, I wonder if this is similar physically to what is being added
here:
https://lore.kernel.org/all/20260419102724.91451-1-pithenrich2d@gmail.com/
?
If yes, we may have to take another look at how to create the interface
for this.
You didn't document unknown but return it (maybe it should return some
-Exx code instead?).
> + The value is formatted as "<n> (<name>)\n", e.g. "1 (top-half)\n".
> +
> + SW_TABLET_MODE input events are also emitted: 0 when the keyboard
> + is docked (either position), 1 when detached.
> diff --git a/MAINTAINERS b/MAINTAINERS
> new file mode 100644
> index 0000000..cb765b4
> --- /dev/null
> +++ b/MAINTAINERS
> @@ -0,0 +1,6 @@
> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
> +M: Dave Carey <carvsdriver@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 9c48487..938b361 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
> To compile this driver as a module, choose M here: the module
> will be called lenovo-wmi-camera.
>
> +config LENOVO_YB9_KBDOCK
> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
> + depends on ACPI_WMI
> + depends on DMI
> + depends on INPUT
> + help
> + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
> + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
> + either screen; this driver reports SW_TABLET_MODE input events based
> + on the attachment state and exposes the raw position in sysfs.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called lenovo-yb9-kbdock.
> +
> config LENOVO_YMC
> tristate "Lenovo Yoga Tablet Mode Control"
> depends on ACPI_WMI
> diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
> index 7b2128e..2842d7d 100644
> --- a/drivers/platform/x86/lenovo/Makefile
> +++ b/drivers/platform/x86/lenovo/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>
> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
> lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000..693e287
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
> @@ -0,0 +1,216 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Yoga Book 9 keyboard-dock detection
> + *
> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
> + * attaches to the bottom screen in one of two positions. The EC tracks
> + * attachment state in a 2-bit field called BKBD and signals changes via WMI
> + * event 0xEB on the WM10 ACPI device.
> + *
> + * BKBD values:
> + * 0 = keyboard detached
> + * 1 = keyboard docked on the top half of the bottom screen
> + * 2 = keyboard docked on the bottom half of the bottom screen
> + * 3 = reserved / not observed
>
> + * This driver registers for the WMI event GUID, queries BKBD on probe and on
> + * each event, reports SW_TABLET_MODE=0 when the keyboard is docked (either
> + * position) and SW_TABLET_MODE=1 when detached, and exposes the raw BKBD
> + * value in sysfs as "keyboard_position".
I don't think the functional description on this level is warranted in the
top comment (may place it below where you define things but if naming is
obvious, some comments may not even be necessary).
> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +
> +/*
> + * WM10 ACPI device (_UID "GMZN"):
> + * Event GUID — notify ID 0xEB fires on keyboard attachment change.
> + * Query GUID — object "AF", maps to WQAF(); returns 8-byte buffer
> + * {LFID=0x00060000, BKBD[31:0]}.
> + */
> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
> +
> +#define YB9_KBDOCK_QUERY_INSTANCE 0
> +
> +/* BKBD encoding — keyboard always docks on the bottom screen */
> +#define BKBD_DETACHED 0
> +#define BKBD_TOP_HALF 1 /* docked on top half of bottom screen */
> +#define BKBD_BOTTOM_HALF 2 /* docked on bottom half of bottom screen */
> +
> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
> + {
> + /* Lenovo Yoga Book 9 14IAH10 */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
Normally these appear towards the end of the file.
> +
> +struct yb9_kbdock_priv {
> + struct input_dev *input_dev;
> + unsigned int bkbd; /* last read BKBD value (0-3) */
> +};
> +
> +/* Read current BKBD state via WQAF. Returns 0-3 or -errno. */
> +static int yb9_kbdock_query(struct wmi_device *wdev)
> +{
> + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *obj;
> + acpi_status status;
> + u32 bkbd;
> +
> + status = wmi_query_block(YB9_KBDOCK_QUERY_GUID,
> + YB9_KBDOCK_QUERY_INSTANCE, &out);
This interface has been deprecated.
> + if (ACPI_FAILURE(status)) {
> + dev_warn(&wdev->dev, "WQAF query failed: %s\n",
> + acpi_format_exception(status));
> + return -EIO;
> + }
> +
> + obj = out.pointer;
> + if (!obj) {
> + dev_warn(&wdev->dev, "WQAF returned NULL\n");
> + return -EIO;
> + }
> +
> + /*
> + * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
> + * bytes [4..7] = BKBD value. Guard against short buffers.
One space is enough.
> + */
> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
> + bkbd &= 0x3;
Can this literal be named with a define? Should it use FIELD_GET()
(don't forget the header if you start to use FIELD_GET())?
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + bkbd = obj->integer.value & 0x3;
Same question here.
> + } else {
> + dev_warn(&wdev->dev,
> + "WQAF: unexpected result type %d len %u\n",
> + obj->type,
> + obj->type == ACPI_TYPE_BUFFER
> + ? obj->buffer.length : 0);
Put the last two lines to one line.
> + kfree(obj);
> + return -EIO;
> + }
> +
> + kfree(obj);
Please use __free() instead of duplicating kfree()s.
When converting to __free(), don't use ... = NULL; pattern, instead place
the variable declaration mid-function as instructed in the long comment in
cleanup.h.
> + return (int)bkbd;
Unnecessary cast. And your types are a major mess between int and
unsigned types.
> +}
> +
> +static void yb9_kbdock_update(struct wmi_device *wdev)
> +{
> + struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
> + int bkbd;
> + int tablet_mode;
Please use reverse-xmas tree order where there are no internal
dependencies between local variables that prevent usinbg it.
> +
> + bkbd = yb9_kbdock_query(wdev);
> + if (bkbd < 0)
> + return;
> +
> + priv->bkbd = bkbd;
> +
> + /*
> + * Report tablet mode only when the keyboard is fully detached.
> + * Both docked positions (top-half and bottom-half of the bottom screen)
> + * indicate a physical keyboard is present — report laptop mode.
> + */
> + tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
> +
> + input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
> + input_sync(priv->input_dev);
> +
> + dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
Missing include.
> +}
> +
> +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + yb9_kbdock_update(wdev);
> +}
> +
> +/* sysfs: keyboard_position — exposes raw BKBD value */
> +static ssize_t keyboard_position_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
> + static const char * const names[] = {
> + "detached", "top-half", "bottom-half", "unknown"
Use comma for all non-terminating entries.
> + };
> + unsigned int bkbd = priv->bkbd;
> +
> + if (bkbd > 3)
How can this happen without it being bug in the driver? Didn't you mask it
when reading the value?
So maybe
if (WARN_ON_ONCE(bkbd > 3))
return -EINVAL;
> + bkbd = 3;
> + return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
> +}
> +static DEVICE_ATTR_RO(keyboard_position);
> +
> +static struct attribute *yb9_kbdock_attrs[] = {
> + &dev_attr_keyboard_position.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(yb9_kbdock);
> +
> +static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct yb9_kbdock_priv *priv;
> + struct input_dev *input_dev;
> + int err;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table)) {
> + dev_dbg(&wdev->dev, "not a Yoga Book 9, skipping\n");
> + return -ENODEV;
> + }
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err) {
> + dev_err(&wdev->dev, "failed to register input device: %d\n", err);
> + return err;
> + }
> +
> + priv->input_dev = input_dev;
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + /* Report initial state */
> + yb9_kbdock_update(wdev);
> + return 0;
> +}
> +
> +static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
> +
> +static struct wmi_driver yb9_kbdock_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock",
> + .dev_groups = yb9_kbdock_groups,
> + },
> + .id_table = yb9_kbdock_wmi_id_table,
> + .probe = yb9_kbdock_probe,
> + .notify = yb9_kbdock_notify,
> +};
> +module_wmi_driver(yb9_kbdock_driver);
> +
> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
> +MODULE_LICENSE("GPL");
>
--
i.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-04-28 14:39 ` Ilpo Järvinen
@ 2026-05-17 15:01 ` Dave Carey
2026-05-17 15:02 ` [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Dave Carey
1 sibling, 0 replies; 23+ messages in thread
From: Dave Carey @ 2026-05-17 15:01 UTC (permalink / raw)
To: ilpo.jarvinen
Cc: hdegoede, pithenrich2d, mpearson-lenovo, derekjohn.clark, W_Armin,
platform-driver-x86, linux-input, linux-kernel, Dave Carey
On Tue, 28 Apr 2026 17:39:17 +0300, Ilpo Järvinen wrote:
> On Sat, 25 Apr 2026, Dave Carey wrote:
Thank you for the review. All points addressed in v2 below.
> Please put this in depth explanation in own paragraph.
> This seems mostly duplicate of what was said previously.
> These two can be combined with the in depth explanation paragraph.
> Please write this without bullet points.
Commit message rewritten as prose. The hardware description, WMI
mechanism, and BKBD encoding are now combined into a single explanatory
paragraph. The functional summary (what the driver does) follows in a
second paragraph without bullet points.
> I don't think the functional description on this level is warranted in
> the top comment.
Top-of-file block comment trimmed to hardware context only. The
functional description (query on probe, SW_TABLET_MODE mapping, sysfs
attribute) has been removed from there; the comment now covers only the
two WMI GUIDs and the BKBD encoding table, which are non-obvious from
the code alone.
> This interface has been deprecated.
Done. v2 uses wmidev_block_query() with two arguments, returning
union acpi_object * directly. This required registering both the event
and query GUIDs in the id_table with context pointers (enum
yb9_guid_type) so the query wdev is reachable from probe and the notify
path. The event-device probe defers with -EPROBE_DEFER until the query
device arrives.
> Please use __free() instead of duplicating kfree()s.
> When converting to __free(), don't use ... = NULL; pattern, instead place
> the variable declaration mid-function as instructed in cleanup.h.
Done. The obj declaration now uses __free(kfree) placed mid-function
after the early-exit checks, per the cleanup.h guidance. Added
linux/cleanup.h to the includes.
> Can this literal be named with a define? Should it use FIELD_GET()?
> (don't forget the header if you start to use FIELD_GET())
Done. BKBD_MASK is now GENMASK(1, 0) and both extraction sites use
FIELD_GET(BKBD_MASK, bkbd). Added linux/bitfield.h to the includes.
> Unnecessary cast. And your types are a major mess between int and
> unsigned types.
Fixed. The query function returns int throughout (0-2 on success,
-errno on error); the cast is gone. Local variables in yb9_kbdock_update
are consistently int.
> Please use reverse-xmas tree order.
Fixed in yb9_kbdock_update(): tablet_mode is now declared before bkbd.
> WARN_ON_ONCE() instead of just WARN_ON() in the sysfs show function.
Done.
> You didn't document unknown but return it (maybe it should return some
> -Exx code instead?).
The "unknown" fourth entry has been removed entirely. BKBD value 3 is
now caught in yb9_kbdock_query() and returned as -EINVAL so it never
reaches the sysfs show function or priv->bkbd. The show function uses
WARN_ON_ONCE(bkbd >= ARRAY_SIZE(names)) and returns -EINVAL as
suggested — this can only fire if there is a driver bug.
> Missing include.
Added linux/bitfield.h (FIELD_GET) and linux/cleanup.h (__free).
> Normally these [DMI table] appear towards the end of the file.
Moved to just before yb9_kbdock_probe().
> Put the last two lines to one line.
Done.
> I wonder if this is similar physically to what is being added here:
> https://lore.kernel.org/all/20260419102724.91451-1-pithenrich2d@gmail.com/
> If yes, we may have to take another look at how to create the interface
> for this.
Pit Henrich's patch targets the ThinkPad X1 Fold 16 Gen 1 — also a
Lenovo device with a magnetically-attached keyboard that docks to the
display and changes between tablet and laptop mode, so the concept is
physically similar.
However, the two drivers differ in several meaningful ways:
* Different hardware families and kernel paths: the X1 Fold patch
extends thinkpad_acpi using an ACPI method (\\_SB.DEVD.GDST) and
the existing TP_HKEY_EV_TABLET_CHANGED hotkey path. The Yoga Book 9
driver is a standalone WMI driver using two separate WMI GUIDs.
* Different state cardinality: the X1 Fold has a binary
keyboard_attached_on_screen attribute because the keyboard is either
present or not. The Yoga Book 9 needs three states — detached,
docked on the top half of the bottom screen, or docked on the bottom
half — because the two docked positions select different screen
layouts that userspace needs to distinguish. A binary attribute
would lose that information.
* SW_TABLET_MODE: both drivers emit SW_TABLET_MODE=1 when the
keyboard is absent and SW_TABLET_MODE=0 when docked, consistent
with existing drivers in this subsystem.
Given these differences the sysfs attribute semantics do not clash, but
if a preferred naming convention is being established for keyboard-dock
attributes across these devices I am happy to align with whatever is
decided for the X1 Fold patch.
Dave
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver
2026-04-28 14:39 ` Ilpo Järvinen
2026-05-17 15:01 ` Dave Carey
@ 2026-05-17 15:02 ` Dave Carey
2026-05-17 15:25 ` sashiko-bot
2026-05-19 0:02 ` [PATCH v3] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
1 sibling, 2 replies; 23+ messages in thread
From: Dave Carey @ 2026-05-17 15:02 UTC (permalink / raw)
To: ilpo.jarvinen
Cc: hdegoede, pithenrich2d, mpearson-lenovo, derekjohn.clark, W_Armin,
platform-driver-x86, linux-input, linux-kernel, Dave Carey
The Lenovo Yoga Book 9 14IAH10 (83KJ) ships with a detachable Bluetooth
keyboard that magnetically attaches to the bottom screen in one of two
positions.
The Embedded Controller tracks the attachment state in a 2-bit field called
BKBD (byte 0x23 of EC RAM, bits 4-5) and signals changes via EC query _QB0,
which calls Notify(WM10, 0xEB) on the WM10 ACPI WMI device (_UID "GMZN").
The WMI event GUID 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 fires on every
state change. The current state is readable at any time via WMI data block
E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF), which returns an 8-byte buffer
whose upper four bytes hold the BKBD value. BKBD encodes the attachment
state as: 0 = keyboard detached, 1 = docked on the top half of the bottom
screen, 2 = docked on the bottom half, 3 = reserved (treated as an error by
this driver).
Add a new driver, lenovo-yb9-kbdock, that queries the BKBD state on probe
and on each WMI event notification. Both docked positions indicate a
physical keyboard is present and report SW_TABLET_MODE=0 (laptop mode);
keyboard detached reports SW_TABLET_MODE=1 (tablet mode). The raw BKBD
value is additionally exposed via the read-only sysfs attribute
"keyboard_position" for userspace that needs to distinguish the two docked
positions (e.g. to choose a different UI layout).
Tested on Lenovo Yoga Book 9 14IAH10 (83KJ): all three BKBD states
(detached, top-half dock, bottom-half dock) reported correctly;
SW_TABLET_MODE transitions verified with evtest.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
drivers/platform/x86/lenovo/Kconfig | 14 ++++
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 233 +++++++++++++++++++++
3 files changed, 248 insertions(+)
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
--- a/drivers/platform/x86/lenovo/yb9-kbdock.c
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * Reports SW_TABLET_MODE based on keyboard attachment state and exposes the
+ * raw dock position via sysfs.
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/wmi.h>
+
+/*
+ * WM10 ACPI device (_UID "GMZN") exposes two relevant WMI GUIDs:
+ * YB9_KBDOCK_EVENT_GUID — notify ID 0xEB fires on attachment state change.
+ * YB9_KBDOCK_QUERY_GUID — object "AF" (WQAF), returns an 8-byte buffer
+ * whose upper four bytes hold the BKBD value.
+ *
+ * BKBD encoding:
+ * 0 (BKBD_DETACHED) — keyboard detached → SW_TABLET_MODE = 1
+ * 1 (BKBD_TOP_HALF) — docked, top half → SW_TABLET_MODE = 0
+ * 2 (BKBD_BOTTOM_HALF) — docked, bottom half → SW_TABLET_MODE = 0
+ * 3 — reserved; treated as an error
+ */
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+#define YB9_KBDOCK_QUERY_INSTANCE 0
+
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1
+#define BKBD_BOTTOM_HALF 2
+#define BKBD_MASK GENMASK(1, 0)
+
+/* Distinguish the two GUIDs via the id_table context field. */
+enum yb9_guid_type { YB9_GUID_EVENT, YB9_GUID_QUERY };
+
+/*
+ * Both GUIDs are children of the same ACPI device (WM10). Store the query
+ * WMI device globally so the event-device probe and notify path can reach it
+ * via wmidev_block_query(). Protected by yb9_query_lock during probe/remove.
+ */
+static struct wmi_device *yb9_query_wdev;
+static DEFINE_MUTEX(yb9_query_lock);
+
+struct yb9_kbdock_priv {
+ struct wmi_device *query_wdev;
+ struct input_dev *input_dev;
+ unsigned int bkbd;
+};
+
+/* Returns 0–2 on success, -errno on error. */
+static int yb9_kbdock_query(struct wmi_device *event_wdev,
+ struct wmi_device *query_wdev)
+{
+ u32 bkbd;
+
+ union acpi_object *obj __free(kfree) =
+ wmidev_block_query(query_wdev, YB9_KBDOCK_QUERY_INSTANCE);
+ if (!obj) {
+ dev_warn(&event_wdev->dev, "WQAF query returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
+ * bytes [4..7] = BKBD value. Guard against short buffers.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8)
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ else if (obj->type == ACPI_TYPE_INTEGER)
+ bkbd = obj->integer.value;
+ else {
+ dev_warn(&event_wdev->dev,
+ "WQAF: unexpected result type %d len %u\n",
+ obj->type, obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
+ return -EIO;
+ }
+
+ bkbd = FIELD_GET(BKBD_MASK, bkbd);
+ if (bkbd == 3) {
+ dev_warn(&event_wdev->dev, "BKBD value 3 is reserved\n");
+ return -EINVAL;
+ }
+
+ return bkbd;
+}
+
+static void yb9_kbdock_update(struct wmi_device *wdev)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
+ int tablet_mode;
+ int bkbd;
+
+ bkbd = yb9_kbdock_query(wdev, priv->query_wdev);
+ if (bkbd < 0)
+ return;
+
+ priv->bkbd = bkbd;
+ tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
+ input_sync(priv->input_dev);
+
+ dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
+}
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ yb9_kbdock_update(wdev);
+}
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ static const char * const names[] = {
+ "detached", "top-half", "bottom-half",
+ };
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
+ unsigned int bkbd = priv->bkbd;
+
+ if (WARN_ON_ONCE(bkbd >= ARRAY_SIZE(names)))
+ return -EINVAL;
+ return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
+{
+ enum yb9_guid_type type = (enum yb9_guid_type)(uintptr_t)ctx;
+ struct yb9_kbdock_priv *priv;
+ struct input_dev *input_dev;
+ struct wmi_device *qwdev;
+ int err;
+
+ if (type == YB9_GUID_QUERY) {
+ mutex_lock(&yb9_query_lock);
+ yb9_query_wdev = wdev;
+ mutex_unlock(&yb9_query_lock);
+ return 0;
+ }
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ mutex_lock(&yb9_query_lock);
+ qwdev = yb9_query_wdev;
+ mutex_unlock(&yb9_query_lock);
+ if (!qwdev)
+ return -EPROBE_DEFER;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->dev.parent = &wdev->dev;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&wdev->dev, "failed to register input device: %d\n", err);
+ return err;
+ }
+
+ priv->query_wdev = qwdev;
+ priv->input_dev = input_dev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ yb9_kbdock_update(wdev);
+ return 0;
+}
+
+static void yb9_kbdock_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9_query_lock);
+ if (wdev == yb9_query_wdev)
+ yb9_query_wdev = NULL;
+ mutex_unlock(&yb9_query_lock);
+}
+
+static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID, .context = (void *)YB9_GUID_EVENT },
+ { .guid_string = YB9_KBDOCK_QUERY_GUID, .context = (void *)YB9_GUID_QUERY },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
+
+static struct wmi_driver yb9_kbdock_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ },
+ .id_table = yb9_kbdock_wmi_id_table,
+ .probe = yb9_kbdock_probe,
+ .remove = yb9_kbdock_remove,
+ .notify = yb9_kbdock_notify,
+};
+module_wmi_driver(yb9_kbdock_driver);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
2.47.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver
2026-05-17 15:02 ` [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Dave Carey
@ 2026-05-17 15:25 ` sashiko-bot
2026-05-19 0:02 ` [PATCH v3] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
1 sibling, 0 replies; 23+ messages in thread
From: sashiko-bot @ 2026-05-17 15:25 UTC (permalink / raw)
To: Dave Carey; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] NULL Pointer Dereference on sysfs read for QUERY device and DMI check bypass
- [High] Use-After-Free due to unreferenced QUERY WMI device pointer
- [Medium] Race condition exposing incorrect initial switch state to userspace
--
commit f26ec3cd843093c54646b94f3331c501f619e886
Author: Dave Carey <carvsdriver@gmail.com>
platform/x86/lenovo: add Yoga Book 9 keyboard dock driver
This commit adds a new driver, lenovo-yb9-kbdock, to support the detachable
keyboard dock on the Lenovo Yoga Book 9 14IAH10. It queries the BKBD state
via WMI to report SW_TABLET_MODE input events and exposes the raw position
to userspace via sysfs.
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000000000..ba0579f7981a3
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
[ ... ]
> +static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + enum yb9_guid_type type = (enum yb9_guid_type)(uintptr_t)ctx;
> + struct yb9_kbdock_priv *priv;
> + struct input_dev *input_dev;
> + struct wmi_device *qwdev;
> + int err;
> +
> + if (type == YB9_GUID_QUERY) {
> + mutex_lock(&yb9_query_lock);
> + yb9_query_wdev = wdev;
> + mutex_unlock(&yb9_query_lock);
> + return 0;
> + }
The wmi_driver struct sets .dev_groups to yb9_kbdock_groups, which means the
keyboard_position sysfs attribute is created for all devices binding to this
driver, including the query WMI device.
If userspace reads this sysfs attribute on the query device, won't
keyboard_position_show() receive a NULL pointer from dev_get_drvdata()
and dereference it when accessing priv->bkbd?
Also, because this returns before dmi_check_system() is called, does this
allow the driver to bind to the query GUID on unsupported hardware?
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
[ ... ]
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err) {
> + dev_err(&wdev->dev, "failed to register input device: %d\n", err);
> + return err;
> + }
> +
> + priv->query_wdev = qwdev;
Is a device link or reference count via get_device() needed here?
If the query WMI device is unbound or its parent ACPI device is hot-removed,
could priv->query_wdev become a dangling pointer that gets used in
yb9_kbdock_notify() when a WMI event fires?
> + priv->input_dev = input_dev;
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + yb9_kbdock_update(wdev);
Could this expose an incorrect initial switch state to userspace?
Since input_register_device() makes the device visible before
yb9_kbdock_update() queries the hardware state, userspace might
read the default laptop mode (0) even if the keyboard is detached.
Can the initial hardware state be queried and reported before registering
the input device?
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260517150224.50191-1-carvsdriver@gmail.com?part=1
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v3] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-17 15:02 ` [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Dave Carey
2026-05-17 15:25 ` sashiko-bot
@ 2026-05-19 0:02 ` Dave Carey
2026-05-21 14:40 ` [PATCH v4] " Dave Carey
1 sibling, 1 reply; 23+ messages in thread
From: Dave Carey @ 2026-05-19 0:02 UTC (permalink / raw)
To: ilpo.jarvinen
Cc: pithenrich2d, mpearson-lenovo, derekjohn.clark, W_Armin,
platform-driver-x86, linux-input, linux-kernel, dmitry.torokhov,
Dave Carey
The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
that magnetically attaches to the bottom (secondary) screen in one of two
positions. The Embedded Controller tracks the attachment state in a 2-bit
field called BKBD and signals changes via WMI event GUID
806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
device).
The current BKBD state is read via a separate WMI query GUID
E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method), which returns an
8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
[4..7] hold the BKBD value.
BKBD encoding:
0 = keyboard detached
1 = keyboard docked on top half of bottom screen
2 = keyboard docked on bottom half of bottom screen
3 = reserved (not observed in practice)
Both GUIDs are children of the same ACPI device (WM10), so both are
matched by a single WMI driver. The query device pointer is stored in a
module-level variable protected by a mutex; the event device uses
wmidev_block_query() via the stored pointer rather than the deprecated
global wmi_query_block(). get_device()/put_device() bracket each use of
the stored pointer so probe/remove races cannot produce a use-after-free.
This driver:
- Registers as a WMI driver on both the event and query GUIDs.
- Queries BKBD state synchronously on probe and on each WMI
notification.
- Sets the initial SW_TABLET_MODE bit before input_register_device()
via __set_bit() so userspace always reads the correct state on first
open.
- Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
in either position (a physical keyboard is present in both cases).
- Exposes the raw BKBD value via a read-only sysfs attribute
"keyboard_position" for use by userspace (e.g. to distinguish between
the two docked positions for different UI layouts). The attribute is
registered per-device via devm_device_add_groups() in the event-device
probe path only; the query device has no priv and no sysfs groups.
Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
.../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
MAINTAINERS | 6 +
drivers/platform/x86/lenovo/Kconfig | 14 ++
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 270 ++++++++++++++++++
5 files changed, 312 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
create mode 100644 MAINTAINERS
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 0000000..bb57690
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,21 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: April 2026
+KernelVersion: 6.10
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == ============================================================
+ 0 detached — keyboard is not docked to any screen
+ 1 top-half — keyboard docked on the top half of the bottom screen
+ 2 bottom-half — keyboard docked on the bottom half of the bottom screen
+ == ============================================================
+
+ The value is formatted as "<n> (<name>)\n", e.g. "1 (top-half)\n".
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..cb765b4
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,6 @@
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 9c48487..938b361 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e..2842d7d 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 0000000..0000000
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * Reports SW_TABLET_MODE based on keyboard attachment state and exposes the
+ * raw dock position via sysfs.
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/wmi.h>
+
+/*
+ * WM10 ACPI device (_UID "GMZN") exposes two relevant WMI GUIDs:
+ * YB9_KBDOCK_EVENT_GUID — notify ID 0xEB fires on attachment state change.
+ * YB9_KBDOCK_QUERY_GUID — object "AF" (WQAF), returns an 8-byte buffer
+ * whose upper four bytes hold the BKBD value.
+ *
+ * BKBD encoding:
+ * 0 (BKBD_DETACHED) — keyboard detached → SW_TABLET_MODE = 1
+ * 1 (BKBD_TOP_HALF) — docked, top half → SW_TABLET_MODE = 0
+ * 2 (BKBD_BOTTOM_HALF) — docked, bottom half → SW_TABLET_MODE = 0
+ * 3 — reserved; treated as an error
+ */
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+#define YB9_KBDOCK_QUERY_INSTANCE 0
+
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1
+#define BKBD_BOTTOM_HALF 2
+#define BKBD_MASK GENMASK(1, 0)
+
+/* Distinguish the two GUIDs via the id_table context field. */
+enum yb9_guid_type { YB9_GUID_EVENT, YB9_GUID_QUERY };
+
+/*
+ * Both GUIDs are children of the same ACPI device (WM10). Store the query
+ * WMI device globally so the event-device probe and notify path can reach it
+ * via wmidev_block_query(). Protected by yb9_query_lock during probe/remove.
+ */
+static struct wmi_device *yb9_query_wdev;
+static DEFINE_MUTEX(yb9_query_lock);
+
+struct yb9_kbdock_priv {
+ struct input_dev *input_dev;
+ unsigned int bkbd;
+};
+
+/* Returns 0–2 on success, -errno on error. */
+static int yb9_kbdock_query(struct wmi_device *event_wdev,
+ struct wmi_device *query_wdev)
+{
+ u32 bkbd;
+
+ union acpi_object *obj __free(kfree) =
+ wmidev_block_query(query_wdev, YB9_KBDOCK_QUERY_INSTANCE);
+ if (!obj) {
+ dev_warn(&event_wdev->dev, "WQAF query returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
+ * bytes [4..7] = BKBD value. Guard against short buffers.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8)
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ else if (obj->type == ACPI_TYPE_INTEGER)
+ bkbd = obj->integer.value;
+ else {
+ dev_warn(&event_wdev->dev,
+ "WQAF: unexpected result type %d len %u\n",
+ obj->type, obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
+ return -EIO;
+ }
+
+ bkbd = FIELD_GET(BKBD_MASK, bkbd);
+ if (bkbd == 3) {
+ dev_warn(&event_wdev->dev, "BKBD value 3 is reserved\n");
+ return -EINVAL;
+ }
+
+ return bkbd;
+}
+
+static void yb9_kbdock_update(struct wmi_device *wdev)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
+ struct wmi_device *qwdev;
+ int tablet_mode;
+ int bkbd;
+
+ mutex_lock(&yb9_query_lock);
+ qwdev = yb9_query_wdev;
+ if (qwdev)
+ get_device(&qwdev->dev);
+ mutex_unlock(&yb9_query_lock);
+ if (!qwdev)
+ return;
+
+ bkbd = yb9_kbdock_query(wdev, qwdev);
+ put_device(&qwdev->dev);
+ if (bkbd < 0)
+ return;
+
+ priv->bkbd = bkbd;
+ tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
+ input_sync(priv->input_dev);
+
+ dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
+}
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ yb9_kbdock_update(wdev);
+}
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ static const char * const names[] = {
+ "detached", "top-half", "bottom-half",
+ };
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
+ unsigned int bkbd = priv->bkbd;
+
+ if (WARN_ON_ONCE(bkbd >= ARRAY_SIZE(names)))
+ return -EINVAL;
+ return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+
+static const struct attribute_group yb9_kbdock_group = {
+ .attrs = yb9_kbdock_attrs,
+};
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
+{
+ enum yb9_guid_type type = (enum yb9_guid_type)(uintptr_t)ctx;
+ struct yb9_kbdock_priv *priv;
+ struct input_dev *input_dev;
+ struct wmi_device *qwdev;
+ int bkbd_init;
+ int err;
+
+ if (type == YB9_GUID_QUERY) {
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+ mutex_lock(&yb9_query_lock);
+ yb9_query_wdev = wdev;
+ mutex_unlock(&yb9_query_lock);
+ return 0;
+ }
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ mutex_lock(&yb9_query_lock);
+ qwdev = yb9_query_wdev;
+ if (qwdev)
+ get_device(&qwdev->dev);
+ mutex_unlock(&yb9_query_lock);
+ if (!qwdev)
+ return -EPROBE_DEFER;
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ put_device(&qwdev->dev);
+ return -ENOMEM;
+ }
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev) {
+ put_device(&qwdev->dev);
+ return -ENOMEM;
+ }
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->dev.parent = &wdev->dev;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ priv->input_dev = input_dev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ /*
+ * Query the initial dock state and preset the switch bit before
+ * input_register_device() so userspace never sees SW_TABLET_MODE = 0
+ * for a detached keyboard on first open.
+ */
+ bkbd_init = yb9_kbdock_query(wdev, qwdev);
+ put_device(&qwdev->dev);
+ if (bkbd_init >= 0) {
+ priv->bkbd = bkbd_init;
+ if (bkbd_init == BKBD_DETACHED)
+ __set_bit(SW_TABLET_MODE, input_dev->sw);
+ }
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&wdev->dev, "failed to register input device: %d\n", err);
+ return err;
+ }
+
+ err = devm_device_add_group(&wdev->dev, &yb9_kbdock_group);
+ if (err) {
+ dev_err(&wdev->dev, "failed to add sysfs group: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static void yb9_kbdock_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9_query_lock);
+ if (wdev == yb9_query_wdev)
+ yb9_query_wdev = NULL;
+ mutex_unlock(&yb9_query_lock);
+}
+
+static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID, .context = (void *)YB9_GUID_EVENT },
+ { .guid_string = YB9_KBDOCK_QUERY_GUID, .context = (void *)YB9_GUID_QUERY },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
+
+static struct wmi_driver yb9_kbdock_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ },
+ .id_table = yb9_kbdock_wmi_id_table,
+ .probe = yb9_kbdock_probe,
+ .remove = yb9_kbdock_remove,
+ .notify = yb9_kbdock_notify,
+};
+module_wmi_driver(yb9_kbdock_driver);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v4] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-19 0:02 ` [PATCH v3] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
@ 2026-05-21 14:40 ` Dave Carey
2026-05-22 21:59 ` Armin Wolf
2026-05-27 12:27 ` [PATCH v5] " Dave Carey
0 siblings, 2 replies; 23+ messages in thread
From: Dave Carey @ 2026-05-21 14:40 UTC (permalink / raw)
To: platform-driver-x86; +Cc: ilpo.jarvinen, carvsdriver
The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
that magnetically attaches to the bottom (secondary) screen in one of two
positions. The Embedded Controller tracks the attachment state in a 2-bit
field called BKBD and signals changes via WMI event GUID
806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
device).
The current BKBD state is read via WMI query GUID
E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method), which returns an
8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
[4..7] hold the BKBD value.
BKBD encoding:
0 = keyboard detached
1 = keyboard docked on top half of bottom screen
2 = keyboard docked on bottom half of bottom screen
3 = reserved (not observed in practice)
This driver:
- Registers as a WMI driver on the event GUID.
- Queries BKBD state on probe and on each WMI notification.
- Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
in either position (a physical keyboard is present in both cases).
- Exposes the raw BKBD value via a read-only sysfs attribute
"keyboard_position" for use by userspace (e.g. to distinguish between
the two docked positions for different UI layouts).
Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
.../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
MAINTAINERS | 7 +
drivers/platform/x86/lenovo/Kconfig | 14 ++
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 216 ++++++++++++++++++
5 files changed, 259 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 0000000..bb57690
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,21 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: April 2026
+KernelVersion: 6.10
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == ============================================================
+ 0 detached — keyboard is not docked to any screen
+ 1 top-half — keyboard docked on the top half of the bottom screen
+ 2 bottom-half — keyboard docked on the bottom half of the bottom screen
+ == ============================================================
+
+ The value is formatted as "<n> (<name>)\n", e.g. "1 (top-half)\n".
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e1..00e8275 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
+
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 9c48487..938b361 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e..2842d7d 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 0000000..693e287
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device.
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * This driver registers for the WMI event GUID, queries BKBD on probe and on
+ * each event, reports SW_TABLET_MODE=0 when the keyboard is docked (either
+ * position) and SW_TABLET_MODE=1 when detached, and exposes the raw BKBD
+ * value in sysfs as "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/wmi.h>
+
+/*
+ * WM10 ACPI device (_UID "GMZN"):
+ * Event GUID — notify ID 0xEB fires on keyboard attachment change.
+ * Query GUID — object "AF", maps to WQAF(); returns 8-byte buffer
+ * {LFID=0x00060000, BKBD[31:0]}.
+ */
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+#define YB9_KBDOCK_QUERY_INSTANCE 0
+
+/* BKBD encoding — keyboard always docks on the bottom screen */
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1 /* docked on top half of bottom screen */
+#define BKBD_BOTTOM_HALF 2 /* docked on bottom half of bottom screen */
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+struct yb9_kbdock_priv {
+ struct input_dev *input_dev;
+ unsigned int bkbd; /* last read BKBD value (0-3) */
+};
+
+/* Read current BKBD state via WQAF. Returns 0-3 or -errno. */
+static int yb9_kbdock_query(struct wmi_device *wdev)
+{
+ struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *obj;
+ acpi_status status;
+ u32 bkbd;
+
+ status = wmi_query_block(YB9_KBDOCK_QUERY_GUID,
+ YB9_KBDOCK_QUERY_INSTANCE, &out);
+ if (ACPI_FAILURE(status)) {
+ dev_warn(&wdev->dev, "WQAF query failed: %s\n",
+ acpi_format_exception(status));
+ return -EIO;
+ }
+
+ obj = out.pointer;
+ if (!obj) {
+ dev_warn(&wdev->dev, "WQAF returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
+ * bytes [4..7] = BKBD value. Guard against short buffers.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ bkbd &= 0x3;
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ bkbd = obj->integer.value & 0x3;
+ } else {
+ dev_warn(&wdev->dev,
+ "WQAF: unexpected result type %d len %u\n",
+ obj->type,
+ obj->type == ACPI_TYPE_BUFFER
+ ? obj->buffer.length : 0);
+ kfree(obj);
+ return -EIO;
+ }
+
+ kfree(obj);
+ return (int)bkbd;
+}
+
+static void yb9_kbdock_update(struct wmi_device *wdev)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
+ int bkbd;
+ int tablet_mode;
+
+ bkbd = yb9_kbdock_query(wdev);
+ if (bkbd < 0)
+ return;
+
+ priv->bkbd = bkbd;
+
+ /*
+ * Report tablet mode only when the keyboard is fully detached.
+ * Both docked positions (top-half and bottom-half of the bottom screen)
+ * indicate a physical keyboard is present — report laptop mode.
+ */
+ tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
+ input_sync(priv->input_dev);
+
+ dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
+}
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ yb9_kbdock_update(wdev);
+}
+
+/* sysfs: keyboard_position — exposes raw BKBD value */
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
+ static const char * const names[] = {
+ "detached", "top-half", "bottom-half", "unknown"
+ };
+ unsigned int bkbd = priv->bkbd;
+
+ if (bkbd > 3)
+ bkbd = 3;
+ return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct yb9_kbdock_priv *priv;
+ struct input_dev *input_dev;
+ int err;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table)) {
+ dev_dbg(&wdev->dev, "not a Yoga Book 9, skipping\n");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ err = input_register_device(input_dev);
+ if (err) {
+ dev_err(&wdev->dev, "failed to register input device: %d\n", err);
+ return err;
+ }
+
+ priv->input_dev = input_dev;
+ dev_set_drvdata(&wdev->dev, priv);
+
+ /* Report initial state */
+ yb9_kbdock_update(wdev);
+ return 0;
+}
+
+static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
+
+static struct wmi_driver yb9_kbdock_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ },
+ .id_table = yb9_kbdock_wmi_id_table,
+ .probe = yb9_kbdock_probe,
+ .notify = yb9_kbdock_notify,
+};
+module_wmi_driver(yb9_kbdock_driver);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v4] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-21 14:40 ` [PATCH v4] " Dave Carey
@ 2026-05-22 21:59 ` Armin Wolf
2026-05-26 13:38 ` Dave Carey
2026-05-27 12:27 ` [PATCH v5] " Dave Carey
1 sibling, 1 reply; 23+ messages in thread
From: Armin Wolf @ 2026-05-22 21:59 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86; +Cc: ilpo.jarvinen
Am 21.05.26 um 16:40 schrieb Dave Carey:
> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
> that magnetically attaches to the bottom (secondary) screen in one of two
> positions. The Embedded Controller tracks the attachment state in a 2-bit
> field called BKBD and signals changes via WMI event GUID
> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
> device).
>
> The current BKBD state is read via WMI query GUID
> E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method), which returns an
> 8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
> [4..7] hold the BKBD value.
Hi,
does the device contain embedded BMOF data? Take a look at Documentation/wmi/driver-development-guide.rst
to find out how to decode said BMOF data.
>
> BKBD encoding:
> 0 = keyboard detached
> 1 = keyboard docked on top half of bottom screen
> 2 = keyboard docked on bottom half of bottom screen
> 3 = reserved (not observed in practice)
>
> This driver:
> - Registers as a WMI driver on the event GUID.
> - Queries BKBD state on probe and on each WMI notification.
> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
> in either position (a physical keyboard is present in both cases).
> - Exposes the raw BKBD value via a read-only sysfs attribute
> "keyboard_position" for use by userspace (e.g. to distinguish between
> the two docked positions for different UI layouts).
>
> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> ---
> .../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
> MAINTAINERS | 7 +
> drivers/platform/x86/lenovo/Kconfig | 14 ++
> drivers/platform/x86/lenovo/Makefile | 1 +
> drivers/platform/x86/lenovo/yb9-kbdock.c | 216 ++++++++++++++++++
> 5 files changed, 259 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> new file mode 100644
> index 0000000..bb57690
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> @@ -0,0 +1,21 @@
> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
> +Date: April 2026
> +KernelVersion: 6.10
> +Contact: Dave Carey <carvsdriver@gmail.com>
> +Description:
> + Read-only attribute reporting the current keyboard dock position
> + as reported by the Embedded Controller on the Lenovo Yoga Book 9
> + 14IAH10.
> +
> + Possible values:
> +
> + == ============================================================
> + 0 detached — keyboard is not docked to any screen
> + 1 top-half — keyboard docked on the top half of the bottom screen
> + 2 bottom-half — keyboard docked on the bottom half of the bottom screen
> + == ============================================================
> +
> + The value is formatted as "<n> (<name>)\n", e.g. "1 (top-half)\n"
IMHO a simple number would be much easier to parse.
> +
> + SW_TABLET_MODE input events are also emitted: 0 when the keyboard
> + is docked (either position), 1 when detached.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1cc0e1..00e8275 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
>
> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
> +M: Dave Carey <carvsdriver@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hansg@kernel.org>
> L: linux-input@vger.kernel.org
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 9c48487..938b361 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
> To compile this driver as a module, choose M here: the module
> will be called lenovo-wmi-camera.
>
> +config LENOVO_YB9_KBDOCK
> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
> + depends on ACPI_WMI
> + depends on DMI
> + depends on INPUT
> + help
> + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
> + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
> + either screen; this driver reports SW_TABLET_MODE input events based
> + on the attachment state and exposes the raw position in sysfs.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called lenovo-yb9-kbdock.
> +
> config LENOVO_YMC
> tristate "Lenovo Yoga Tablet Mode Control"
> depends on ACPI_WMI
> diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
> index 7b2128e..2842d7d 100644
> --- a/drivers/platform/x86/lenovo/Makefile
> +++ b/drivers/platform/x86/lenovo/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>
> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
> lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000..693e287
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
> @@ -0,0 +1,216 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Yoga Book 9 keyboard-dock detection
> + *
> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
> + * attaches to the bottom screen in one of two positions. The EC tracks
> + * attachment state in a 2-bit field called BKBD and signals changes via WMI
> + * event 0xEB on the WM10 ACPI device.
> + *
> + * BKBD values:
> + * 0 = keyboard detached
> + * 1 = keyboard docked on the top half of the bottom screen
> + * 2 = keyboard docked on the bottom half of the bottom screen
> + * 3 = reserved / not observed
> + *
> + * This driver registers for the WMI event GUID, queries BKBD on probe and on
> + * each event, reports SW_TABLET_MODE=0 when the keyboard is docked (either
> + * position) and SW_TABLET_MODE=1 when detached, and exposes the raw BKBD
> + * value in sysfs as "keyboard_position".
> + *
> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/wmi.h>
> +
> +/*
> + * WM10 ACPI device (_UID "GMZN"):
> + * Event GUID — notify ID 0xEB fires on keyboard attachment change.
> + * Query GUID — object "AF", maps to WQAF(); returns 8-byte buffer
> + * {LFID=0x00060000, BKBD[31:0]}.
> + */
> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
> +
> +#define YB9_KBDOCK_QUERY_INSTANCE 0
> +
> +/* BKBD encoding — keyboard always docks on the bottom screen */
> +#define BKBD_DETACHED 0
> +#define BKBD_TOP_HALF 1 /* docked on top half of bottom screen */
> +#define BKBD_BOTTOM_HALF 2 /* docked on bottom half of bottom screen */
> +
> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
> + {
> + /* Lenovo Yoga Book 9 14IAH10 */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> +struct yb9_kbdock_priv {
> + struct input_dev *input_dev;
> + unsigned int bkbd; /* last read BKBD value (0-3) */
> +};
> +
> +/* Read current BKBD state via WQAF. Returns 0-3 or -errno. */
> +static int yb9_kbdock_query(struct wmi_device *wdev)
> +{
> + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
> + union acpi_object *obj;
> + acpi_status status;
> + u32 bkbd;
> +
> + status = wmi_query_block(YB9_KBDOCK_QUERY_GUID,
> + YB9_KBDOCK_QUERY_INSTANCE, &out);
That method is deprecated, please use wmidev_query_block(); I suggest you use a notifier
to connect both the WMI event device and the WMI blockd device. Take a look at drivers/platform/
x86/uniwill/uniwill-wmi.c for example.
> + if (ACPI_FAILURE(status)) {
> + dev_warn(&wdev->dev, "WQAF query failed: %s\n",
> + acpi_format_exception(status));
> + return -EIO;
> + }
> +
> + obj = out.pointer;
> + if (!obj) {
> + dev_warn(&wdev->dev, "WQAF returned NULL\n");
> + return -EIO;
> + }
> +
> + /*
> + * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
> + * bytes [4..7] = BKBD value. Guard against short buffers.
> + */
> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
> + bkbd &= 0x3;
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + bkbd = obj->integer.value & 0x3;
> + } else {
> + dev_warn(&wdev->dev,
> + "WQAF: unexpected result type %d len %u\n",
> + obj->type,
> + obj->type == ACPI_TYPE_BUFFER
> + ? obj->buffer.length : 0);
> + kfree(obj);
> + return -EIO;
> + }
> +
> + kfree(obj);
> + return (int)bkbd;
> +}
> +
> +static void yb9_kbdock_update(struct wmi_device *wdev)
> +{
> + struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
> + int bkbd;
> + int tablet_mode;
> +
> + bkbd = yb9_kbdock_query(wdev);
> + if (bkbd < 0)
> + return;
> +
> + priv->bkbd = bkbd;
> +
> + /*
> + * Report tablet mode only when the keyboard is fully detached.
> + * Both docked positions (top-half and bottom-half of the bottom screen)
> + * indicate a physical keyboard is present — report laptop mode.
> + */
> + tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
> +
> + input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
> + input_sync(priv->input_dev);
> +
> + dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
> +}
> +
> +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + yb9_kbdock_update(wdev);
> +}
> +
> +/* sysfs: keyboard_position — exposes raw BKBD value */
> +static ssize_t keyboard_position_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
> + static const char * const names[] = {
> + "detached", "top-half", "bottom-half", "unknown"
> + };
> + unsigned int bkbd = priv->bkbd;
> +
> + if (bkbd > 3)
> + bkbd = 3;
I think you should protect bkbd with a spinlock. Or you could just read the current value
from the hardware directly.
> + return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
> +}
> +static DEVICE_ATTR_RO(keyboard_position);
> +
> +static struct attribute *yb9_kbdock_attrs[] = {
> + &dev_attr_keyboard_position.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(yb9_kbdock);
> +
> +static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct yb9_kbdock_priv *priv;
> + struct input_dev *input_dev;
> + int err;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table)) {
> + dev_dbg(&wdev->dev, "not a Yoga Book 9, skipping\n");
> + return -ENODEV;
> + }
> +
> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err) {
> + dev_err(&wdev->dev, "failed to register input device: %d\n", err);
> + return err;
> + }
> +
> + priv->input_dev = input_dev;
> + dev_set_drvdata(&wdev->dev, priv);
> +
> + /* Report initial state */
> + yb9_kbdock_update(wdev);
> + return 0;
> +}
> +
> +static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
> +
> +static struct wmi_driver yb9_kbdock_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock",
> + .dev_groups = yb9_kbdock_groups,
> + },
> + .id_table = yb9_kbdock_wmi_id_table,
> + .probe = yb9_kbdock_probe,
> + .notify = yb9_kbdock_notify,
Please set .no_singleton = true. Also please update the docking state when resuming from suspend or hibernation.
Thanks,
Armin Wolf
> +};
> +module_wmi_driver(yb9_kbdock_driver);
> +
> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v4] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-22 21:59 ` Armin Wolf
@ 2026-05-26 13:38 ` Dave Carey
0 siblings, 0 replies; 23+ messages in thread
From: Dave Carey @ 2026-05-26 13:38 UTC (permalink / raw)
To: Armin Wolf, platform-driver-x86; +Cc: ilpo.jarvinen
ack. will review your feedback. thank you.
On 5/22/26 5:59 PM, Armin Wolf wrote:
> Am 21.05.26 um 16:40 schrieb Dave Carey:
>
>> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth
>> keyboard
>> that magnetically attaches to the bottom (secondary) screen in one of
>> two
>> positions. The Embedded Controller tracks the attachment state in a
>> 2-bit
>> field called BKBD and signals changes via WMI event GUID
>> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
>> device).
>>
>> The current BKBD state is read via WMI query GUID
>> E7F300FA-21CD-4003-ADAC-2696135982E6 (WQAF method), which returns an
>> 8-byte buffer: bytes [0..3] hold the LFID constant 0x00060000 and bytes
>> [4..7] hold the BKBD value.
>
> Hi,
>
> does the device contain embedded BMOF data? Take a look at
> Documentation/wmi/driver-development-guide.rst
> to find out how to decode said BMOF data.
>
>>
>> BKBD encoding:
>> 0 = keyboard detached
>> 1 = keyboard docked on top half of bottom screen
>> 2 = keyboard docked on bottom half of bottom screen
>> 3 = reserved (not observed in practice)
>>
>> This driver:
>> - Registers as a WMI driver on the event GUID.
>> - Queries BKBD state on probe and on each WMI notification.
>> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when
>> docked
>> in either position (a physical keyboard is present in both cases).
>> - Exposes the raw BKBD value via a read-only sysfs attribute
>> "keyboard_position" for use by userspace (e.g. to distinguish
>> between
>> the two docked positions for different UI layouts).
>>
>> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 6.19.
>>
>> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
>> ---
>> .../testing/sysfs-driver-lenovo-yb9-kbdock | 21 ++
>> MAINTAINERS | 7 +
>> drivers/platform/x86/lenovo/Kconfig | 14 ++
>> drivers/platform/x86/lenovo/Makefile | 1 +
>> drivers/platform/x86/lenovo/yb9-kbdock.c | 216 ++++++++++++++++++
>> 5 files changed, 259 insertions(+)
>> create mode 100644
>> Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
>> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>>
>> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
>> b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
>> new file mode 100644
>> index 0000000..bb57690
>> --- /dev/null
>> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
>> @@ -0,0 +1,21 @@
>> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
>> +Date: April 2026
>> +KernelVersion: 6.10
>> +Contact: Dave Carey <carvsdriver@gmail.com>
>> +Description:
>> + Read-only attribute reporting the current keyboard dock
>> position
>> + as reported by the Embedded Controller on the Lenovo Yoga
>> Book 9
>> + 14IAH10.
>> +
>> + Possible values:
>> +
>> + == ============================================================
>> + 0 detached — keyboard is not docked to any screen
>> + 1 top-half — keyboard docked on the top half of the
>> bottom screen
>> + 2 bottom-half — keyboard docked on the bottom half of the
>> bottom screen
>> + == ============================================================
>> +
>> + The value is formatted as "<n> (<name>)\n", e.g. "1
>> (top-half)\n"
>
> IMHO a simple number would be much easier to parse.
>
>> +
>> + SW_TABLET_MODE input events are also emitted: 0 when the
>> keyboard
>> + is docked (either position), 1 when detached.
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index d1cc0e1..00e8275 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
>> S: Maintained
>> F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
>> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
>> +M: Dave Carey <carvsdriver@gmail.com>
>> +L: platform-driver-x86@vger.kernel.org
>> +S: Maintained
>> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
>> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
>> +
>> LETSKETCH HID TABLET DRIVER
>> M: Hans de Goede <hansg@kernel.org>
>> L: linux-input@vger.kernel.org
>> diff --git a/drivers/platform/x86/lenovo/Kconfig
>> b/drivers/platform/x86/lenovo/Kconfig
>> index 9c48487..938b361 100644
>> --- a/drivers/platform/x86/lenovo/Kconfig
>> +++ b/drivers/platform/x86/lenovo/Kconfig
>> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
>> To compile this driver as a module, choose M here: the module
>> will be called lenovo-wmi-camera.
>> +config LENOVO_YB9_KBDOCK
>> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
>> + depends on ACPI_WMI
>> + depends on DMI
>> + depends on INPUT
>> + help
>> + Say Y here to enable keyboard dock detection on the Lenovo
>> Yoga Book 9
>> + 14IAH10. The detachable Bluetooth keyboard magnetically
>> attaches to
>> + either screen; this driver reports SW_TABLET_MODE input events
>> based
>> + on the attachment state and exposes the raw position in sysfs.
>> +
>> + To compile this driver as a module, choose M here: the module
>> will be
>> + called lenovo-yb9-kbdock.
>> +
>> config LENOVO_YMC
>> tristate "Lenovo Yoga Tablet Mode Control"
>> depends on ACPI_WMI
>> diff --git a/drivers/platform/x86/lenovo/Makefile
>> b/drivers/platform/x86/lenovo/Makefile
>> index 7b2128e..2842d7d 100644
>> --- a/drivers/platform/x86/lenovo/Makefile
>> +++ b/drivers/platform/x86/lenovo/Makefile
>> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
>> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) +=
>> wmi-hotkey-utilities.o
>> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
>> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
>> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
>> lenovo-target-$(CONFIG_YT2_1380) +=
>> yoga-tab2-pro-1380-fastcharger.o
>> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c
>> b/drivers/platform/x86/lenovo/yb9-kbdock.c
>> new file mode 100644
>> index 0000000..693e287
>> --- /dev/null
>> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
>> @@ -0,0 +1,216 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Lenovo Yoga Book 9 keyboard-dock detection
>> + *
>> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that
>> magnetically
>> + * attaches to the bottom screen in one of two positions. The EC
>> tracks
>> + * attachment state in a 2-bit field called BKBD and signals changes
>> via WMI
>> + * event 0xEB on the WM10 ACPI device.
>> + *
>> + * BKBD values:
>> + * 0 = keyboard detached
>> + * 1 = keyboard docked on the top half of the bottom screen
>> + * 2 = keyboard docked on the bottom half of the bottom screen
>> + * 3 = reserved / not observed
>> + *
>> + * This driver registers for the WMI event GUID, queries BKBD on
>> probe and on
>> + * each event, reports SW_TABLET_MODE=0 when the keyboard is docked
>> (either
>> + * position) and SW_TABLET_MODE=1 when detached, and exposes the raw
>> BKBD
>> + * value in sysfs as "keyboard_position".
>> + *
>> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
>> + */
>> +
>> +#include <linux/acpi.h>
>> +#include <linux/dmi.h>
>> +#include <linux/input.h>
>> +#include <linux/module.h>
>> +#include <linux/wmi.h>
>> +
>> +/*
>> + * WM10 ACPI device (_UID "GMZN"):
>> + * Event GUID — notify ID 0xEB fires on keyboard attachment change.
>> + * Query GUID — object "AF", maps to WQAF(); returns 8-byte buffer
>> + * {LFID=0x00060000, BKBD[31:0]}.
>> + */
>> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
>> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
>> +
>> +#define YB9_KBDOCK_QUERY_INSTANCE 0
>> +
>> +/* BKBD encoding — keyboard always docks on the bottom screen */
>> +#define BKBD_DETACHED 0
>> +#define BKBD_TOP_HALF 1 /* docked on top half of bottom
>> screen */
>> +#define BKBD_BOTTOM_HALF 2 /* docked on bottom half of bottom
>> screen */
>> +
>> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
>> + {
>> + /* Lenovo Yoga Book 9 14IAH10 */
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
>> + },
>> + },
>> + { }
>> +};
>> +
>> +struct yb9_kbdock_priv {
>> + struct input_dev *input_dev;
>> + unsigned int bkbd; /* last read BKBD value (0-3) */
>> +};
>> +
>> +/* Read current BKBD state via WQAF. Returns 0-3 or -errno. */
>> +static int yb9_kbdock_query(struct wmi_device *wdev)
>> +{
>> + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
>> + union acpi_object *obj;
>> + acpi_status status;
>> + u32 bkbd;
>> +
>> + status = wmi_query_block(YB9_KBDOCK_QUERY_GUID,
>> + YB9_KBDOCK_QUERY_INSTANCE, &out);
>
> That method is deprecated, please use wmidev_query_block(); I suggest
> you use a notifier
> to connect both the WMI event device and the WMI blockd device. Take a
> look at drivers/platform/
> x86/uniwill/uniwill-wmi.c for example.
>
>> + if (ACPI_FAILURE(status)) {
>> + dev_warn(&wdev->dev, "WQAF query failed: %s\n",
>> + acpi_format_exception(status));
>> + return -EIO;
>> + }
>> +
>> + obj = out.pointer;
>> + if (!obj) {
>> + dev_warn(&wdev->dev, "WQAF returned NULL\n");
>> + return -EIO;
>> + }
>> +
>> + /*
>> + * WQAF returns an 8-byte buffer: bytes [0..3] = LFID (0x00060000),
>> + * bytes [4..7] = BKBD value. Guard against short buffers.
>> + */
>> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
>> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
>> + bkbd &= 0x3;
>> + } else if (obj->type == ACPI_TYPE_INTEGER) {
>> + bkbd = obj->integer.value & 0x3;
>> + } else {
>> + dev_warn(&wdev->dev,
>> + "WQAF: unexpected result type %d len %u\n",
>> + obj->type,
>> + obj->type == ACPI_TYPE_BUFFER
>> + ? obj->buffer.length : 0);
>> + kfree(obj);
>> + return -EIO;
>> + }
>> +
>> + kfree(obj);
>> + return (int)bkbd;
>> +}
>> +
>> +static void yb9_kbdock_update(struct wmi_device *wdev)
>> +{
>> + struct yb9_kbdock_priv *priv = dev_get_drvdata(&wdev->dev);
>> + int bkbd;
>> + int tablet_mode;
>> +
>> + bkbd = yb9_kbdock_query(wdev);
>> + if (bkbd < 0)
>> + return;
>> +
>> + priv->bkbd = bkbd;
>> +
>> + /*
>> + * Report tablet mode only when the keyboard is fully detached.
>> + * Both docked positions (top-half and bottom-half of the bottom
>> screen)
>> + * indicate a physical keyboard is present — report laptop mode.
>> + */
>> + tablet_mode = (bkbd == BKBD_DETACHED) ? 1 : 0;
>> +
>> + input_report_switch(priv->input_dev, SW_TABLET_MODE, tablet_mode);
>> + input_sync(priv->input_dev);
>> +
>> + dev_dbg(&wdev->dev, "BKBD=%u tablet_mode=%d\n", bkbd, tablet_mode);
>> +}
>> +
>> +static void yb9_kbdock_notify(struct wmi_device *wdev, union
>> acpi_object *data)
>> +{
>> + yb9_kbdock_update(wdev);
>> +}
>> +
>> +/* sysfs: keyboard_position — exposes raw BKBD value */
>> +static ssize_t keyboard_position_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct yb9_kbdock_priv *priv = dev_get_drvdata(dev);
>> + static const char * const names[] = {
>> + "detached", "top-half", "bottom-half", "unknown"
>> + };
>> + unsigned int bkbd = priv->bkbd;
>> +
>> + if (bkbd > 3)
>> + bkbd = 3;
>
> I think you should protect bkbd with a spinlock. Or you could just
> read the current value
> from the hardware directly.
>
>> + return sysfs_emit(buf, "%u (%s)\n", bkbd, names[bkbd]);
>> +}
>> +static DEVICE_ATTR_RO(keyboard_position);
>> +
>> +static struct attribute *yb9_kbdock_attrs[] = {
>> + &dev_attr_keyboard_position.attr,
>> + NULL,
>> +};
>> +ATTRIBUTE_GROUPS(yb9_kbdock);
>> +
>> +static int yb9_kbdock_probe(struct wmi_device *wdev, const void *ctx)
>> +{
>> + struct yb9_kbdock_priv *priv;
>> + struct input_dev *input_dev;
>> + int err;
>> +
>> + if (!dmi_check_system(yb9_kbdock_dmi_table)) {
>> + dev_dbg(&wdev->dev, "not a Yoga Book 9, skipping\n");
>> + return -ENODEV;
>> + }
>> +
>> + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
>> + if (!priv)
>> + return -ENOMEM;
>> +
>> + input_dev = devm_input_allocate_device(&wdev->dev);
>> + if (!input_dev)
>> + return -ENOMEM;
>> +
>> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
>> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
>> + input_dev->id.bustype = BUS_HOST;
>> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
>> +
>> + err = input_register_device(input_dev);
>> + if (err) {
>> + dev_err(&wdev->dev, "failed to register input device: %d\n",
>> err);
>> + return err;
>> + }
>> +
>> + priv->input_dev = input_dev;
>> + dev_set_drvdata(&wdev->dev, priv);
>> +
>> + /* Report initial state */
>> + yb9_kbdock_update(wdev);
>> + return 0;
>> +}
>> +
>> +static const struct wmi_device_id yb9_kbdock_wmi_id_table[] = {
>> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_wmi_id_table);
>> +
>> +static struct wmi_driver yb9_kbdock_driver = {
>> + .driver = {
>> + .name = "lenovo-yb9-kbdock",
>> + .dev_groups = yb9_kbdock_groups,
>> + },
>> + .id_table = yb9_kbdock_wmi_id_table,
>> + .probe = yb9_kbdock_probe,
>> + .notify = yb9_kbdock_notify,
>
> Please set .no_singleton = true. Also please update the docking state
> when resuming from suspend or hibernation.
>
> Thanks,
> Armin Wolf
>
>> +};
>> +module_wmi_driver(yb9_kbdock_driver);
>> +
>> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
>> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
>> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-21 14:40 ` [PATCH v4] " Dave Carey
2026-05-22 21:59 ` Armin Wolf
@ 2026-05-27 12:27 ` Dave Carey
2026-06-08 9:13 ` johannes.goede
1 sibling, 1 reply; 23+ messages in thread
From: Dave Carey @ 2026-05-27 12:27 UTC (permalink / raw)
To: platform-driver-x86
Cc: hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey
The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
that magnetically attaches to the bottom (secondary) screen in one of two
positions. The Embedded Controller tracks the attachment state in a 2-bit
field called BKBD and signals changes via WMI event GUID
806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
device, _UID "GMZN").
The device contains embedded BMOF data (WQDD, 20705 bytes) documenting
both WMI interfaces used by this driver:
LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status.
The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer,
so the notify callback receives BKBD without a separate query.
LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an
8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}.
Used for the initial state read on probe and after resume.
BKBD encoding:
0 = keyboard detached
1 = keyboard docked on top half of bottom screen
2 = keyboard docked on bottom half of bottom screen
3 = reserved (not observed in practice)
This driver:
- Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT)
and one on the block GUID (LENOVO_FEATURE_STATUS_DATA).
- On probe, reads initial BKBD state via wmidev_block_query() on the
block device; whichever driver probes last triggers the initial read.
- On WMI notification, reads BKBD directly from the event data integer
(ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call.
- Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
in either position (a physical keyboard is present in both cases).
- Exposes the raw BKBD value via read-only sysfs attribute
"keyboard_position".
- Re-reads BKBD state on resume from suspend or hibernation.
Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
v5:
- Rewrote as two WMI drivers (event + block) to avoid the deprecated
wmi_query_block() API: event driver owns the GUID that delivers
notifications, block driver owns the GUID used for querying state.
Either can probe first; both probe callbacks check whether the other
has already probed and fire the initial read when both are ready.
- Use wmidev_block_query() on the block wmi_device directly (replaces
the deprecated wmi_query_block()).
- In the notify callback, read BKBD directly from the event data integer
passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by
decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF
call that v4 made on every notification.
- Documented the two BMOF classes (LENOVO_BTKBD_EVENT and
LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message
and file header.
- sysfs keyboard_position: output bare integer, drop the parenthetical
position-name suffix that a couple of reviewers found non-standard.
- Set .no_singleton = true on both WMI drivers (was missing from block
driver in v4).
- Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state
after suspend/hibernation.
v4:
- Dropped module_wmi_driver(); registered two WMI drivers manually to
allow sharing state. module_init/exit pair registers/unregisters
both.
- Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute.
- sysfs show: performed a live query instead of returning a cached value.
v3:
- Switched to devm_input_allocate_device().
- Added DMI guard (dmi_check_system) to reject non-YB9 machines.
- Removed redundant MODULE_DEVICE_TABLE on block driver.
v2:
- Added .no_singleton = true on the event WMI driver.
- Added PM suspend/resume skeleton.
- Fixed MODULE_LICENSE to "GPL" (was "GPL v2").
.../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
MAINTAINERS | 7 +
drivers/platform/x86/lenovo/Kconfig | 14 +
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
5 files changed, 365 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 0000000..04e5293
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,19 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: April 2026
+KernelVersion: 6.10
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == =============================================================
+ 0 keyboard is not docked to any screen (detached)
+ 1 keyboard docked on the top half of the bottom screen
+ 2 keyboard docked on the bottom half of the bottom screen
+ == =============================================================
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e1..00e8275 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
+
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 9c48487..938b361 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e..2842d7d 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 0000000..28a3ec7
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device (_UID "GMZN").
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
+ *
+ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
+ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
+ * The notify callback receives BKBD as an integer; no separate query needed.
+ *
+ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
+ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
+ * WmiDataId(2) uint32 Status = BKBD value
+ * Used on probe and resume to read initial state.
+ *
+ * SW_TABLET_MODE=1 is reported when the keyboard is detached;
+ * SW_TABLET_MODE=0 when docked in either position (keyboard present).
+ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/wmi.h>
+
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+/* BKBD encoding */
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1
+#define BKBD_BOTTOM_HALF 2
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+/*
+ * Shared state between the event and block WMI drivers. Protected by lock.
+ * The lock may be held across wmidev_query_block() calls (which can sleep);
+ * block_remove() acquires the lock before clearing block_wdev, ensuring
+ * block_wdev remains valid for the duration of any in-progress query.
+ */
+static struct {
+ struct mutex lock;
+ struct input_dev *input_dev; /* set by event probe */
+ struct wmi_device *block_wdev; /* set by block probe */
+} yb9;
+
+/*
+ * Read BKBD from the block device via wmidev_block_query().
+ * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock.
+ */
+static int yb9_kbdock_query_locked(struct device *log_dev)
+{
+ union acpi_object *obj;
+ u32 bkbd;
+
+ if (!yb9.block_wdev)
+ return -ENODEV;
+
+ obj = wmidev_block_query(yb9.block_wdev, 0);
+ if (!obj) {
+ dev_warn(log_dev, "WQAF returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ bkbd &= 0x3;
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ bkbd = obj->integer.value & 0x3;
+ } else {
+ dev_warn(log_dev, "WQAF: unexpected type %d len %u\n",
+ obj->type,
+ obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
+ kfree(obj);
+ return -EIO;
+ }
+
+ kfree(obj);
+ return (int)bkbd;
+}
+
+/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */
+static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev)
+{
+ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet);
+ input_sync(yb9.input_dev);
+ dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet);
+}
+
+/* ------------------------------------------------------------------
+ * sysfs
+ * ------------------------------------------------------------------ */
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int bkbd;
+
+ mutex_lock(&yb9.lock);
+ bkbd = yb9_kbdock_query_locked(dev);
+ mutex_unlock(&yb9.lock);
+
+ if (bkbd < 0)
+ return bkbd;
+ return sysfs_emit(buf, "%u\n", (unsigned int)bkbd);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+/* ------------------------------------------------------------------
+ * Event WMI driver — LENOVO_BTKBD_EVENT
+ * ------------------------------------------------------------------ */
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ u32 bkbd;
+
+ /*
+ * _WED(0xEB) returns EC.BKBD directly as an integer
+ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
+ */
+ if (!data || data->type != ACPI_TYPE_INTEGER) {
+ dev_warn(&wdev->dev, "unexpected event data type %d\n",
+ data ? data->type : -1);
+ return;
+ }
+ bkbd = data->integer.value & 0x3;
+
+ mutex_lock(&yb9.lock);
+ if (yb9.input_dev)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ mutex_unlock(&yb9.lock);
+}
+
+static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct input_dev *input_dev;
+ int bkbd, err;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ mutex_lock(&yb9.lock);
+ yb9.input_dev = input_dev;
+ /* Read initial state if the block device has already probed. */
+ if (yb9.block_wdev) {
+ bkbd = yb9_kbdock_query_locked(&wdev->dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ }
+ mutex_unlock(&yb9.lock);
+
+ return 0;
+}
+
+static void yb9_kbdock_event_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9.lock);
+ yb9.input_dev = NULL;
+ mutex_unlock(&yb9.lock);
+}
+
+static int yb9_kbdock_resume(struct device *dev)
+{
+ int bkbd;
+
+ mutex_lock(&yb9.lock);
+ if (yb9.input_dev && yb9.block_wdev) {
+ bkbd = yb9_kbdock_query_locked(dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, dev);
+ }
+ mutex_unlock(&yb9.lock);
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
+
+static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
+
+static struct wmi_driver yb9_kbdock_event_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
+ },
+ .id_table = yb9_kbdock_event_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_event_probe,
+ .remove = yb9_kbdock_event_remove,
+ .notify = yb9_kbdock_notify,
+};
+
+/* ------------------------------------------------------------------
+ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
+ * ------------------------------------------------------------------ */
+
+static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
+{
+ int bkbd;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ mutex_lock(&yb9.lock);
+ yb9.block_wdev = wdev;
+ /* Read initial state if the event device has already probed. */
+ if (yb9.input_dev) {
+ bkbd = yb9_kbdock_query_locked(&wdev->dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ }
+ mutex_unlock(&yb9.lock);
+ return 0;
+}
+
+static void yb9_kbdock_block_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9.lock);
+ yb9.block_wdev = NULL;
+ mutex_unlock(&yb9.lock);
+}
+
+static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
+ { .guid_string = YB9_KBDOCK_QUERY_GUID },
+ { }
+};
+
+static struct wmi_driver yb9_kbdock_block_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock-block",
+ },
+ .id_table = yb9_kbdock_block_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_block_probe,
+ .remove = yb9_kbdock_block_remove,
+};
+
+/* ------------------------------------------------------------------
+ * Module init / exit
+ * ------------------------------------------------------------------ */
+
+static int __init yb9_kbdock_init(void)
+{
+ int ret;
+
+ mutex_init(&yb9.lock);
+
+ ret = wmi_driver_register(&yb9_kbdock_event_driver);
+ if (ret)
+ return ret;
+
+ ret = wmi_driver_register(&yb9_kbdock_block_driver);
+ if (ret) {
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+ return ret;
+ }
+ return 0;
+}
+module_init(yb9_kbdock_init);
+
+static void __exit yb9_kbdock_exit(void)
+{
+ wmi_driver_unregister(&yb9_kbdock_block_driver);
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+}
+module_exit(yb9_kbdock_exit);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-05-27 12:27 ` [PATCH v5] " Dave Carey
@ 2026-06-08 9:13 ` johannes.goede
2026-06-08 12:52 ` Dave Carey
0 siblings, 1 reply; 23+ messages in thread
From: johannes.goede @ 2026-06-08 9:13 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86, Pit Henrich, Mark Pearson
Cc: hdegoede, ilpo.jarvinen, armin.wolf, linux-kernel
On 27-May-26 2:27 PM, Dave Carey wrote:
> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
> that magnetically attaches to the bottom (secondary) screen in one of two
> positions. The Embedded Controller tracks the attachment state in a 2-bit
> field called BKBD and signals changes via WMI event GUID
> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
> device, _UID "GMZN").
>
> The device contains embedded BMOF data (WQDD, 20705 bytes) documenting
> both WMI interfaces used by this driver:
>
> LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status.
> The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer,
> so the notify callback receives BKBD without a separate query.
>
> LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an
> 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}.
> Used for the initial state read on probe and after resume.
>
> BKBD encoding:
> 0 = keyboard detached
> 1 = keyboard docked on top half of bottom screen
> 2 = keyboard docked on bottom half of bottom screen
> 3 = reserved (not observed in practice)
>
> This driver:
> - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT)
> and one on the block GUID (LENOVO_FEATURE_STATUS_DATA).
> - On probe, reads initial BKBD state via wmidev_block_query() on the
> block device; whichever driver probes last triggers the initial read.
> - On WMI notification, reads BKBD directly from the event data integer
> (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call.
> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
> in either position (a physical keyboard is present in both cases).
> - Exposes the raw BKBD value via read-only sysfs attribute
> "keyboard_position".
> - Re-reads BKBD state on resume from suspend or hibernation.
>
> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Thank you for your patch. It seems that a second patch adding the same functionality
by extending the thinkpad_acpi driver has been developed in parallel :
https://patchwork.kernel.org/project/platform-driver-x86/patch/20260419102724.91451-1-pithenrich2d@gmail.com/
+Cc Pit Henrich (author of that patch) and Mark Pearson (Lenovo)
Mark do you know what Windows is using to access this info, is the Windows sw
using WMI like this driver or making direct ACPI calls ? (or maybe different
pieces of Windows use different paths?)
As I wrote in reply to the thinkpad_acpi patch:
Since doing this in thinkpad_acpi requires adding a DMI match and directly calling
an APCI method which name may change I believe that the WMI approach may be better.
There we can rely on only the Fold exposing the WMI GUID for this functionality.
Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard
dock detection driver" patch also uses a DMI match, is that necessary ?
If we need the DMI match because the GUID is not unique enough, then we might
just as well add this functionality here as this patch is doing ...
I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi
events if the existing thinkpad_acpi support does not already provide working
SW_TABLET_MODE input ?
If it does then also having the WMI driver emit SW_TABLET_MODE events seems
to be undesirable duplicate functionality.
Regards,
Hans
> ---
> v5:
> - Rewrote as two WMI drivers (event + block) to avoid the deprecated
> wmi_query_block() API: event driver owns the GUID that delivers
> notifications, block driver owns the GUID used for querying state.
> Either can probe first; both probe callbacks check whether the other
> has already probed and fire the initial read when both are ready.
> - Use wmidev_block_query() on the block wmi_device directly (replaces
> the deprecated wmi_query_block()).
> - In the notify callback, read BKBD directly from the event data integer
> passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by
> decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF
> call that v4 made on every notification.
> - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and
> LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message
> and file header.
> - sysfs keyboard_position: output bare integer, drop the parenthetical
> position-name suffix that a couple of reviewers found non-standard.
> - Set .no_singleton = true on both WMI drivers (was missing from block
> driver in v4).
> - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state
> after suspend/hibernation.
>
> v4:
> - Dropped module_wmi_driver(); registered two WMI drivers manually to
> allow sharing state. module_init/exit pair registers/unregisters
> both.
> - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute.
> - sysfs show: performed a live query instead of returning a cached value.
>
> v3:
> - Switched to devm_input_allocate_device().
> - Added DMI guard (dmi_check_system) to reject non-YB9 machines.
> - Removed redundant MODULE_DEVICE_TABLE on block driver.
>
> v2:
> - Added .no_singleton = true on the event WMI driver.
> - Added PM suspend/resume skeleton.
> - Fixed MODULE_LICENSE to "GPL" (was "GPL v2").
>
> .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
> MAINTAINERS | 7 +
> drivers/platform/x86/lenovo/Kconfig | 14 +
> drivers/platform/x86/lenovo/Makefile | 1 +
> drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
> 5 files changed, 365 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> new file mode 100644
> index 0000000..04e5293
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> @@ -0,0 +1,19 @@
> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
> +Date: April 2026
> +KernelVersion: 6.10
> +Contact: Dave Carey <carvsdriver@gmail.com>
> +Description:
> + Read-only attribute reporting the current keyboard dock position
> + as reported by the Embedded Controller on the Lenovo Yoga Book 9
> + 14IAH10.
> +
> + Possible values:
> +
> + == =============================================================
> + 0 keyboard is not docked to any screen (detached)
> + 1 keyboard docked on the top half of the bottom screen
> + 2 keyboard docked on the bottom half of the bottom screen
> + == =============================================================
> +
> + SW_TABLET_MODE input events are also emitted: 0 when the keyboard
> + is docked (either position), 1 when detached.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1cc0e1..00e8275 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
>
> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
> +M: Dave Carey <carvsdriver@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hansg@kernel.org>
> L: linux-input@vger.kernel.org
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 9c48487..938b361 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
> To compile this driver as a module, choose M here: the module
> will be called lenovo-wmi-camera.
>
> +config LENOVO_YB9_KBDOCK
> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
> + depends on ACPI_WMI
> + depends on DMI
> + depends on INPUT
> + help
> + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
> + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
> + either screen; this driver reports SW_TABLET_MODE input events based
> + on the attachment state and exposes the raw position in sysfs.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called lenovo-yb9-kbdock.
> +
> config LENOVO_YMC
> tristate "Lenovo Yoga Tablet Mode Control"
> depends on ACPI_WMI
> diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
> index 7b2128e..2842d7d 100644
> --- a/drivers/platform/x86/lenovo/Makefile
> +++ b/drivers/platform/x86/lenovo/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>
> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
> lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000..28a3ec7
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Yoga Book 9 keyboard-dock detection
> + *
> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
> + * attaches to the bottom screen in one of two positions. The EC tracks
> + * attachment state in a 2-bit field called BKBD and signals changes via WMI
> + * event 0xEB on the WM10 ACPI device (_UID "GMZN").
> + *
> + * BKBD values:
> + * 0 = keyboard detached
> + * 1 = keyboard docked on the top half of the bottom screen
> + * 2 = keyboard docked on the bottom half of the bottom screen
> + * 3 = reserved / not observed
> + *
> + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
> + *
> + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
> + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
> + * The notify callback receives BKBD as an integer; no separate query needed.
> + *
> + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
> + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
> + * WmiDataId(2) uint32 Status = BKBD value
> + * Used on probe and resume to read initial state.
> + *
> + * SW_TABLET_MODE=1 is reported when the keyboard is detached;
> + * SW_TABLET_MODE=0 when docked in either position (keyboard present).
> + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
> + *
> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/wmi.h>
> +
> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
> +
> +/* BKBD encoding */
> +#define BKBD_DETACHED 0
> +#define BKBD_TOP_HALF 1
> +#define BKBD_BOTTOM_HALF 2
> +
> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
> + {
> + /* Lenovo Yoga Book 9 14IAH10 */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> +/*
> + * Shared state between the event and block WMI drivers. Protected by lock.
> + * The lock may be held across wmidev_query_block() calls (which can sleep);
> + * block_remove() acquires the lock before clearing block_wdev, ensuring
> + * block_wdev remains valid for the duration of any in-progress query.
> + */
> +static struct {
> + struct mutex lock;
> + struct input_dev *input_dev; /* set by event probe */
> + struct wmi_device *block_wdev; /* set by block probe */
> +} yb9;
> +
> +/*
> + * Read BKBD from the block device via wmidev_block_query().
> + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock.
> + */
> +static int yb9_kbdock_query_locked(struct device *log_dev)
> +{
> + union acpi_object *obj;
> + u32 bkbd;
> +
> + if (!yb9.block_wdev)
> + return -ENODEV;
> +
> + obj = wmidev_block_query(yb9.block_wdev, 0);
> + if (!obj) {
> + dev_warn(log_dev, "WQAF returned NULL\n");
> + return -EIO;
> + }
> +
> + /*
> + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}.
> + */
> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
> + bkbd &= 0x3;
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + bkbd = obj->integer.value & 0x3;
> + } else {
> + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n",
> + obj->type,
> + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
> + kfree(obj);
> + return -EIO;
> + }
> +
> + kfree(obj);
> + return (int)bkbd;
> +}
> +
> +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */
> +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev)
> +{
> + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
> +
> + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet);
> + input_sync(yb9.input_dev);
> + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet);
> +}
> +
> +/* ------------------------------------------------------------------
> + * sysfs
> + * ------------------------------------------------------------------ */
> +
> +static ssize_t keyboard_position_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + bkbd = yb9_kbdock_query_locked(dev);
> + mutex_unlock(&yb9.lock);
> +
> + if (bkbd < 0)
> + return bkbd;
> + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd);
> +}
> +static DEVICE_ATTR_RO(keyboard_position);
> +
> +static struct attribute *yb9_kbdock_attrs[] = {
> + &dev_attr_keyboard_position.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(yb9_kbdock);
> +
> +/* ------------------------------------------------------------------
> + * Event WMI driver — LENOVO_BTKBD_EVENT
> + * ------------------------------------------------------------------ */
> +
> +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + u32 bkbd;
> +
> + /*
> + * _WED(0xEB) returns EC.BKBD directly as an integer
> + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
> + */
> + if (!data || data->type != ACPI_TYPE_INTEGER) {
> + dev_warn(&wdev->dev, "unexpected event data type %d\n",
> + data ? data->type : -1);
> + return;
> + }
> + bkbd = data->integer.value & 0x3;
> +
> + mutex_lock(&yb9.lock);
> + if (yb9.input_dev)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct input_dev *input_dev;
> + int bkbd, err;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err)
> + return err;
> +
> + mutex_lock(&yb9.lock);
> + yb9.input_dev = input_dev;
> + /* Read initial state if the block device has already probed. */
> + if (yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
> +
> + return 0;
> +}
> +
> +static void yb9_kbdock_event_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
> + yb9.input_dev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_resume(struct device *dev)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + if (yb9.input_dev && yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, dev);
> + }
> + mutex_unlock(&yb9.lock);
> + return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
> +
> +static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
> +
> +static struct wmi_driver yb9_kbdock_event_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock",
> + .dev_groups = yb9_kbdock_groups,
> + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
> + },
> + .id_table = yb9_kbdock_event_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_event_probe,
> + .remove = yb9_kbdock_event_remove,
> + .notify = yb9_kbdock_notify,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
> + * ------------------------------------------------------------------ */
> +
> +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + int bkbd;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + mutex_lock(&yb9.lock);
> + yb9.block_wdev = wdev;
> + /* Read initial state if the event device has already probed. */
> + if (yb9.input_dev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
> + return 0;
> +}
> +
> +static void yb9_kbdock_block_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
> + yb9.block_wdev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
> + { .guid_string = YB9_KBDOCK_QUERY_GUID },
> + { }
> +};
> +
> +static struct wmi_driver yb9_kbdock_block_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock-block",
> + },
> + .id_table = yb9_kbdock_block_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_block_probe,
> + .remove = yb9_kbdock_block_remove,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Module init / exit
> + * ------------------------------------------------------------------ */
> +
> +static int __init yb9_kbdock_init(void)
> +{
> + int ret;
> +
> + mutex_init(&yb9.lock);
> +
> + ret = wmi_driver_register(&yb9_kbdock_event_driver);
> + if (ret)
> + return ret;
> +
> + ret = wmi_driver_register(&yb9_kbdock_block_driver);
> + if (ret) {
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> + return ret;
> + }
> + return 0;
> +}
> +module_init(yb9_kbdock_init);
> +
> +static void __exit yb9_kbdock_exit(void)
> +{
> + wmi_driver_unregister(&yb9_kbdock_block_driver);
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> +}
> +module_exit(yb9_kbdock_exit);
> +
> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
> +MODULE_LICENSE("GPL");
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 9:13 ` johannes.goede
@ 2026-06-08 12:52 ` Dave Carey
2026-06-08 15:55 ` johannes.goede
0 siblings, 1 reply; 23+ messages in thread
From: Dave Carey @ 2026-06-08 12:52 UTC (permalink / raw)
To: johannes.goede
Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede,
ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey
On 08-Jun-26 11:13, Hans de Goede wrote:
> Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard
> dock detection driver" patch also uses a DMI match, is that necessary ?
>
> If we need the DMI match because the GUID is not unique enough, then we might
> just as well add this functionality here as this patch is doing ...
Yes, the DMI match is necessary, specifically for the block GUID
(E7F300FA, LENOVO_FEATURE_STATUS_DATA). This is a generic Lenovo feature
query interface that may appear on other Lenovo machines for unrelated
purposes. Both GUIDs live under a PNP0C14 device with _UID "GMZN" on
this machine, which is firmware-specific, but the WMI core matches by
GUID alone — not by parent device UID. Without the DMI guard the block
driver could bind on unrelated hardware and misinterpret WQAF's return
value as a BKBD field.
The event GUID (806BD2A2, LENOVO_BTKBD_EVENT) is named specifically for
Bluetooth keyboard events in the BMOF and is likely unique to BT keyboard
dock hardware, but keeping the DMI guard on both drivers is the safe choice
given we can't easily verify all Lenovo platforms.
> I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi
> events if the existing thinkpad_acpi support does not already provide working
> SW_TABLET_MODE input ?
>
> If it does then also having the WMI driver emit SW_TABLET_MODE events seems
> to be undesirable duplicate functionality.
Tested: no overlap. On the YB9 the existing SW_TABLET_MODE source is
lenovo-ymc (not thinkpad_acpi). The YMC driver tracks hinge/orientation
modes (clamshell=0, tent/tablet/stand=1) — it does not track BT keyboard
attachment state. I ran evtest on both the lenovo-ymc and yb9-kbdock input
devices while detaching and reattaching the keyboard: lenovo-ymc did not
fire at all; only yb9-kbdock emitted SW_TABLET_MODE events. There is no
duplicate functionality.
Regards,
Dave
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 12:52 ` Dave Carey
@ 2026-06-08 15:55 ` johannes.goede
2026-06-08 19:05 ` Dave Carey
0 siblings, 1 reply; 23+ messages in thread
From: johannes.goede @ 2026-06-08 15:55 UTC (permalink / raw)
To: Dave Carey
Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede,
ilpo.jarvinen, armin.wolf, linux-kernel
Hi Dave,
On 8-Jun-26 14:52, Dave Carey wrote:
> On 08-Jun-26 11:13, Hans de Goede wrote:
>> Dave, I see that your "[PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard
>> dock detection driver" patch also uses a DMI match, is that necessary ?
>>
>> If we need the DMI match because the GUID is not unique enough, then we might
>> just as well add this functionality here as this patch is doing ...
>
> Yes, the DMI match is necessary, specifically for the block GUID
> (E7F300FA, LENOVO_FEATURE_STATUS_DATA). This is a generic Lenovo feature
> query interface that may appear on other Lenovo machines for unrelated
> purposes. Both GUIDs live under a PNP0C14 device with _UID "GMZN" on
> this machine, which is firmware-specific, but the WMI core matches by
> GUID alone — not by parent device UID. Without the DMI guard the block
> driver could bind on unrelated hardware and misinterpret WQAF's return
> value as a BKBD field.
>
> The event GUID (806BD2A2, LENOVO_BTKBD_EVENT) is named specifically for
> Bluetooth keyboard events in the BMOF and is likely unique to BT keyboard
> dock hardware, but keeping the DMI guard on both drivers is the safe choice
> given we can't easily verify all Lenovo platforms.
>
>> I do wonder since this seems to emit TP_HKEY_EV_TABLET_CHANGED thinkpad_acpi
>> events if the existing thinkpad_acpi support does not already provide working
>> SW_TABLET_MODE input ?
>>
>> If it does then also having the WMI driver emit SW_TABLET_MODE events seems
>> to be undesirable duplicate functionality.
>
> Tested: no overlap. On the YB9 the existing SW_TABLET_MODE source is
> lenovo-ymc (not thinkpad_acpi).
Hmm, interesting not having thinkpad_acpi on a (non ThinkPad) Yoga makes
sense. Let me double check if this driver really is duplicate with
the thinkpad_acpi patch from pit:
https://patchwork.kernel.org/project/platform-driver-x86/patch/20260419102724.91451-1-pithenrich2d@gmail.com/
Ok, I should have looked closer, that one if for a "ThinkPad X1 Fold 16 Gen 1"
and yours is for the "Yoga Book 9" so I was mistaken that these drivers are
duplicate, we will need both, my bad.
That does bring the question if we want to unify the userspace interface,
but the 2 setups seem to be different enough that having a single unified
userspace interface also seems unnecessary.
The yoga book 9 has a keyboard which as your driver reports can either
cover the bottom or top half of the bottom screen, where as the
"ThinkPad X1 Fold 16 Gen 1" has a keyboard trackpad combo which covers
the whole bottom screen (*) when attached to the screen.
*) bottom screen half as it is a single big screen
> The YMC driver tracks hinge/orientation
> modes (clamshell=0, tent/tablet/stand=1) — it does not track BT keyboard
> attachment state. I ran evtest on both the lenovo-ymc and yb9-kbdock input
> devices while detaching and reattaching the keyboard: lenovo-ymc did not
> fire at all; only yb9-kbdock emitted SW_TABLET_MODE events. There is no
> duplicate functionality.
Hmm, so lenovo-ymc does load and create a /dev/input/event# nodes with
a SW_TABLET_MODE switch reported as being present ?
That is a problem because for switches unlike keys the mere presence
of them has meaning. E.g GNOME will read the SW_TABLET_MODE switch
value at GNOME start and use that to decide if it should start in tablet
or regular=clamshell/desktop mode. So the mere presence of a SW_* style
evdev node can cause behavior changes.
And userspace really does not expect there to be 2 evdev nodes which
both report SW_TABLET_MODE. So I think we need a patch to the lenovo-ymc
driver to exit probe() early (with return 0) when loading on the
yoga book 9 to avoid there being 2 SW_TABLET_MODE reporting input devices.
Regards,
Hans
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 15:55 ` johannes.goede
@ 2026-06-08 19:05 ` Dave Carey
2026-06-08 19:23 ` johannes.goede
0 siblings, 1 reply; 23+ messages in thread
From: Dave Carey @ 2026-06-08 19:05 UTC (permalink / raw)
To: johannes.goede
Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede,
ilpo.jarvinen, armin.wolf, linux-kernel, Dave Carey
Hi Hans,
Confirmed: on the YB9, both lenovo-ymc and yb9-kbdock register input
nodes with SW_TABLET_MODE in their capability bitmap (SW=2 in
/proc/bus/input/devices). You are right that this is a problem
regardless of which one fires events -- GNOME reads the capability at
startup.
The recommended fix would be: add a DMI blocklist to lenovo_ymc_probe()
to return early on the Yoga Book 9, matching on:
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
That leaves yb9-kbdock as the sole SW_TABLET_MODE source on this
hardware.
Two questions on how you'd like to proceed:
1. Should the lenovo-ymc fix come as a standalone patch, or would you
prefer a v6 2-patch series with the ymc change as patch 1/2 and the
kbdock driver as patch 2/2?
2. For the early return in probe(): you suggested "return 0" to avoid
WMI framework error messages. That requires a NULL guard in
lenovo_ymc_notify() since priv is never set in that path. Is that
the right approach, or would you prefer a different pattern?
Thanks,
Dave
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v5] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 19:05 ` Dave Carey
@ 2026-06-08 19:23 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey
0 siblings, 1 reply; 23+ messages in thread
From: johannes.goede @ 2026-06-08 19:23 UTC (permalink / raw)
To: Dave Carey
Cc: platform-driver-x86, pithenrich2d, mpearson-lenovo, hdegoede,
ilpo.jarvinen, armin.wolf, linux-kernel
Hi,
On 8-Jun-26 21:05, Dave Carey wrote:
> Hi Hans,
>
> Confirmed: on the YB9, both lenovo-ymc and yb9-kbdock register input
> nodes with SW_TABLET_MODE in their capability bitmap (SW=2 in
> /proc/bus/input/devices). You are right that this is a problem
> regardless of which one fires events -- GNOME reads the capability at
> startup.
>
> The recommended fix would be: add a DMI blocklist to lenovo_ymc_probe()
> to return early on the Yoga Book 9, matching on:
>
> DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
>
> That leaves yb9-kbdock as the sole SW_TABLET_MODE source on this
> hardware.
Ack, that sounds like the right approach.
> Two questions on how you'd like to proceed:
>
> 1. Should the lenovo-ymc fix come as a standalone patch, or would you
> prefer a v6 2-patch series with the ymc change as patch 1/2 and the
> kbdock driver as patch 2/2?
I think a v6 series makes most sense, even if one of the patches
is just an unmodified reposting of this v5.
> 2. For the early return in probe(): you suggested "return 0" to avoid
> WMI framework error messages. That requires a NULL guard in
> lenovo_ymc_notify() since priv is never set in that path. Is that
> the right approach, or would you prefer a different pattern?
My bad, that should be -ENODEV, so that the driver does not count
as being bound, while still suppressing errors from the caller of
probe. That should avoid lenovo_ymc_notify() getting called.
Assuming there is no other functionality in lenovo-ymc we care
about ...
Regards,
Hans
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 0/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 19:23 ` johannes.goede
@ 2026-06-08 21:52 ` Dave Carey
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
0 siblings, 2 replies; 23+ messages in thread
From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw)
To: platform-driver-x86
Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf,
linux-kernel, Dave Carey
This series adds keyboard dock detection for the Lenovo Yoga Book 9
14IAH10 and fixes a pre-existing SW_TABLET_MODE conflict that the new
driver exposes.
Patch 1/2 fixes lenovo-ymc: it registers an input node advertising
SW_TABLET_MODE on the Yoga Book 9, but so does patch 2/2. Userspace
(GNOME) reads SW_TABLET_MODE at login from every input node that
advertises the capability and does not tolerate two such nodes. The fix
returns -ENODEV in lenovo-ymc probe() for this hardware; the
ymc_ec_trigger EC write is separately gated by a DMI table that already
excludes this machine, so no other functionality is affected.
Patch 2/2 is the keyboard dock driver, unchanged from v5.
Changes since v5:
- New patch 1/2: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10.
Identified during v5 review by Hans de Goede.
- Patch 2/2: no code changes; reposted as part of the series.
Dave Carey (2):
platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10
platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
.../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
MAINTAINERS | 7 +
drivers/platform/x86/lenovo/Kconfig | 14 +
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++
6 files changed, 384 insertions(+)
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10
2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey
@ 2026-06-08 21:52 ` Dave Carey
2026-06-09 1:29 ` Mark Pearson
2026-06-09 8:50 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
1 sibling, 2 replies; 23+ messages in thread
From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw)
To: platform-driver-x86
Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf,
linux-kernel, Dave Carey
The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated
yb9-kbdock WMI driver that registers an input device reporting
SW_TABLET_MODE to track the detachable Bluetooth keyboard.
lenovo-ymc also loads on this machine and creates an input node with the
SW_TABLET_MODE capability bit set. For input switches, the presence of
the capability bit has semantic meaning: userspace (e.g. GNOME) reads
the switch state at startup from every node advertising the capability
and does not expect more than one such node.
Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that
lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the
sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only
other action taken in response to a YMC event, is guarded by a separate
DMI table that excludes this machine; no other functionality is affected.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c
--- a/drivers/platform/x86/lenovo/ymc.c
+++ b/drivers/platform/x86/lenovo/ymc.c
@@ -23,7 +23,23 @@ module_param(force, bool, 0444);
static bool force;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
+static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
+ {
+ /*
+ * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
+ * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
+ * avoid userspace seeing two input nodes that both advertise
+ * the SW_TABLET_MODE capability.
+ */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
{
.matches = {
@@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
struct lenovo_ymc_private *priv;
struct input_dev *input_dev;
int err;
+ if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
+ return -ENODEV;
+
if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
if (force)
dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
--
2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
@ 2026-06-08 21:52 ` Dave Carey
2026-06-09 8:50 ` johannes.goede
2026-06-09 9:47 ` Ilpo Järvinen
1 sibling, 2 replies; 23+ messages in thread
From: Dave Carey @ 2026-06-08 21:52 UTC (permalink / raw)
To: platform-driver-x86
Cc: johannes.goede, mpearson-lenovo, ilpo.jarvinen, armin.wolf,
linux-kernel, Dave Carey
The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
that magnetically attaches to the bottom (secondary) screen in one of two
positions. The Embedded Controller tracks the attachment state in a 2-bit
field called BKBD and signals changes via WMI event GUID
806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
device, _UID "GMZN").
The device contains embedded BMOF data (WQDD, 20705 bytes) documenting
both WMI interfaces used by this driver:
LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status.
The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer,
so the notify callback receives BKBD without a separate query.
LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an
8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}.
Used for the initial state read on probe and after resume.
BKBD encoding:
0 = keyboard detached
1 = keyboard docked on top half of bottom screen
2 = keyboard docked on bottom half of bottom screen
3 = reserved (not observed in practice)
This driver:
- Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT)
and one on the block GUID (LENOVO_FEATURE_STATUS_DATA).
- On probe, reads initial BKBD state via wmidev_block_query() on the
block device; whichever driver probes last triggers the initial read.
- On WMI notification, reads BKBD directly from the event data integer
(ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call.
- Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
in either position (a physical keyboard is present in both cases).
- Exposes the raw BKBD value via read-only sysfs attribute
"keyboard_position".
- Re-reads BKBD state on resume from suspend or hibernation.
Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0.
Signed-off-by: Dave Carey <carvsdriver@gmail.com>
---
v6:
- Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to
prevent duplicate SW_TABLET_MODE input nodes on the YB9.
v5:
- Rewrote as two WMI drivers (event + block) to avoid the deprecated
wmi_query_block() API: event driver owns the GUID that delivers
notifications, block driver owns the GUID used for querying state.
Either can probe first; both probe callbacks check whether the other
has already probed and fire the initial read when both are ready.
- Use wmidev_block_query() on the block wmi_device directly (replaces
the deprecated wmi_query_block()).
- In the notify callback, read BKBD directly from the event data integer
passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by
decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF
call that v4 made on every notification.
- Documented the two BMOF classes (LENOVO_BTKBD_EVENT and
LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message
and file header.
- sysfs keyboard_position: output bare integer, drop the parenthetical
position-name suffix that a couple of reviewers found non-standard.
- Set .no_singleton = true on both WMI drivers (was missing from block
driver in v4).
- Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state
after suspend/hibernation.
v4:
- Dropped module_wmi_driver(); registered two WMI drivers manually to
allow sharing state. module_init/exit pair registers/unregisters
both.
- Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute.
- sysfs show: performed a live query instead of returning a cached value.
v3:
- Switched to devm_input_allocate_device().
- Added DMI guard (dmi_check_system) to reject non-YB9 machines.
- Removed redundant MODULE_DEVICE_TABLE on block driver.
v2:
- Added .no_singleton = true on the event WMI driver.
- Added PM suspend/resume skeleton.
- Fixed MODULE_LICENSE to "GPL" (was "GPL v2").
.../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
MAINTAINERS | 7 +
drivers/platform/x86/lenovo/Kconfig | 14 +
drivers/platform/x86/lenovo/Makefile | 1 +
drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
5 files changed, 365 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
new file mode 100644
index 0000000..04e5293
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
@@ -0,0 +1,19 @@
+What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
+Date: April 2026
+KernelVersion: 6.10
+Contact: Dave Carey <carvsdriver@gmail.com>
+Description:
+ Read-only attribute reporting the current keyboard dock position
+ as reported by the Embedded Controller on the Lenovo Yoga Book 9
+ 14IAH10.
+
+ Possible values:
+
+ == =============================================================
+ 0 keyboard is not docked to any screen (detached)
+ 1 keyboard docked on the top half of the bottom screen
+ 2 keyboard docked on the bottom half of the bottom screen
+ == =============================================================
+
+ SW_TABLET_MODE input events are also emitted: 0 when the keyboard
+ is docked (either position), 1 when detached.
diff --git a/MAINTAINERS b/MAINTAINERS
index d1cc0e1..00e8275 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
+LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
+M: Dave Carey <carvsdriver@gmail.com>
+L: platform-driver-x86@vger.kernel.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
+F: drivers/platform/x86/lenovo/yb9-kbdock.c
+
LETSKETCH HID TABLET DRIVER
M: Hans de Goede <hansg@kernel.org>
L: linux-input@vger.kernel.org
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 9c48487..938b361 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
To compile this driver as a module, choose M here: the module
will be called lenovo-wmi-camera.
+config LENOVO_YB9_KBDOCK
+ tristate "Lenovo Yoga Book 9 keyboard dock detection"
+ depends on ACPI_WMI
+ depends on DMI
+ depends on INPUT
+ help
+ Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
+ 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
+ either screen; this driver reports SW_TABLET_MODE input events based
+ on the attachment state and exposes the raw position in sysfs.
+
+ To compile this driver as a module, choose M here: the module will be
+ called lenovo-yb9-kbdock.
+
config LENOVO_YMC
tristate "Lenovo Yoga Tablet Mode Control"
depends on ACPI_WMI
diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
index 7b2128e..2842d7d 100644
--- a/drivers/platform/x86/lenovo/Makefile
+++ b/drivers/platform/x86/lenovo/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
+lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
new file mode 100644
index 0000000..28a3ec7
--- /dev/null
+++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
@@ -0,0 +1,324 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Lenovo Yoga Book 9 keyboard-dock detection
+ *
+ * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
+ * attaches to the bottom screen in one of two positions. The EC tracks
+ * attachment state in a 2-bit field called BKBD and signals changes via WMI
+ * event 0xEB on the WM10 ACPI device (_UID "GMZN").
+ *
+ * BKBD values:
+ * 0 = keyboard detached
+ * 1 = keyboard docked on the top half of the bottom screen
+ * 2 = keyboard docked on the bottom half of the bottom screen
+ * 3 = reserved / not observed
+ *
+ * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
+ *
+ * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
+ * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
+ * The notify callback receives BKBD as an integer; no separate query needed.
+ *
+ * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
+ * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
+ * WmiDataId(2) uint32 Status = BKBD value
+ * Used on probe and resume to read initial state.
+ *
+ * SW_TABLET_MODE=1 is reported when the keyboard is detached;
+ * SW_TABLET_MODE=0 when docked in either position (keyboard present).
+ * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
+ *
+ * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
+ */
+
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/wmi.h>
+
+#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
+#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
+
+/* BKBD encoding */
+#define BKBD_DETACHED 0
+#define BKBD_TOP_HALF 1
+#define BKBD_BOTTOM_HALF 2
+
+static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
+ {
+ /* Lenovo Yoga Book 9 14IAH10 */
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
+ },
+ },
+ { }
+};
+
+/*
+ * Shared state between the event and block WMI drivers. Protected by lock.
+ * The lock may be held across wmidev_query_block() calls (which can sleep);
+ * block_remove() acquires the lock before clearing block_wdev, ensuring
+ * block_wdev remains valid for the duration of any in-progress query.
+ */
+static struct {
+ struct mutex lock;
+ struct input_dev *input_dev; /* set by event probe */
+ struct wmi_device *block_wdev; /* set by block probe */
+} yb9;
+
+/*
+ * Read BKBD from the block device via wmidev_block_query().
+ * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock.
+ */
+static int yb9_kbdock_query_locked(struct device *log_dev)
+{
+ union acpi_object *obj;
+ u32 bkbd;
+
+ if (!yb9.block_wdev)
+ return -ENODEV;
+
+ obj = wmidev_block_query(yb9.block_wdev, 0);
+ if (!obj) {
+ dev_warn(log_dev, "WQAF returned NULL\n");
+ return -EIO;
+ }
+
+ /*
+ * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}.
+ */
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
+ memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
+ bkbd &= 0x3;
+ } else if (obj->type == ACPI_TYPE_INTEGER) {
+ bkbd = obj->integer.value & 0x3;
+ } else {
+ dev_warn(log_dev, "WQAF: unexpected type %d len %u\n",
+ obj->type,
+ obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
+ kfree(obj);
+ return -EIO;
+ }
+
+ kfree(obj);
+ return (int)bkbd;
+}
+
+/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */
+static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev)
+{
+ int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
+
+ input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet);
+ input_sync(yb9.input_dev);
+ dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet);
+}
+
+/* ------------------------------------------------------------------
+ * sysfs
+ * ------------------------------------------------------------------ */
+
+static ssize_t keyboard_position_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int bkbd;
+
+ mutex_lock(&yb9.lock);
+ bkbd = yb9_kbdock_query_locked(dev);
+ mutex_unlock(&yb9.lock);
+
+ if (bkbd < 0)
+ return bkbd;
+ return sysfs_emit(buf, "%u\n", (unsigned int)bkbd);
+}
+static DEVICE_ATTR_RO(keyboard_position);
+
+static struct attribute *yb9_kbdock_attrs[] = {
+ &dev_attr_keyboard_position.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(yb9_kbdock);
+
+/* ------------------------------------------------------------------
+ * Event WMI driver — LENOVO_BTKBD_EVENT
+ * ------------------------------------------------------------------ */
+
+static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
+{
+ u32 bkbd;
+
+ /*
+ * _WED(0xEB) returns EC.BKBD directly as an integer
+ * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
+ */
+ if (!data || data->type != ACPI_TYPE_INTEGER) {
+ dev_warn(&wdev->dev, "unexpected event data type %d\n",
+ data ? data->type : -1);
+ return;
+ }
+ bkbd = data->integer.value & 0x3;
+
+ mutex_lock(&yb9.lock);
+ if (yb9.input_dev)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ mutex_unlock(&yb9.lock);
+}
+
+static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx)
+{
+ struct input_dev *input_dev;
+ int bkbd, err;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ input_dev = devm_input_allocate_device(&wdev->dev);
+ if (!input_dev)
+ return -ENOMEM;
+
+ input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
+ input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+
+ err = input_register_device(input_dev);
+ if (err)
+ return err;
+
+ mutex_lock(&yb9.lock);
+ yb9.input_dev = input_dev;
+ /* Read initial state if the block device has already probed. */
+ if (yb9.block_wdev) {
+ bkbd = yb9_kbdock_query_locked(&wdev->dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ }
+ mutex_unlock(&yb9.lock);
+
+ return 0;
+}
+
+static void yb9_kbdock_event_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9.lock);
+ yb9.input_dev = NULL;
+ mutex_unlock(&yb9.lock);
+}
+
+static int yb9_kbdock_resume(struct device *dev)
+{
+ int bkbd;
+
+ mutex_lock(&yb9.lock);
+ if (yb9.input_dev && yb9.block_wdev) {
+ bkbd = yb9_kbdock_query_locked(dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, dev);
+ }
+ mutex_unlock(&yb9.lock);
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
+
+static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
+ { .guid_string = YB9_KBDOCK_EVENT_GUID },
+ { }
+};
+MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
+
+static struct wmi_driver yb9_kbdock_event_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock",
+ .dev_groups = yb9_kbdock_groups,
+ .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
+ },
+ .id_table = yb9_kbdock_event_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_event_probe,
+ .remove = yb9_kbdock_event_remove,
+ .notify = yb9_kbdock_notify,
+};
+
+/* ------------------------------------------------------------------
+ * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
+ * ------------------------------------------------------------------ */
+
+static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
+{
+ int bkbd;
+
+ if (!dmi_check_system(yb9_kbdock_dmi_table))
+ return -ENODEV;
+
+ mutex_lock(&yb9.lock);
+ yb9.block_wdev = wdev;
+ /* Read initial state if the event device has already probed. */
+ if (yb9.input_dev) {
+ bkbd = yb9_kbdock_query_locked(&wdev->dev);
+ if (bkbd >= 0)
+ yb9_kbdock_report_locked(bkbd, &wdev->dev);
+ }
+ mutex_unlock(&yb9.lock);
+ return 0;
+}
+
+static void yb9_kbdock_block_remove(struct wmi_device *wdev)
+{
+ mutex_lock(&yb9.lock);
+ yb9.block_wdev = NULL;
+ mutex_unlock(&yb9.lock);
+}
+
+static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
+ { .guid_string = YB9_KBDOCK_QUERY_GUID },
+ { }
+};
+
+static struct wmi_driver yb9_kbdock_block_driver = {
+ .driver = {
+ .name = "lenovo-yb9-kbdock-block",
+ },
+ .id_table = yb9_kbdock_block_id_table,
+ .no_singleton = true,
+ .probe = yb9_kbdock_block_probe,
+ .remove = yb9_kbdock_block_remove,
+};
+
+/* ------------------------------------------------------------------
+ * Module init / exit
+ * ------------------------------------------------------------------ */
+
+static int __init yb9_kbdock_init(void)
+{
+ int ret;
+
+ mutex_init(&yb9.lock);
+
+ ret = wmi_driver_register(&yb9_kbdock_event_driver);
+ if (ret)
+ return ret;
+
+ ret = wmi_driver_register(&yb9_kbdock_block_driver);
+ if (ret) {
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+ return ret;
+ }
+ return 0;
+}
+module_init(yb9_kbdock_init);
+
+static void __exit yb9_kbdock_exit(void)
+{
+ wmi_driver_unregister(&yb9_kbdock_block_driver);
+ wmi_driver_unregister(&yb9_kbdock_event_driver);
+}
+module_exit(yb9_kbdock_exit);
+
+MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
+MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
+MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
@ 2026-06-09 1:29 ` Mark Pearson
2026-06-09 1:29 ` Mark Pearson
2026-06-09 8:50 ` johannes.goede
1 sibling, 1 reply; 23+ messages in thread
From: Mark Pearson @ 2026-06-09 1:29 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86@vger.kernel.org
Cc: Hans de Goede, Ilpo Järvinen, armin.wolf, linux-kernel
Hi Dave
On Mon, Jun 8, 2026, at 5:52 PM, Dave Carey wrote:
> The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated
> yb9-kbdock WMI driver that registers an input device reporting
> SW_TABLET_MODE to track the detachable Bluetooth keyboard.
>
> lenovo-ymc also loads on this machine and creates an input node with the
> SW_TABLET_MODE capability bit set. For input switches, the presence of
> the capability bit has semantic meaning: userspace (e.g. GNOME) reads
> the switch state at startup from every node advertising the capability
> and does not expect more than one such node.
>
> Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that
> lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the
> sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only
> other action taken in response to a YMC event, is guarded by a separate
> DMI table that excludes this machine; no other functionality is affected.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> ---
> drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/drivers/platform/x86/lenovo/ymc.c
> b/drivers/platform/x86/lenovo/ymc.c
> --- a/drivers/platform/x86/lenovo/ymc.c
> +++ b/drivers/platform/x86/lenovo/ymc.c
> @@ -23,7 +23,23 @@ module_param(force, bool, 0444);
> static bool force;
> module_param(force, bool, 0444);
> MODULE_PARM_DESC(force, "Force loading on boards without a convertible
> DMI chassis-type");
>
> +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
> + {
> + /*
> + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
> + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
> + * avoid userspace seeing two input nodes that both advertise
> + * the SW_TABLET_MODE capability.
> + */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
> {
> .matches = {
> @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device
> *wdev, const void *ctx)
> struct lenovo_ymc_private *priv;
> struct input_dev *input_dev;
> int err;
>
> + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
> + return -ENODEV;
> +
> if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
> if (force)
> dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
> --
> 2.54.0
I'm sure I'm missing the obvious - but where is the yb9-kbdock driver? I can't find it...
I was curious as to whether a separate driver was really needed for this platform, or if it should have been integrated in the ymc driver in the first place?
Mark
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10
2026-06-09 1:29 ` Mark Pearson
@ 2026-06-09 1:29 ` Mark Pearson
0 siblings, 0 replies; 23+ messages in thread
From: Mark Pearson @ 2026-06-09 1:29 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86@vger.kernel.org
Cc: Hans de Goede, Ilpo Järvinen, armin.wolf, linux-kernel
On Mon, Jun 8, 2026, at 9:29 PM, Mark Pearson wrote:
> Hi Dave
>
> On Mon, Jun 8, 2026, at 5:52 PM, Dave Carey wrote:
>> The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated
>> yb9-kbdock WMI driver that registers an input device reporting
>> SW_TABLET_MODE to track the detachable Bluetooth keyboard.
>>
>> lenovo-ymc also loads on this machine and creates an input node with the
>> SW_TABLET_MODE capability bit set. For input switches, the presence of
>> the capability bit has semantic meaning: userspace (e.g. GNOME) reads
>> the switch state at startup from every node advertising the capability
>> and does not expect more than one such node.
>>
>> Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that
>> lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the
>> sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only
>> other action taken in response to a YMC event, is guarded by a separate
>> DMI table that excludes this machine; no other functionality is affected.
>>
>> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
>> ---
>> drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++
>> 1 file changed, 19 insertions(+)
>>
>> diff --git a/drivers/platform/x86/lenovo/ymc.c
>> b/drivers/platform/x86/lenovo/ymc.c
>> --- a/drivers/platform/x86/lenovo/ymc.c
>> +++ b/drivers/platform/x86/lenovo/ymc.c
>> @@ -23,7 +23,23 @@ module_param(force, bool, 0444);
>> static bool force;
>> module_param(force, bool, 0444);
>> MODULE_PARM_DESC(force, "Force loading on boards without a convertible
>> DMI chassis-type");
>>
>> +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
>> + {
>> + /*
>> + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
>> + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
>> + * avoid userspace seeing two input nodes that both advertise
>> + * the SW_TABLET_MODE capability.
>> + */
>> + .matches = {
>> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
>> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
>> + },
>> + },
>> + { }
>> +};
>> +
>> static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
>> {
>> .matches = {
>> @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device
>> *wdev, const void *ctx)
>> struct lenovo_ymc_private *priv;
>> struct input_dev *input_dev;
>> int err;
>>
>> + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
>> + return -ENODEV;
>> +
>> if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
>> if (force)
>> dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
>> --
>> 2.54.0
>
> I'm sure I'm missing the obvious - but where is the yb9-kbdock driver?
> I can't find it...
>
> I was curious as to whether a separate driver was really needed for
> this platform, or if it should have been integrated in the ymc driver
> in the first place?
>
> Mark
Nevermind - ignore this. I just saw patch 2 and went Durrrr....
My brain is still in vacation mode :)
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
2026-06-09 1:29 ` Mark Pearson
@ 2026-06-09 8:50 ` johannes.goede
1 sibling, 0 replies; 23+ messages in thread
From: johannes.goede @ 2026-06-09 8:50 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86
Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel
Hi,
On 8-Jun-26 23:52, Dave Carey wrote:
> The Yoga Book 9 14IAH10 (DMI product name "83KJ") has a dedicated
> yb9-kbdock WMI driver that registers an input device reporting
> SW_TABLET_MODE to track the detachable Bluetooth keyboard.
>
> lenovo-ymc also loads on this machine and creates an input node with the
> SW_TABLET_MODE capability bit set. For input switches, the presence of
> the capability bit has semantic meaning: userspace (e.g. GNOME) reads
> the switch state at startup from every node advertising the capability
> and does not expect more than one such node.
>
> Add a DMI match for the Yoga Book 9 14IAH10 to probe() so that
> lenovo-ymc returns -ENODEV on this hardware, leaving yb9-kbdock as the
> sole SW_TABLET_MODE source. The ymc_ec_trigger EC write, the only
> other action taken in response to a YMC event, is guarded by a separate
> DMI table that excludes this machine; no other functionality is affected.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
Thanks, patch looks good to me:
Reviewed-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
Regards,
Hans
> ---
> drivers/platform/x86/lenovo/ymc.c | 19 +++++++++++++++++++
> 1 file changed, 19 insertions(+)
>
> diff --git a/drivers/platform/x86/lenovo/ymc.c b/drivers/platform/x86/lenovo/ymc.c
> --- a/drivers/platform/x86/lenovo/ymc.c
> +++ b/drivers/platform/x86/lenovo/ymc.c
> @@ -23,7 +23,23 @@ module_param(force, bool, 0444);
> static bool force;
> module_param(force, bool, 0444);
> MODULE_PARM_DESC(force, "Force loading on boards without a convertible DMI chassis-type");
>
> +static const struct dmi_system_id lenovo_ymc_nosupport_dmi_table[] = {
> + {
> + /*
> + * Yoga Book 9 14IAH10: SW_TABLET_MODE is reported by the
> + * yb9-kbdock driver. Suppress lenovo-ymc on this machine to
> + * avoid userspace seeing two input nodes that both advertise
> + * the SW_TABLET_MODE capability.
> + */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> static const struct dmi_system_id allowed_chasis_types_dmi_table[] = {
> {
> .matches = {
> @@ -100,7 +116,10 @@ static int lenovo_ymc_probe(struct wmi_device *wdev, const void *ctx)
> struct lenovo_ymc_private *priv;
> struct input_dev *input_dev;
> int err;
>
> + if (dmi_check_system(lenovo_ymc_nosupport_dmi_table))
> + return -ENODEV;
> +
> if (!dmi_check_system(allowed_chasis_types_dmi_table)) {
> if (force)
> dev_info(&wdev->dev, "Force loading Lenovo YMC support\n");
> --
> 2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
@ 2026-06-09 8:50 ` johannes.goede
2026-06-09 9:47 ` Ilpo Järvinen
1 sibling, 0 replies; 23+ messages in thread
From: johannes.goede @ 2026-06-09 8:50 UTC (permalink / raw)
To: Dave Carey, platform-driver-x86
Cc: mpearson-lenovo, ilpo.jarvinen, armin.wolf, linux-kernel
Hi,
On 8-Jun-26 23:52, Dave Carey wrote:
> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
> that magnetically attaches to the bottom (secondary) screen in one of two
> positions. The Embedded Controller tracks the attachment state in a 2-bit
> field called BKBD and signals changes via WMI event GUID
> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
> device, _UID "GMZN").
>
> The device contains embedded BMOF data (WQDD, 20705 bytes) documenting
> both WMI interfaces used by this driver:
>
> LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status.
> The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer,
> so the notify callback receives BKBD without a separate query.
>
> LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an
> 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}.
> Used for the initial state read on probe and after resume.
>
> BKBD encoding:
> 0 = keyboard detached
> 1 = keyboard docked on top half of bottom screen
> 2 = keyboard docked on bottom half of bottom screen
> 3 = reserved (not observed in practice)
>
> This driver:
> - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT)
> and one on the block GUID (LENOVO_FEATURE_STATUS_DATA).
> - On probe, reads initial BKBD state via wmidev_block_query() on the
> block device; whichever driver probes last triggers the initial read.
> - On WMI notification, reads BKBD directly from the event data integer
> (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call.
> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
> in either position (a physical keyboard is present in both cases).
> - Exposes the raw BKBD value via read-only sysfs attribute
> "keyboard_position".
> - Re-reads BKBD state on resume from suspend or hibernation.
>
> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> ---> v6:
> - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to
> prevent duplicate SW_TABLET_MODE input nodes on the YB9.
Thank you.
For the userspace-API parts of this and the previously discussed DMI
match usage:
Acked-by: Hans de Goede <johannes.goede@oss.qualcomm.com>
Regards,
Hans
>
> v5:
> - Rewrote as two WMI drivers (event + block) to avoid the deprecated
> wmi_query_block() API: event driver owns the GUID that delivers
> notifications, block driver owns the GUID used for querying state.
> Either can probe first; both probe callbacks check whether the other
> has already probed and fire the initial read when both are ready.
> - Use wmidev_block_query() on the block wmi_device directly (replaces
> the deprecated wmi_query_block()).
> - In the notify callback, read BKBD directly from the event data integer
> passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by
> decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF
> call that v4 made on every notification.
> - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and
> LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message
> and file header.
> - sysfs keyboard_position: output bare integer, drop the parenthetical
> position-name suffix that a couple of reviewers found non-standard.
> - Set .no_singleton = true on both WMI drivers (was missing from block
> driver in v4).
> - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state
> after suspend/hibernation.
>
> v4:
> - Dropped module_wmi_driver(); registered two WMI drivers manually to
> allow sharing state. module_init/exit pair registers/unregisters
> both.
> - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute.
> - sysfs show: performed a live query instead of returning a cached value.
>
> v3:
> - Switched to devm_input_allocate_device().
> - Added DMI guard (dmi_check_system) to reject non-YB9 machines.
> - Removed redundant MODULE_DEVICE_TABLE on block driver.
>
> v2:
> - Added .no_singleton = true on the event WMI driver.
> - Added PM suspend/resume skeleton.
> - Fixed MODULE_LICENSE to "GPL" (was "GPL v2").
>
> .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
> MAINTAINERS | 7 +
> drivers/platform/x86/lenovo/Kconfig | 14 +
> drivers/platform/x86/lenovo/Makefile | 1 +
> drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
> 5 files changed, 365 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> new file mode 100644
> index 0000000..04e5293
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> @@ -0,0 +1,19 @@
> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
> +Date: April 2026
> +KernelVersion: 6.10
> +Contact: Dave Carey <carvsdriver@gmail.com>
> +Description:
> + Read-only attribute reporting the current keyboard dock position
> + as reported by the Embedded Controller on the Lenovo Yoga Book 9
> + 14IAH10.
> +
> + Possible values:
> +
> + == =============================================================
> + 0 keyboard is not docked to any screen (detached)
> + 1 keyboard docked on the top half of the bottom screen
> + 2 keyboard docked on the bottom half of the bottom screen
> + == =============================================================
> +
> + SW_TABLET_MODE input events are also emitted: 0 when the keyboard
> + is docked (either position), 1 when detached.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1cc0e1..00e8275 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
>
> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
> +M: Dave Carey <carvsdriver@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hansg@kernel.org>
> L: linux-input@vger.kernel.org
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 9c48487..938b361 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
> To compile this driver as a module, choose M here: the module
> will be called lenovo-wmi-camera.
>
> +config LENOVO_YB9_KBDOCK
> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
> + depends on ACPI_WMI
> + depends on DMI
> + depends on INPUT
> + help
> + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
> + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
> + either screen; this driver reports SW_TABLET_MODE input events based
> + on the attachment state and exposes the raw position in sysfs.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called lenovo-yb9-kbdock.
> +
> config LENOVO_YMC
> tristate "Lenovo Yoga Tablet Mode Control"
> depends on ACPI_WMI
> diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
> index 7b2128e..2842d7d 100644
> --- a/drivers/platform/x86/lenovo/Makefile
> +++ b/drivers/platform/x86/lenovo/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>
> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
> lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000..28a3ec7
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Yoga Book 9 keyboard-dock detection
> + *
> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
> + * attaches to the bottom screen in one of two positions. The EC tracks
> + * attachment state in a 2-bit field called BKBD and signals changes via WMI
> + * event 0xEB on the WM10 ACPI device (_UID "GMZN").
> + *
> + * BKBD values:
> + * 0 = keyboard detached
> + * 1 = keyboard docked on the top half of the bottom screen
> + * 2 = keyboard docked on the bottom half of the bottom screen
> + * 3 = reserved / not observed
> + *
> + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
> + *
> + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
> + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
> + * The notify callback receives BKBD as an integer; no separate query needed.
> + *
> + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
> + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
> + * WmiDataId(2) uint32 Status = BKBD value
> + * Used on probe and resume to read initial state.
> + *
> + * SW_TABLET_MODE=1 is reported when the keyboard is detached;
> + * SW_TABLET_MODE=0 when docked in either position (keyboard present).
> + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
> + *
> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/wmi.h>
> +
> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
> +
> +/* BKBD encoding */
> +#define BKBD_DETACHED 0
> +#define BKBD_TOP_HALF 1
> +#define BKBD_BOTTOM_HALF 2
> +
> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
> + {
> + /* Lenovo Yoga Book 9 14IAH10 */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> +/*
> + * Shared state between the event and block WMI drivers. Protected by lock.
> + * The lock may be held across wmidev_query_block() calls (which can sleep);
> + * block_remove() acquires the lock before clearing block_wdev, ensuring
> + * block_wdev remains valid for the duration of any in-progress query.
> + */
> +static struct {
> + struct mutex lock;
> + struct input_dev *input_dev; /* set by event probe */
> + struct wmi_device *block_wdev; /* set by block probe */
> +} yb9;
> +
> +/*
> + * Read BKBD from the block device via wmidev_block_query().
> + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock.
> + */
> +static int yb9_kbdock_query_locked(struct device *log_dev)
> +{
> + union acpi_object *obj;
> + u32 bkbd;
> +
> + if (!yb9.block_wdev)
> + return -ENODEV;
> +
> + obj = wmidev_block_query(yb9.block_wdev, 0);
> + if (!obj) {
> + dev_warn(log_dev, "WQAF returned NULL\n");
> + return -EIO;
> + }
> +
> + /*
> + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}.
> + */
> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
> + bkbd &= 0x3;
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + bkbd = obj->integer.value & 0x3;
> + } else {
> + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n",
> + obj->type,
> + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
> + kfree(obj);
> + return -EIO;
> + }
> +
> + kfree(obj);
> + return (int)bkbd;
> +}
> +
> +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */
> +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev)
> +{
> + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
> +
> + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet);
> + input_sync(yb9.input_dev);
> + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet);
> +}
> +
> +/* ------------------------------------------------------------------
> + * sysfs
> + * ------------------------------------------------------------------ */
> +
> +static ssize_t keyboard_position_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + bkbd = yb9_kbdock_query_locked(dev);
> + mutex_unlock(&yb9.lock);
> +
> + if (bkbd < 0)
> + return bkbd;
> + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd);
> +}
> +static DEVICE_ATTR_RO(keyboard_position);
> +
> +static struct attribute *yb9_kbdock_attrs[] = {
> + &dev_attr_keyboard_position.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(yb9_kbdock);
> +
> +/* ------------------------------------------------------------------
> + * Event WMI driver — LENOVO_BTKBD_EVENT
> + * ------------------------------------------------------------------ */
> +
> +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + u32 bkbd;
> +
> + /*
> + * _WED(0xEB) returns EC.BKBD directly as an integer
> + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
> + */
> + if (!data || data->type != ACPI_TYPE_INTEGER) {
> + dev_warn(&wdev->dev, "unexpected event data type %d\n",
> + data ? data->type : -1);
> + return;
> + }
> + bkbd = data->integer.value & 0x3;
> +
> + mutex_lock(&yb9.lock);
> + if (yb9.input_dev)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct input_dev *input_dev;
> + int bkbd, err;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err)
> + return err;
> +
> + mutex_lock(&yb9.lock);
> + yb9.input_dev = input_dev;
> + /* Read initial state if the block device has already probed. */
> + if (yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
> +
> + return 0;
> +}
> +
> +static void yb9_kbdock_event_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
> + yb9.input_dev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_resume(struct device *dev)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + if (yb9.input_dev && yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, dev);
> + }
> + mutex_unlock(&yb9.lock);
> + return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
> +
> +static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
> +
> +static struct wmi_driver yb9_kbdock_event_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock",
> + .dev_groups = yb9_kbdock_groups,
> + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
> + },
> + .id_table = yb9_kbdock_event_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_event_probe,
> + .remove = yb9_kbdock_event_remove,
> + .notify = yb9_kbdock_notify,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
> + * ------------------------------------------------------------------ */
> +
> +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + int bkbd;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + mutex_lock(&yb9.lock);
> + yb9.block_wdev = wdev;
> + /* Read initial state if the event device has already probed. */
> + if (yb9.input_dev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
> + return 0;
> +}
> +
> +static void yb9_kbdock_block_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
> + yb9.block_wdev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
> + { .guid_string = YB9_KBDOCK_QUERY_GUID },
> + { }
> +};
> +
> +static struct wmi_driver yb9_kbdock_block_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock-block",
> + },
> + .id_table = yb9_kbdock_block_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_block_probe,
> + .remove = yb9_kbdock_block_remove,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Module init / exit
> + * ------------------------------------------------------------------ */
> +
> +static int __init yb9_kbdock_init(void)
> +{
> + int ret;
> +
> + mutex_init(&yb9.lock);
> +
> + ret = wmi_driver_register(&yb9_kbdock_event_driver);
> + if (ret)
> + return ret;
> +
> + ret = wmi_driver_register(&yb9_kbdock_block_driver);
> + if (ret) {
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> + return ret;
> + }
> + return 0;
> +}
> +module_init(yb9_kbdock_init);
> +
> +static void __exit yb9_kbdock_exit(void)
> +{
> + wmi_driver_unregister(&yb9_kbdock_block_driver);
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> +}
> +module_exit(yb9_kbdock_exit);
> +
> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
> +MODULE_LICENSE("GPL");
> --
> 2.54.0
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-06-09 8:50 ` johannes.goede
@ 2026-06-09 9:47 ` Ilpo Järvinen
1 sibling, 0 replies; 23+ messages in thread
From: Ilpo Järvinen @ 2026-06-09 9:47 UTC (permalink / raw)
To: Dave Carey
Cc: platform-driver-x86, johannes.goede, Mark Pearson, armin.wolf,
LKML
[-- Attachment #1: Type: text/plain, Size: 19230 bytes --]
On Mon, 8 Jun 2026, Dave Carey wrote:
> The Lenovo Yoga Book 9 14IAH10 ships with a detachable Bluetooth keyboard
> that magnetically attaches to the bottom (secondary) screen in one of two
> positions. The Embedded Controller tracks the attachment state in a 2-bit
> field called BKBD
Please add a define for the field then with GENMASK() and extract it with
FIELD_GET(). Don't forget to add headers.
> and signals changes via WMI event GUID
> 806BD2A2-177B-481D-BFB5-3BA0BB4A2285 (notify ID 0xEB on the WM10 ACPI
> device, _UID "GMZN").
>
> The device contains embedded BMOF data (WQDD, 20705 bytes) documenting
> both WMI interfaces used by this driver:
>
> LENOVO_BTKBD_EVENT (event GUID): WmiDataId(1) uint32 Status.
> The ACPI _WED(0xEB) method returns EC.BKBD directly as an integer,
> so the notify callback receives BKBD without a separate query.
>
> LENOVO_FEATURE_STATUS_DATA (block GUID, WQAF method): returns an
> 8-byte buffer {uint32 IDs=0x00060000, uint32 Status=BKBD}.
> Used for the initial state read on probe and after resume.
>
> BKBD encoding:
> 0 = keyboard detached
> 1 = keyboard docked on top half of bottom screen
> 2 = keyboard docked on bottom half of bottom screen
> 3 = reserved (not observed in practice)
>
> This driver:
> - Registers two WMI drivers: one on the event GUID (LENOVO_BTKBD_EVENT)
> and one on the block GUID (LENOVO_FEATURE_STATUS_DATA).
> - On probe, reads initial BKBD state via wmidev_block_query() on the
> block device; whichever driver probes last triggers the initial read.
> - On WMI notification, reads BKBD directly from the event data integer
> (ACPI _WED(0xEB) returns EC.BKBD) without a redundant WQAF call.
> - Reports SW_TABLET_MODE=1 when detached, SW_TABLET_MODE=0 when docked
> in either position (a physical keyboard is present in both cases).
> - Exposes the raw BKBD value via read-only sysfs attribute
> "keyboard_position".
> - Re-reads BKBD state on resume from suspend or hibernation.
>
> Tested on: Lenovo Yoga Book 9 14IAH10 (model 83KJ), kernel 7.0.
>
> Signed-off-by: Dave Carey <carvsdriver@gmail.com>
> ---
> v6:
> - Submitted as 2/2; patch 1/2 adds a DMI early-exit to lenovo-ymc to
> prevent duplicate SW_TABLET_MODE input nodes on the YB9.
>
> v5:
> - Rewrote as two WMI drivers (event + block) to avoid the deprecated
> wmi_query_block() API: event driver owns the GUID that delivers
> notifications, block driver owns the GUID used for querying state.
> Either can probe first; both probe callbacks check whether the other
> has already probed and fire the initial read when both are ready.
> - Use wmidev_block_query() on the block wmi_device directly (replaces
> the deprecated wmi_query_block()).
> - In the notify callback, read BKBD directly from the event data integer
> passed by the WMI core (_WED(0xEB) returns EC.BKBD; confirmed by
> decoding the embedded BMOF from WQDD). Eliminates the redundant WQAF
> call that v4 made on every notification.
> - Documented the two BMOF classes (LENOVO_BTKBD_EVENT and
> LENOVO_FEATURE_STATUS_DATA) with field layouts in the commit message
> and file header.
> - sysfs keyboard_position: output bare integer, drop the parenthetical
> position-name suffix that a couple of reviewers found non-standard.
> - Set .no_singleton = true on both WMI drivers (was missing from block
> driver in v4).
> - Add PM resume callback (yb9_kbdock_resume) to re-read BKBD state
> after suspend/hibernation.
>
> v4:
> - Dropped module_wmi_driver(); registered two WMI drivers manually to
> allow sharing state. module_init/exit pair registers/unregisters
> both.
> - Added ATTRIBUTE_GROUPS() and .dev_groups for the sysfs attribute.
> - sysfs show: performed a live query instead of returning a cached value.
>
> v3:
> - Switched to devm_input_allocate_device().
> - Added DMI guard (dmi_check_system) to reject non-YB9 machines.
> - Removed redundant MODULE_DEVICE_TABLE on block driver.
>
> v2:
> - Added .no_singleton = true on the event WMI driver.
> - Added PM suspend/resume skeleton.
> - Fixed MODULE_LICENSE to "GPL" (was "GPL v2").
>
> .../testing/sysfs-driver-lenovo-yb9-kbdock | 19 +
> MAINTAINERS | 7 +
> drivers/platform/x86/lenovo/Kconfig | 14 +
> drivers/platform/x86/lenovo/Makefile | 1 +
> drivers/platform/x86/lenovo/yb9-kbdock.c | 324 ++++++++++++++++++
> 5 files changed, 365 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> create mode 100644 drivers/platform/x86/lenovo/yb9-kbdock.c
>
> diff --git a/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> new file mode 100644
> index 0000000..04e5293
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> @@ -0,0 +1,19 @@
> +What: /sys/bus/wmi/drivers/lenovo-yb9-kbdock/<guid>/keyboard_position
> +Date: April 2026
> +KernelVersion: 6.10
> +Contact: Dave Carey <carvsdriver@gmail.com>
> +Description:
> + Read-only attribute reporting the current keyboard dock position
> + as reported by the Embedded Controller on the Lenovo Yoga Book 9
> + 14IAH10.
> +
> + Possible values:
> +
> + == =============================================================
> + 0 keyboard is not docked to any screen (detached)
> + 1 keyboard docked on the top half of the bottom screen
> + 2 keyboard docked on the bottom half of the bottom screen
> + == =============================================================
> +
> + SW_TABLET_MODE input events are also emitted: 0 when the keyboard
> + is docked (either position), 1 when detached.
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1cc0e1..00e8275 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14479,6 +14479,13 @@ L: platform-driver-x86@vger.kernel.org
> S: Maintained
> F: drivers/platform/x86/lenovo/wmi-hotkey-utilities.c
>
> +LENOVO YOGA BOOK 9 KEYBOARD DOCK DRIVER
> +M: Dave Carey <carvsdriver@gmail.com>
> +L: platform-driver-x86@vger.kernel.org
> +S: Maintained
> +F: Documentation/ABI/testing/sysfs-driver-lenovo-yb9-kbdock
> +F: drivers/platform/x86/lenovo/yb9-kbdock.c
> +
> LETSKETCH HID TABLET DRIVER
> M: Hans de Goede <hansg@kernel.org>
> L: linux-input@vger.kernel.org
> diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
> index 9c48487..938b361 100644
> --- a/drivers/platform/x86/lenovo/Kconfig
> +++ b/drivers/platform/x86/lenovo/Kconfig
> @@ -43,6 +43,20 @@ config LENOVO_WMI_CAMERA
> To compile this driver as a module, choose M here: the module
> will be called lenovo-wmi-camera.
>
> +config LENOVO_YB9_KBDOCK
> + tristate "Lenovo Yoga Book 9 keyboard dock detection"
> + depends on ACPI_WMI
> + depends on DMI
> + depends on INPUT
> + help
> + Say Y here to enable keyboard dock detection on the Lenovo Yoga Book 9
> + 14IAH10. The detachable Bluetooth keyboard magnetically attaches to
> + either screen; this driver reports SW_TABLET_MODE input events based
> + on the attachment state and exposes the raw position in sysfs.
> +
> + To compile this driver as a module, choose M here: the module will be
> + called lenovo-yb9-kbdock.
> +
> config LENOVO_YMC
> tristate "Lenovo Yoga Tablet Mode Control"
> depends on ACPI_WMI
> diff --git a/drivers/platform/x86/lenovo/Makefile b/drivers/platform/x86/lenovo/Makefile
> index 7b2128e..2842d7d 100644
> --- a/drivers/platform/x86/lenovo/Makefile
> +++ b/drivers/platform/x86/lenovo/Makefile
> @@ -8,6 +8,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
> obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
>
> lenovo-target-$(CONFIG_LENOVO_WMI_HOTKEY_UTILITIES) += wmi-hotkey-utilities.o
> +lenovo-target-$(CONFIG_LENOVO_YB9_KBDOCK) += yb9-kbdock.o
> lenovo-target-$(CONFIG_LENOVO_YMC) += ymc.o
> lenovo-target-$(CONFIG_YOGABOOK) += yogabook.o
> lenovo-target-$(CONFIG_YT2_1380) += yoga-tab2-pro-1380-fastcharger.o
> diff --git a/drivers/platform/x86/lenovo/yb9-kbdock.c b/drivers/platform/x86/lenovo/yb9-kbdock.c
> new file mode 100644
> index 0000000..28a3ec7
> --- /dev/null
> +++ b/drivers/platform/x86/lenovo/yb9-kbdock.c
> @@ -0,0 +1,324 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Lenovo Yoga Book 9 keyboard-dock detection
> + *
> + * The Yoga Book 9 ships with a detachable Bluetooth keyboard that magnetically
> + * attaches to the bottom screen in one of two positions. The EC tracks
> + * attachment state in a 2-bit field called BKBD and signals changes via WMI
> + * event 0xEB on the WM10 ACPI device (_UID "GMZN").
> + *
> + * BKBD values:
> + * 0 = keyboard detached
> + * 1 = keyboard docked on the top half of the bottom screen
> + * 2 = keyboard docked on the bottom half of the bottom screen
> + * 3 = reserved / not observed
> + *
> + * Two WMI interfaces are used (documented in embedded BMOF, WQDD, 20705 bytes):
> + *
> + * LENOVO_BTKBD_EVENT (event GUID, 806BD2A2-...)
> + * WmiDataId(1) uint32 Status — _WED(0xEB) returns EC.BKBD directly.
> + * The notify callback receives BKBD as an integer; no separate query needed.
> + *
> + * LENOVO_FEATURE_STATUS_DATA (block GUID, E7F300FA-...)
> + * WmiDataId(1) uint32 IDs = 0x00060000 (feature selector)
> + * WmiDataId(2) uint32 Status = BKBD value
> + * Used on probe and resume to read initial state.
> + *
> + * SW_TABLET_MODE=1 is reported when the keyboard is detached;
> + * SW_TABLET_MODE=0 when docked in either position (keyboard present).
> + * The raw BKBD value is exposed via the sysfs attribute "keyboard_position".
> + *
> + * Copyright (C) 2026 Dave Carey <carvsdriver@gmail.com>
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/dmi.h>
> +#include <linux/input.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/pm.h>
> +#include <linux/wmi.h>
> +
> +#define YB9_KBDOCK_EVENT_GUID "806BD2A2-177B-481D-BFB5-3BA0BB4A2285"
> +#define YB9_KBDOCK_QUERY_GUID "E7F300FA-21CD-4003-ADAC-2696135982E6"
> +
> +/* BKBD encoding */
> +#define BKBD_DETACHED 0
> +#define BKBD_TOP_HALF 1
> +#define BKBD_BOTTOM_HALF 2
> +
> +static const struct dmi_system_id yb9_kbdock_dmi_table[] = {
> + {
> + /* Lenovo Yoga Book 9 14IAH10 */
> + .matches = {
> + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
> + DMI_MATCH(DMI_PRODUCT_NAME, "83KJ"),
> + },
> + },
> + { }
> +};
> +
> +/*
> + * Shared state between the event and block WMI drivers. Protected by lock.
> + * The lock may be held across wmidev_query_block() calls (which can sleep);
> + * block_remove() acquires the lock before clearing block_wdev, ensuring
> + * block_wdev remains valid for the duration of any in-progress query.
> + */
> +static struct {
> + struct mutex lock;
Please add a short comment here on the same line too about what it
protects.
> + struct input_dev *input_dev; /* set by event probe */
> + struct wmi_device *block_wdev; /* set by block probe */
> +} yb9;
> +
> +/*
> + * Read BKBD from the block device via wmidev_block_query().
> + * Returns 0-3 on success, -errno on failure. Caller must hold yb9.lock.
To match kerneldoc formatting guidelines, use:
Context: Caller must hold yb9.lock.
Returns:
> + */
> +static int yb9_kbdock_query_locked(struct device *log_dev)
> +{
> + union acpi_object *obj;
> + u32 bkbd;
> +
> + if (!yb9.block_wdev)
> + return -ENODEV;
> +
> + obj = wmidev_block_query(yb9.block_wdev, 0);
This is interface is deprecated:
* wmidev_block_query - Return contents of a WMI block (deprectated)
> + if (!obj) {
> + dev_warn(log_dev, "WQAF returned NULL\n");
Please add include.
> + return -EIO;
> + }
> +
> + /*
> + * LENOVO_FEATURE_STATUS_DATA: 8-byte buffer {IDs=0x00060000, Status=BKBD}.
> + */
> + if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length >= 8) {
> + memcpy(&bkbd, obj->buffer.pointer + 4, sizeof(bkbd));
> + bkbd &= 0x3;
> + } else if (obj->type == ACPI_TYPE_INTEGER) {
> + bkbd = obj->integer.value & 0x3;
> + } else {
> + dev_warn(log_dev, "WQAF: unexpected type %d len %u\n",
> + obj->type,
> + obj->type == ACPI_TYPE_BUFFER ? obj->buffer.length : 0);
> + kfree(obj);
> + return -EIO;
> + }
> +
> + kfree(obj);
Please add include for kfree().
With the new WMI API, please use __free() to avoid dupli
> + return (int)bkbd;
> +}
> +
> +/* Report SW_TABLET_MODE from BKBD. Caller must hold yb9.lock. */
> +static void yb9_kbdock_report_locked(int bkbd, struct device *log_dev)
> +{
> + int tablet = (bkbd == BKBD_DETACHED) ? 1 : 0;
> +
> + input_report_switch(yb9.input_dev, SW_TABLET_MODE, tablet);
> + input_sync(yb9.input_dev);
> + dev_dbg(log_dev, "BKBD=%d SW_TABLET_MODE=%d\n", bkbd, tablet);
> +}
> +
> +/* ------------------------------------------------------------------
> + * sysfs
> + * ------------------------------------------------------------------ */
> +
> +static ssize_t keyboard_position_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + bkbd = yb9_kbdock_query_locked(dev);
> + mutex_unlock(&yb9.lock);
> +
> + if (bkbd < 0)
> + return bkbd;
> + return sysfs_emit(buf, "%u\n", (unsigned int)bkbd);
> +}
> +static DEVICE_ATTR_RO(keyboard_position);
> +
> +static struct attribute *yb9_kbdock_attrs[] = {
> + &dev_attr_keyboard_position.attr,
> + NULL,
> +};
> +ATTRIBUTE_GROUPS(yb9_kbdock);
> +
> +/* ------------------------------------------------------------------
> + * Event WMI driver — LENOVO_BTKBD_EVENT
> + * ------------------------------------------------------------------ */
> +
> +static void yb9_kbdock_notify(struct wmi_device *wdev, union acpi_object *data)
> +{
> + u32 bkbd;
> +
> + /*
> + * _WED(0xEB) returns EC.BKBD directly as an integer
> + * (LENOVO_BTKBD_EVENT WmiDataId(1) uint32 Status).
> + */
> + if (!data || data->type != ACPI_TYPE_INTEGER) {
> + dev_warn(&wdev->dev, "unexpected event data type %d\n",
> + data ? data->type : -1);
> + return;
> + }
> + bkbd = data->integer.value & 0x3;
> +
> + mutex_lock(&yb9.lock);
guard()
> + if (yb9.input_dev)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_event_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + struct input_dev *input_dev;
> + int bkbd, err;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + input_dev = devm_input_allocate_device(&wdev->dev);
> + if (!input_dev)
> + return -ENOMEM;
> +
> + input_dev->name = "Lenovo Yoga Book 9 keyboard dock switch";
> + input_dev->phys = YB9_KBDOCK_EVENT_GUID "/input0";
> + input_dev->id.bustype = BUS_HOST;
> + input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
> +
> + err = input_register_device(input_dev);
> + if (err)
> + return err;
> +
> + mutex_lock(&yb9.lock);
guard()
> + yb9.input_dev = input_dev;
> + /* Read initial state if the block device has already probed. */
> + if (yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
> +
> + return 0;
> +}
> +
> +static void yb9_kbdock_event_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
guard()
> + yb9.input_dev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static int yb9_kbdock_resume(struct device *dev)
> +{
> + int bkbd;
> +
> + mutex_lock(&yb9.lock);
> + if (yb9.input_dev && yb9.block_wdev) {
> + bkbd = yb9_kbdock_query_locked(dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, dev);
This looks mostly duplicated with the code in yb9_kbdock_event_probe(),
try to consolidate somehow.
> + }
> + mutex_unlock(&yb9.lock);
> + return 0;
> +}
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(yb9_kbdock_pm_ops, NULL, yb9_kbdock_resume);
> +
> +static const struct wmi_device_id yb9_kbdock_event_id_table[] = {
> + { .guid_string = YB9_KBDOCK_EVENT_GUID },
> + { }
> +};
> +MODULE_DEVICE_TABLE(wmi, yb9_kbdock_event_id_table);
> +
> +static struct wmi_driver yb9_kbdock_event_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock",
> + .dev_groups = yb9_kbdock_groups,
> + .pm = pm_sleep_ptr(&yb9_kbdock_pm_ops),
> + },
> + .id_table = yb9_kbdock_event_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_event_probe,
> + .remove = yb9_kbdock_event_remove,
> + .notify = yb9_kbdock_notify,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Block WMI driver — LENOVO_FEATURE_STATUS_DATA
> + * ------------------------------------------------------------------ */
> +
> +static int yb9_kbdock_block_probe(struct wmi_device *wdev, const void *ctx)
> +{
> + int bkbd;
> +
> + if (!dmi_check_system(yb9_kbdock_dmi_table))
> + return -ENODEV;
> +
> + mutex_lock(&yb9.lock);
> + yb9.block_wdev = wdev;
> + /* Read initial state if the event device has already probed. */
> + if (yb9.input_dev) {
> + bkbd = yb9_kbdock_query_locked(&wdev->dev);
> + if (bkbd >= 0)
> + yb9_kbdock_report_locked(bkbd, &wdev->dev);
> + }
> + mutex_unlock(&yb9.lock);
More duplication?
> + return 0;
> +}
> +
> +static void yb9_kbdock_block_remove(struct wmi_device *wdev)
> +{
> + mutex_lock(&yb9.lock);
guard()
> + yb9.block_wdev = NULL;
> + mutex_unlock(&yb9.lock);
> +}
> +
> +static const struct wmi_device_id yb9_kbdock_block_id_table[] = {
> + { .guid_string = YB9_KBDOCK_QUERY_GUID },
> + { }
> +};
> +
> +static struct wmi_driver yb9_kbdock_block_driver = {
> + .driver = {
> + .name = "lenovo-yb9-kbdock-block",
> + },
> + .id_table = yb9_kbdock_block_id_table,
> + .no_singleton = true,
> + .probe = yb9_kbdock_block_probe,
> + .remove = yb9_kbdock_block_remove,
> +};
> +
> +/* ------------------------------------------------------------------
> + * Module init / exit
> + * ------------------------------------------------------------------ */
> +
> +static int __init yb9_kbdock_init(void)
> +{
> + int ret;
> +
> + mutex_init(&yb9.lock);
> +
> + ret = wmi_driver_register(&yb9_kbdock_event_driver);
> + if (ret)
> + return ret;
> +
> + ret = wmi_driver_register(&yb9_kbdock_block_driver);
> + if (ret) {
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> + return ret;
> + }
> + return 0;
> +}
> +module_init(yb9_kbdock_init);
> +
> +static void __exit yb9_kbdock_exit(void)
> +{
> + wmi_driver_unregister(&yb9_kbdock_block_driver);
> + wmi_driver_unregister(&yb9_kbdock_event_driver);
> +}
> +module_exit(yb9_kbdock_exit);
> +
> +MODULE_AUTHOR("Dave Carey <carvsdriver@gmail.com>");
> +MODULE_DESCRIPTION("Lenovo Yoga Book 9 keyboard dock detection");
> +MODULE_LICENSE("GPL");
> --
> 2.54.0
>
--
i.
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-06-09 9:47 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-25 13:23 [PATCH] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-04-28 14:39 ` Ilpo Järvinen
2026-05-17 15:01 ` Dave Carey
2026-05-17 15:02 ` [PATCH v2] platform/x86/lenovo: add Yoga Book 9 keyboard dock driver Dave Carey
2026-05-17 15:25 ` sashiko-bot
2026-05-19 0:02 ` [PATCH v3] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-05-21 14:40 ` [PATCH v4] " Dave Carey
2026-05-22 21:59 ` Armin Wolf
2026-05-26 13:38 ` Dave Carey
2026-05-27 12:27 ` [PATCH v5] " Dave Carey
2026-06-08 9:13 ` johannes.goede
2026-06-08 12:52 ` Dave Carey
2026-06-08 15:55 ` johannes.goede
2026-06-08 19:05 ` Dave Carey
2026-06-08 19:23 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 0/2] " Dave Carey
2026-06-08 21:52 ` [PATCH v6 1/2] platform/x86/lenovo: lenovo-ymc: Suppress probe on Yoga Book 9 14IAH10 Dave Carey
2026-06-09 1:29 ` Mark Pearson
2026-06-09 1:29 ` Mark Pearson
2026-06-09 8:50 ` johannes.goede
2026-06-08 21:52 ` [PATCH v6 2/2] platform/x86/lenovo: Add Yoga Book 9 keyboard dock detection driver Dave Carey
2026-06-09 8:50 ` johannes.goede
2026-06-09 9:47 ` Ilpo Järvinen
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.