Asus Zenbook Duo (2024 / UX8406MA) & NixOS

This is my display configuration so far for the boot process. It uses the maximum resolution everywhere and enables KMS as early as possible (with 120Hz for those beautiful logs).

I am investigating turning the lower display off in the initramfs - this can be done statically by adding a lowercase d to the end of the eDP-2 modedb argument, but it cannot be enabled at all after being disabled in this fashion (as far as I can tell). This may be useful to put in a specialisation if you’d rather not deal with the lower monitor in userspace at all, though.

I’m also looking into setting the brightness in the initramfs - it defaults to the maximum, and I’d rather not get the Plymouth password box burnt in to the middle of the screen. (Done: See this post.)

{
  boot = {
    loader.systemd-boot.consoleMode = "max";
    kernelParams =
      let
        efiMode = "2880x1800";
        drmMode = "${efiMode}@120";
      in
      [
        # https://docs.kernel.org/fb/efifb.html
        "video=efifb:${efiMode}"

        # https://docs.kernel.org/fb/modedb.html
        "video=eDP-1:${drmMode}" # Upper
        "video=eDP-2:${drmMode}" # Lower
      ];
    initrd.kernelModules = [ "i915" ]; # Early KMS
    plymouth.extraConfig = ''
      DeviceScale=2
    '';
  };
}

On an unrelated note, I got the NPU (a.k.a. VPU) firmware loading - I’ll leave the Nixpkgs upstreaming to someone with more time and patience than I, but it’s nice to have for the moment.

{
  hardware.firmware = [
    (
      let
        model = "37xx";
        version = "0.0";

        firmware = pkgs.fetchurl {
          url = "https://github.com/intel/linux-npu-driver/raw/v1.2.0/firmware/bin/vpu_${model}_v${version}.bin";
          hash = "sha256-qGhLLiBnOlmF/BEIGC7DEPjfgdLCaMe7mWEtM9uK1mo=";
        };
      in
      pkgs.runCommand "intel-vpu-firmware-${model}-${version}" { } ''
        mkdir -p "$out/lib/firmware/intel/vpu"
        cp '${firmware}' "$out/lib/firmware/intel/vpu/vpu_${model}_v${version}.bin"
      ''
    )
  ];
}

UPDATE (cannot post more than thrice in a row)

Alright, with a lot of experimentation I’ve got the keyboard hotkeys working over Bluetooth. Here’s what I’ve discovered so far:

  1. ASUS keyboards send hotkey events over custom HID reports. The ASUS HID driver turns these custom reports into standard keypresses, and is mostly compatible with this laptop too.
  2. The relevant HID descriptor is slightly different from what the driver expects, and so it needs to be modified. Luckily, a similar thing has already been done for previous detachable ASUS keyboards. Unfortunately, the logic is not exactly the same (the byte layout is a little different) - but it’s close enough, so I used it for a working prototype. The kernel can handle the slightly mangled resultant descriptor.
  3. The ASUS HID driver will prefer to use WMI for backlight control if it is available. This is not great for our usecase (I don’t think it works with Bluetooth or even USB - it’s probably there for legacy reasons), so this behaviour needs to be changed. There’s also no reason that the backlight should be disabled if the keyboard is plugged in to another ASUS laptop, either.
  4. The keyboard actually shows up as two HID devices: One for the keyboard itself, and one for the touchpad. Oddly, the function key by itself actually does generate HID reports - but on the touchpad device, for some reason. I don’t think any driver currently takes advantage of this.
  5. Over USB, the relevant hotkey HID report descriptors are missing entirely, which is why I haven’t been able to get it working. There’s also some sort of third HID device, but I can’t seem to generate any events from it. The function key reports over the touchpad are still available, though, so that might be an easy way to get similar functionality.
  6. I have been unable to get the backlight working in any capacity. This requires more investigation.

I think my next plan of action is to look at the USB HID descriptors and run some packet capture in Windows to see how it compares to Linux.

CC @mfenniak as you were working on this too - you were right about the initialization step, but luckily hid_asus can already do it.

Here’s my prototype kernel patch. Note that for development, I have been using CCache along with the technique to patch in-tree modules to keep things nice and fast (not that anything is particularly slow on this thing anyway!).

diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c
index 78cdfb8b9a7a..0450ad7424a8 100644
--- a/drivers/hid/hid-asus.c
+++ b/drivers/hid/hid-asus.c
@@ -84,6 +84,7 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad");
 #define QUIRK_MEDION_E1239T		BIT(10)
 #define QUIRK_ROG_NKEY_KEYBOARD		BIT(11)
 #define QUIRK_ROG_CLAYMORE_II_KEYBOARD BIT(12)
+#define QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD	BIT(13)
 
 #define I2C_KEYBOARD_QUIRKS			(QUIRK_FIX_NOTEBOOK_REPORT | \
 						 QUIRK_NO_INIT_REPORTS | \
@@ -835,7 +836,7 @@ static int asus_input_configured(struct hid_device *hdev, struct hid_input *hi)
 	drvdata->input = input;
 
 	if (drvdata->enable_backlight &&
-	    !asus_kbd_wmi_led_control_present(hdev) &&
+	    (!asus_kbd_wmi_led_control_present(hdev) || (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) &&
 	    asus_kbd_register_leds(hdev))
 		hid_warn(hdev, "Failed to initialize backlight.\n");
 
@@ -897,7 +898,9 @@ static int asus_input_mapping(struct hid_device *hdev,
 		case 0xb3: asus_map_key_clear(KEY_PROG3);	break; /* Fn+Left next aura */
 		case 0x6a: asus_map_key_clear(KEY_F13);		break; /* Screenpad toggle */
 		case 0x4b: asus_map_key_clear(KEY_F14);		break; /* Arrows/Pg-Up/Dn toggle */
-
+		case 0x9c: asus_map_key_clear(KEY_F15);     break; /* Screen swap */
+		case 0x7e: asus_map_key_clear(KEY_F16);     break; /* Emoji panel */
+		case 0x86: asus_map_key_clear(KEY_F17);     break; /* MyASUS */
 
 		default:
 			/* ASUS lazily declares 256 usages, ignore the rest,
@@ -1183,17 +1186,20 @@ static __u8 *asus_report_fixup(struct hid_device *hdev, __u8 *rdesc,
 		hid_info(hdev, "Fixing up Asus T100 keyb report descriptor\n");
 		rdesc[74] &= ~HID_MAIN_ITEM_CONSTANT;
 	}
-	/* For the T100CHI/T90CHI keyboard dock */
-	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI)) {
+	/* For the T100CHI/T90CHI keyboard dock and Zenbook Duo 2024+ keyboards */
+	if (drvdata->quirks & (QUIRK_T100CHI | QUIRK_T90CHI | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD)) {
 		int rsize_orig;
 		int offs;
 
 		if (drvdata->quirks & QUIRK_T100CHI) {
 			rsize_orig = 403;
 			offs = 388;
-		} else {
+		} else if (drvdata->quirks & QUIRK_T90CHI) {
 			rsize_orig = 306;
 			offs = 291;
+		} else if (drvdata->quirks & QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD) {
+			rsize_orig = 257;
+			offs = 176;
 		}
 
 		/*
@@ -1298,6 +1304,12 @@ static const struct hid_device_id asus_devices[] = {
 	 */
 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
 		USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_T101HA_KEYBOARD) },
+	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+		QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
+	{ HID_DEVICE(BUS_BLUETOOTH, HID_GROUP_GENERIC,
+	    USB_VENDOR_ID_ASUSTEK, BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD),
+	  	QUIRK_USE_KBD_BACKLIGHT | QUIRK_ZENBOOK_DUO_REMOVABLE_KEYBOARD },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, asus_devices);
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 828a5c022c64..8d9a3e29f28f 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -208,6 +208,8 @@
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD	0x1866
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2	0x19b6
 #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3	0x1a30
+#define USB_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD	0x1b2c
+#define BT_DEVICE_ID_ASUSTEK_ZENBOOK_DUO_UX8406_KEYBOARD    0x1b2d
 #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD	0x196b
 #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD	0x1869
 

6 Likes