Jump to content

Xiaomi Book Pro 14

From ArchWiki
Hardware PCI/USB ID Working?
GPU 8086:b090 Yes
Wi-Fi 8086:e440 Yes
Bluetooth 8086:e476 Yes
Webcam 3277:00ff Yes
Touchpad I2C 347D:7853 Yes
Keyboard PS/2 No
TPM ACPI INTC7002 Yes
Fingerprint reader 27c6:6890 No
Audio 8086:e428 Yes
NPU 8086:b03e Yes

The Xiaomi Book Pro 14 Laptop features a 14.55" 3120x2080 OLED display, an Intel Panther Lake Ultra 5 325 processor, and integrated Intel Graphics.

For a general overview of laptop-related articles and recommendations, see Laptop.

Installation

The single most important kernel parameter is i8042.dumbkbd=1. Without it, the built-in keyboard works in GRUB but stops responding once the kernel has booted, both in TTY and in desktop environments.

Another recommended parameter is xe.enable_psr=0, which disables Panel Self Refresh on the Xe GPU driver, fixing screen flickering. (Strongly recommended, but not mandatory if you are TTY-only)

For example, setting the following variable:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i8042.dumbkbd=1 xe.enable_psr=0"

To disable Secure Boot, a UEFI password has to be set first. After that the password can be removed.

KeyBoard

Unless you compile the kernel and use the xiaomi keyboard kernel patch file, the keyboard won't work at all without adding keyboard kernel parameters.Get the Kernel Patch File

Accessibility

This device offers no accessibility for blind users. The built-in keyboard requires the i8042.dumbkbd=1 kernel parameter, which must be added to the GRUB configuration before the keyboard becomes functional. Editing the boot configuration requires eyesight.

Note Blind users should request the help of a sighted person to install and configure this device.

Fingerprint reader

The fingerprint reader is a Goodix MOC (Match-on-Chip) sensor (27c6:6890), handled by the goodixmoc driver in libfprint.

The stock libfprint does not include 27c6:6890 in the goodixmoc driver's ID table, so fprintd will not detect the device. The driver must be manually compiled with support for 27c6:6890 added to the driver's ID table and installed over the system libfprint.

See Fprint for general fingerprint reader configuration.

Caps Lock LED

Because the keyboard is driven in dumb mode (i8042.dumbkbd=1), the keyboard controller cannot be commanded to update the LED state. The Caps Lock LED does not reflect the toggled state.

The code below works around this: it listens for lock key events on the keyboard's evdev device and writes the PS/2 0xED LED command followed by the LED state byte directly to the controller's data port (0x60) through /dev/port whenever a lock key is toggled. The initial LED state is read via KDGETLED on the TTY, or via xset q when running under X11.

Build and install the daemon and its systemd unit:

The complete source code of the daemon:

caps-led-sync.c
/*
 * caps-led-sync.c
 * Listen for lock key events and sync the keyboard LEDs via /dev/port
 * by sending the PS/2 0xED command.
 *
 * Phase state machine: each lock key is tracked independently through
 * phases 0->1->2->3->0.
 *   Phase 0 (OFF): key pressed  -> set ON, sync, enter phase 1
 *   Phase 1 (ON):  key released -> skip, enter phase 2
 *   Phase 2 (ON):  key pressed  -> skip, enter phase 3
 *   Phase 3 (ON):  key released -> set OFF, sync, back to phase 0
 * Initial state is obtained via KDGETLED (TTY) or xset q (X11).
 *
 * Purely event-driven: select() blocks, fixed delays, no polling.
 *
 * Build: gcc -O2 -Wall -o caps-led-sync caps-led-sync.c
 * Run:   pkexec ./caps-led-sync
 */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/input-event-codes.h>
#include <linux/kd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <unistd.h>

/* -- PS/2 ------------------------------------------------------ */
#define PS2_DATA    0x60
#define PS2_LED_CMD 0xED

#define LED_SCR 0x01
#define LED_NUM 0x02
#define LED_CAP 0x04

static volatile sig_atomic_t running = 1;
static void sig_handler(int sig) { (void)sig; running = 0; }

/* -- /dev/port ------------------------------------------------ */
static int port_fd = -1;

static void port_write(int port, unsigned char val) {
    if (lseek(port_fd, port, SEEK_SET) < 0) return;
    if (write(port_fd, &val, 1) != 1) return;
}

/* -- send 0xED + LED byte ------------------------------------- */
static int ps2_sync_leds(int caps, int num, int scroll) {
    unsigned char led = 0;
    if (scroll) led |= LED_SCR;
    if (num)    led |= LED_NUM;
    if (caps)   led |= LED_CAP;

    /* The PS/2 controller responds in microseconds, so fixed delays
     * are sufficient; no need to poll the status register. */
    usleep(2000);
    port_write(PS2_DATA, PS2_LED_CMD);
    usleep(2000);
    port_write(PS2_DATA, led);
    return 0;
}

/* -- get initial lock state from X11 via xset q --------------- */
static int get_x11_caps_state(void) {
    const char *disp = getenv("DISPLAY");
    const char *xauth = getenv("XAUTHORITY");
    char cmd[512];
    snprintf(cmd, sizeof(cmd),
             "DISPLAY=%s XAUTHORITY=%s xset q 2>/dev/null | grep 'Caps Lock'",
             disp ? disp : ":0",
             xauth ? xauth : "");
    FILE *fp = popen(cmd, "r");
    if (!fp) return 0;
    char buf[256];
    int caps = 0, num = 0, scroll = 0;
    if (fgets(buf, sizeof(buf), fp)) {
        /* Parse: "00: Caps Lock:   on/off  ..." */
        char *p = strstr(buf, "Caps Lock:");
        if (p && strstr(p, "on"))  caps   = 1;
        p = strstr(buf, "Num Lock:");
        if (p && strstr(p, "on"))  num    = 1;
        p = strstr(buf, "Scroll Lock:");
        if (p && strstr(p, "on"))  scroll = 1;
    }
    pclose(fp);
    /* Encode caps/num/scroll in the low three bits of the return value */
    return caps | (num << 1) | (scroll << 2);
}

static int read_leds_tty(int *caps, int *num, int *scroll) {
    int fd = open("/dev/tty0", O_RDWR);
    if (fd < 0) return -1;
    unsigned char led;
    if (ioctl(fd, KDGETLED, &led) < 0) { close(fd); return -1; }
    close(fd);
    *caps   = !!(led & LED_CAP);
    *num    = !!(led & LED_NUM);
    *scroll = !!(led & LED_SCR);
    return 0;
}

/* -- probe for the keyboard evdev device ---------------------- */
static int find_keyboard(char *out_path, size_t len) {
    DIR *dir = opendir("/dev/input");
    struct dirent *entry;
    int best_fd = -1;
    if (!dir) return -1;

    while ((entry = readdir(dir)) != NULL) {
        if (strncmp(entry->d_name, "event", 5) != 0) continue;
        char full[256];
        snprintf(full, sizeof(full), "/dev/input/%s", entry->d_name);
        int fd = open(full, O_RDONLY | O_NONBLOCK);
        if (fd < 0) continue;

        unsigned long evbits[EV_CNT / 8 + 1];
        memset(evbits, 0, sizeof(evbits));
        if (ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits) < 0)
            { close(fd); continue; }
        if (!(evbits[EV_KEY / 8] & (1UL << (EV_KEY % 8))))
            { close(fd); continue; }

        unsigned long keybits[KEY_CNT / (sizeof(unsigned long) * 8) + 1];
        memset(keybits, 0, sizeof(keybits));
        if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) < 0)
            { close(fd); continue; }
        if (!(keybits[KEY_CAPSLOCK / (sizeof(unsigned long) * 8)]
              & (1UL << (KEY_CAPSLOCK % (sizeof(unsigned long) * 8)))))
            { close(fd); continue; }

        if (best_fd >= 0) close(best_fd);
        best_fd = fd;
        strncpy(out_path, full, len - 1);
        out_path[len - 1] = '\0';
    }
    closedir(dir);
    return best_fd;
}

int main(void) {
    char dev_path[256];
    int kbd_fd;
    struct input_event ev;
    fd_set fds;

    signal(SIGTERM, sig_handler);
    signal(SIGINT,  sig_handler);

    /* Open /dev/port */
    port_fd = open("/dev/port", O_RDWR);
    if (port_fd < 0) { perror("/dev/port"); return 1; }

    /* Probe for the keyboard */
    kbd_fd = find_keyboard(dev_path, sizeof(dev_path));
    if (kbd_fd < 0) {
        fprintf(stderr, "No keyboard found.\n");
        close(port_fd);
        return 1;
    }
    printf("Keyboard: %s waiting for lock key events...\n", dev_path);
    fflush(stdout);

    /* Initial sync */
    int caps = 0, num = 0, scroll = 0;
    if (read_leds_tty(&caps, &num, &scroll) < 0) {
        /* TTY unavailable -> try X11 (xset q) */
        int state = get_x11_caps_state();
        caps   = state & 1;
        num    = (state >> 1) & 1;
        scroll = (state >> 2) & 1;
    }
    /* Phases: 0=OFF, 1=ON wait 1st release, 2=ON wait 2nd press,
     *         3=ON wait 2nd release */
    int ph_c = caps ? 1 : 0, ph_n = num ? 1 : 0, ph_s = scroll ? 1 : 0;
    ps2_sync_leds(caps, num, scroll);
    printf("Initial (0xED): Caps=%d Num=%d Scroll=%d\n", caps, num, scroll);
    fflush(stdout);

    /* Event-driven main loop */
    while (running) {
        FD_ZERO(&fds);
        FD_SET(kbd_fd, &fds);

        int ret = select(kbd_fd + 1, &fds, NULL, NULL, NULL);
        if (ret < 0) { if (errno == EINTR) continue; break; }

        ssize_t n = read(kbd_fd, &ev, sizeof(ev));
        if (n != sizeof(ev)) continue;
        if (ev.type != EV_KEY) continue;
        if (ev.code != KEY_CAPSLOCK &&
            ev.code != KEY_NUMLOCK  &&
            ev.code != KEY_SCROLLLOCK) continue;

        /* Select the state and phase for the current key */
        int *led, *ph;
        const char *name;
        switch (ev.code) {
        case KEY_CAPSLOCK:   led = ∩︀   ph = &ph_c;   name = "CapsLock";   break;
        case KEY_NUMLOCK:    led = #    ph = &ph_n;   name = "NumLock";    break;
        case KEY_SCROLLLOCK: led = &scroll; ph = &ph_s;   name = "ScrollLock"; break;
        default: continue;
        }

        int sync = 0;
        switch (*ph) {
        case 0: /* OFF, waiting for press */
            if (ev.value == 1) { *led = 1; *ph = 1; sync = 1; }
            break;
        case 1: /* ON, just pressed, waiting for 1st release */
            if (ev.value == 0) { *ph = 2; }
            break;
        case 2: /* ON, 1st release done, waiting for 2nd press */
            if (ev.value == 1) { *ph = 3; }
            break;
        case 3: /* ON, 2nd press done, waiting for 2nd release */
            if (ev.value == 0) { *led = 0; *ph = 0; sync = 1; }
            break;
        }

        if (sync) {
            if (ps2_sync_leds(caps, num, scroll) == 0) {
                printf("%s %s -> 0xED: Caps=%d Num=%d Scroll=%d\n",
                       name, ev.value ? "pressed" : "released", caps, num, scroll);
                fflush(stdout);
            }
        }
    }

    printf("Exiting.\n");
    close(port_fd);
    close(kbd_fd);
    return 0;
}

The systemd unit:

caps-led-sync.service
[Unit]
Description=Caps Lock LED sync service (syncs keyboard LEDs via the 0xED command)
Documentation=https://wiki.archlinux.org/title/Keyboard_input
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/bin/caps-led-sync
Restart=always
RestartSec=3
StandardOutput=journal
StandardError=journal

# Hardening
NoNewPrivileges=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

Compile and install the daemon and its systemd unit:

$ gcc -O2 -Wall -Wextra -o caps-led-sync caps-led-sync.c
$ sudo install -Dm755 caps-led-sync /usr/local/bin/caps-led-sync
$ sudo install -Dm644 caps-led-sync.service /etc/systemd/system/
$ sudo systemctl enable --now caps-led-sync

Because the daemon writes directly to /dev/port, it must run as root.

Function keys

Key Visible?1 Marked?2 Effect
Fn+Esc No Yes Enable Fn lock
Fn+F1 Yes Yes XF86AudioMute
Fn+F2 Yes Yes XF86AudioLowerVolume
Fn+F3 Yes Yes XF86AudioRaiseVolume
Fn+F4 No Yes Mute Microphone
Fn+F5 Yes Yes XF86MonBrightnessDown
Fn+F6 Yes Yes XF86MonBrightnessUp
Fn+F7 No Yes Xiao AI
Fn+F8 No Yes Project
Fn+F9 No Yes Settings
Fn+F10 No Yes Toggle Keyboard Backlight
Fn+F11 Yes Yes Print
Fn+F12 Yes Yes Insert
Copilot Yes Yes Win + Left Shift + F23
  1. The key is visible to xev and similar tools.
  2. The physical key has a symbol on it, which describes its function.

See also