Xiaomi Book Pro 14 - ArchWiki

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

- Xiaomi RedmiBook Pro 16 2025
- Xiaomi PC Manager Drivers (TM2409)
- Linux Hardware Database

Category:

- Xiaomi

Xiaomi Book Pro 14
Add topic