Xiaomi Book Pro 14
| 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.
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
|
- The key is visible to
xevand similar tools. - The physical key has a symbol on it, which describes its function.