* [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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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; 10+ 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] 10+ 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 1 sibling, 0 replies; 10+ 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] 10+ messages in thread
end of thread, other threads:[~2026-05-27 12:27 UTC | newest] Thread overview: 10+ 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
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.