PunchSpain mourns after latest train tragedy kills 69ESPNEvery Premier League team ranked by bang-for-buck: What if they spent like Man City?Bollywood HungamaBhumi Pednekar reunites with Akshay Kumar for Priyadarshan’s next comedyInquirerMMDA suspends coding scheme on Monday, Nov. 2UN NewsAfghanistan opium production jumps fivefold as farmers find ways round Taliban banDaily MaverickEl Salvador to send over 2,000 soldiers to Haiti for rescue while elections postponedEl ComercioEstados Unidos revela su hoja de ruta para Venezuela: reformas, vacancia de Delcy Rodríguez y eleccionesZDF heuteAktuelle Pressemitteilungen des ZDFFotogramas"Perfecta para ver en familia en Halloween": La película con la que vuelve a los cines el personaje más entrañable de la animación 'stop motion'Business AMDuitse elitesoldaten van KSK bereiden zich voor op mogelijke oorlog met RuslandSoompiSon Suk Ku And Nagayama Eita’s Opposing Detective Styles Complicate Their Partnership In “The Blue Road”Egypt IndependentHeal the victim, heal the world – how Sri Sri Ravi Shankar reclaims lives in war zones, prisons, and rural communities
The Daily Newsstand · Free, Always
Thursday, October 29, 2026

Arch Linux на Xiaomi 12T Pro: стоковое ядро Android, vendor‑модули и девять граблей загрузки

Translate

На моём Xiaomi 12T Pro загружается Arch Linux. Это не Termux и не chroot, а полноценная система: штатное ядро телефона, systemd, Wayland. Android остаётся рядом, при включении выбор делается кнопкой громкости. Ниже: как это устроено, что работает и на какие грабли я наступил по дороге. Большинство из них не привязаны к модели и встретятся на любом Qualcomm‑телефоне с GKI.

Скриншоты

Устройство

Xiaomi 12T Pro, он же Redmi K50 Ultra (diting): Snapdragon 8+ Gen 1, Adreno 730, 12 ГБ ОЗУ. Загрузчик разблокирован.

Подход: ядро от Android, система от Arch

Для Snapdragon 8+ Gen 1 нет готового mainline‑порта этой платы: нет ни дерева устройств, ни драйвера панели, ни тача. Собирать ядро из исходников Xiaomi тоже плохая идея: пересобранное ядро ломает ABI vendor‑модулей, и пересобирать тогда придётся всё.

Поэтому схема такая:

  • ядро — бинарный GKI от Google той же сборки, что стоит на телефоне (совпадает байт в байт, номер сборки виден в uname -r);

  • драйверы — готовые vendor‑модули из разделов vendor_boot и vendor_dlkm самого телефона;

  • всё остальное — чистый Arch Linux ARM.

От Android в системе остаются только ядро, модули и прошивки сопроцессоров. Arch лежит файлом‑образом в /data, сам Android не трогается.

Главное правило: ничего не прошивать, пока всё не работает через fastboot boot. Так система загружается один раз из памяти: если что‑то не так, перезагружаешь телефон, и он снова в Android.

Как грузится

fastboot boot boot.img  └─ загрузчик склеивает vendor_ramdisk + наш ramdisk, добавляет bootconfig       └─ ядро GKI распаковывает оба архива, наш /init перекрывает андроидовский            └─ /init (busybox): модули → /data → USB-сеть → switch_root в rootfs.img                 └─ systemd, Arch Linux ARM

Звучит просто. На деле почти каждая строчка стоила одной или нескольких перезагрузок вслепую: экрана ещё нет, логов тоже.

Как отлаживаться без экрана и логов

Пока не поднялись ни экран, ни USB, единственный канал связи с телефоном — вернулся он сам или нет. Я собирал initramfs, в котором /init делает одно: «перезагрузись через 5 секунд». Если телефон вернулся в fastboot сам через ~20 секунд, значит, наш init запустился. Если завис, проблема раньше. Дальше /init по кусочку обрастает кодом, и каждая гипотеза проверяется тем же способом.

Когда смонтирован /data, логи пишутся туда, и их можно забрать из Android через adb.

Грабли по порядку

1. Чёрный экран навсегда. Ramdisk производителя сжат lz4 -l (legacy‑формат, без маркера конца потока). Распаковав его, ядро не переключается на gzip нашего архива, и /init просто не появляется. Решение: свой архив тоже сжимать в lz4 legacy.

2. Модули «грузятся», но диска и USB нет. В modules.load от Qualcomm 118 нарушений порядка зависимостей. Android этого не замечает: его libmodprobe читает modules.dep. А голый insmod по списку работает строго по порядку. Пришлось написать рекурсивный загрузчик: сначала зависимости, потом softdep pre, потом сам модуль, потом softdep post.

3. Без softdep USB не поднимется никогда. PHY eUSB2, загруженный раньше своего репитера, не откладывает загрузку, а падает с ошибкой насовсем. Правильный порядок задан только в modules.softdep.

4. Экран «работает», но драйвер выбирает не ту панель. Параметр msm_drm.dsi_display0=… из командной строки ядра доходит до модуля, только если модуль грузит modprobe или libmodprobe: они сами читают /proc/cmdline. Busybox insmod этого не делает. Без параметра драйвер берёт эталонную панель Qualcomm 1080×2340, и кадры не идут. Решение: доставать параметры модуля из cmdline самому.

Загрузчик из пунктов 2–4 целиком (busybox sh):

loadmod() { # $1 = имя модуля (x.ko или x)    local n f d sd opts    n=$(basename "$1" .ko | tr '-' '_')    grep -q "^$n " /proc/modules && return 0    f=$(modfile "$(basename "$1" .ko)") || return 1    # жёсткие зависимости    for d in $(grep -E "^(.*/)?$(basename "$f"):" "$MODDIR/modules.dep" | cut -d: -f2); do        loadmod "$(basename "$d")"    done    # softdep pre    sd=$(sed -n "s/^softdep $n .*pre: *\(.*\)/\1/p" "$MODDIR/modules.softdep" | sed 's/ *post:.*//')    for d in $sd; do loadmod "$d"; done    # параметры из cmdline (module.param=value), как делает modprobe    opts=$(tr ' ' '\n' < /proc/cmdline | sed -n "s/^$n\.\([^=]*=.*\)/\1/p" | tr '\n' ' ')    insmod "$f" $opts || return 1    # softdep post    sd=$(sed -n "s/^softdep $n .*post: *\(.*\)/\1/p" "$MODDIR/modules.softdep")    for d in $sd; do loadmod "$d"; done
}

5. В GKI нет devtmpfs. Android создаёт узлы в /dev своим ueventd. У нас вместо этого tmpfs и mdev, а в Arch mdev работает демоном. Отдельная история: systemd без devtmpfs не слушает udev, и половина служб, завязанных на устройства, не стартует.

6. USB‑гаджет «поднялся», но компьютер его не видит. Причин две. В ядре есть фиктивный dummy_udc.0, и если взять первый попавшийся UDC, гаджет уедет туда. А контроллер dwc3 от Qualcomm спит, пока ему не сообщат роль порта (в Android это делает сопроцессор, которого у нас на этом этапе нет). Роль device нужно записать до ожидания UDC.

7. Android не видит файлы, созданные Linux. Всё, что Linux пишет на /data, для SELinux в Android остаётся «без метки», и adb не может такие файлы ни прочитать, ни перезаписать. Решение: каталоги и файлы создаются через adb, а Linux только дописывает в уже существующие (> сохраняет inode и метку, mv нет).

8. Телефон «сам выключился». Дважды. Кнопка питания приходит в Linux обычной клавишей, logind видит её и честно выключает систему. HandlePowerKey=ignore нужно поставить раньше, чем кто‑нибудь нажмёт кнопку.

9. pacman не ставит пакеты. pacman 7 требует Landlock, а в GKI его нет. Лечится DisableSandbox в pacman.conf.

Графика

Своего GL‑драйвера под этот GPU в Linux нет, доступен только Vulkan через turnip (драйвер Mesa) с бэкендом KGSL. Поэтому GL для композитора — это zink поверх turnip, плюс небольшой патч, чтобы turnip знал узел дисплея. Разница заметная: vkcube на весь экран на GPU даёт 60 кадров/с при 3% одного ядра, на CPU — 30 кадров/с при 54%.

Двойная загрузка: меню без экрана

Для постоянной работы свой boot.img прошивается в раздел boot. Внутри лежат и наш initramfs, и стоковый ramdisk Android. При включении /init первым делом грузит модули кнопок и вибромотора, телефон коротко вибрирует и три секунды ждёт: Vol+ — Arch, Vol− — Android. Если ничего не нажать, грузится то, что выбирали в прошлый раз. Экрана в этот момент ещё нет, так что меню — это вибрация.

Счётчик попыток A/B

Самая неожиданная проблема была здесь. Примерно через семь загрузок в Arch телефон перестал загружаться совсем и остался в fastboot.

Оказалось, загрузчик (ABL) при каждом старте уменьшает счётчик попыток слота, пока система не поставит бит «загрузка успешна». Всё это хранится в атрибутах GPT‑записи boot_a/boot_b:

Биты

Значение

50

слот активен

51–53

осталось попыток

54

загрузка успешна

55

слот незагружаемый

Android ставит бит 54 сам (это делает boot_control HAL), Arch — нет. Когда счётчик доходит до нуля, слот помечается незагружаемым. На месте это лечится fastboot set_active a, навсегда — маленькой службой, которая при каждой загрузке ставит бит 54 и сбрасывает счётчик.

Менять нужно 8 байт атрибутов в первичной GPT и пересчитать два CRC (массива записей и заголовка). Ровно это делает сам ABL. sfdisk и sgdisk здесь не годятся: резервная GPT у Qualcomm лежит не там, где они её ищут, они сочтут её повреждённой и перезапишут. Суть на Python:

SUCCESS, UNBOOTABLE = 1 << 54, 1 << 55
RETRY_SHIFT, RETRY_MASK = 51, 0x7
hdr = bytearray(os.pread(fd, ss, ss))                 # заголовок первичной GPT (LBA 1)
elba, n, esz, _ = struct.unpack_from("<QIII", hdr, 72)
ents = bytearray(os.pread(fd, n * esz, elba * ss))    # массив записей
a = struct.unpack_from("<Q", ents, i * esz + 48)[0]   # атрибуты записи boot_<слот>
a = (a | SUCCESS) & ~UNBOOTABLE
a = (a & ~(RETRY_MASK << RETRY_SHIFT)) | (RETRY_MASK << RETRY_SHIFT)
struct.pack_into("<Q", ents, i * esz + 48, a)
struct.pack_into("<I", hdr, 88, zlib.crc32(ents))     # CRC записей
hsize = struct.unpack_from("<I", hdr, 12)[0]
h = bytearray(hdr[:hsize]); struct.pack_into("<I", h, 16, 0)
struct.pack_into("<I", hdr, 16, zlib.crc32(h))        # CRC заголовка

Дальше записываются обратно только изменённый сектор записей и заголовок.

Что работает

  • экран 120 Гц, тач, вибрация, автоповорот и автояркость;

  • GPU: Vulkan и композитинг на Adreno 730;

  • Wi‑Fi, Bluetooth, звук;

  • зарядка, батарея, сон;

  • камера (фото, ночной режим, видео 4K), аппаратное декодирование видео;

  • GPS, NPU Hexagon;

  • USB OTG (мышь, клавиатура, флешки), ИК‑порт.

Сверху стоит Wayland‑оболочка, рассчитанная на сенсорный экран: рабочие столы с виджетами, две шторки, меню приложений, список недавних, экранная клавиатура.

Ещё скриншоты

Windows‑игры

Windows‑игры идут через Wine 11 с патчами для ARM64EC, FEX и WoW64 (их набралось больше двадцати), DXVK работает поверх turnip. Касания работают как мышь, а экранный геймпад изображает контроллер Xbox 360.

Android‑приложения

Android работает в контейнере (LineageOS 20), а его приложения открываются обычными окнами рядом с Linux‑программами.

Итог

Самое ценное, что я вынес: на Qualcomm с GKI не нужно ни mainline‑ядро, ни сборка ядра из исходников производителя. Достаточно ядра и модулей, которые уже лежат в телефоне. Нужно только повторить руками то, что за тебя молча делают ueventd, libmodprobe и boot_control HAL в Android. Почти все грабли выше — как раз про это.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.