Platinum One OS на базе Samsung M52 звонки, смс, Vulkan, Misa

—

от автора

Platinum OS One на Samsung M52: Vulkan, собственная телефония и Misa внутри рабочего стола

Platinum OS One на Samsung M52: Vulkan, собственная телефония и Misa внутри рабочего стола

Первый запуск Linux на телефоне выглядит как победа: появляется рабочий стол, реагирует сенсор, открывается терминал. Потом начинаешь пользоваться устройством и обнаруживаешь, сколько работы скрывалось за этой картинкой.

После перезагрузки экран может остаться чёрным. Модем обнаруживает SIM, но не регистрируется в сети. DSP сообщает, что запущен, а ALSA по-прежнему не видит звуковых карт. Трёхмерная модель ассистента отображается, но стоит в T-позе.

На Samsung M52 я прохожу именно этот этап разработки Platinum OS One: превращаю загружающуюся Linux-систему в устройство, которым можно пользоваться как смартфоном. На момент этой статьи на телефоне работают оболочка с Vulkan, сенсорный ввод, Wi-Fi и SSH. SMS, звонки и USSD я проверил на самом устройстве. Misa получила трёхмерный аватар и подключение к своему диалоговому серверу.

При этом аудиоподсистема, камеры и устойчивость графики ещё требуют работы. Ниже — устройство системы, несколько фрагментов реального кода и ошибки, которые оказались наиболее полезными для понимания платформы.

Ubuntu userspace и аппаратная платформа Samsung

Platinum OS One строится вокруг Ubuntu Base 26.04 LTS и собственных компонентов. Samsung M52 — одна из целевых платформ. Сборочная система должна оставаться общей для разных устройств, а аппаратные особенности приходят из конфигурации платы, ядра, Device Tree и firmware.

На M52 используется vendor-ядро Samsung на базе Linux 5.4. Здесь это практический выбор: значительная часть поддержки дисплея, модема, аудио и камер связана с downstream-драйверами Qualcomm и Samsung.

Текущая загрузка устроена так:

Qualcomm / Samsung boot chain              |       загрузчик Samsung              |       +------+------+       |             |      BOOT        RECOVERY       |             | штатное ядро    ядро Platinum   Android       + initramfs       |             |     One UI      rootfs на microSD                     |                  systemd                     |                SDDM / Cage                     |             Platinum Shell                 Qt / QML

Собственная часть ранней загрузки Platinum — initramfs и ранний init. Загрузчик Samsung остаётся на месте.

Разделение ядер позволило сохранить штатный путь Android и отдельно развивать Platinum. Для переключения приходится учитывать состояние Bootloader Control Block: оставленная команда boot-recovery способна снова направить устройство в RECOVERY после обычной перезагрузки. Поэтому в Platinum есть служба, очищающая именно поле команды в BCB.

Это важная граница ответственности: переключение ОС не должно превращаться в операцию над пользовательскими данными Android.

Сборочная система написана на Rust. Её основная модель достаточно простая:

use anyhow::Result;use crate::BuildContext;/// Независимый этап конвейера сборки образа.pub trait Stage {    /// Стабильное имя этапа для журналирования.    fn name(&self) -> &'static str;    /// Выполняет этап и обновляет контекст сборки.    fn execute(&self, context: &mut BuildContext) -> Result<()>;}

BuildEngine собирает последовательность этапов, Pipeline выполняет их и фиксирует результат и длительность. Аппаратные особенности не должны расползаться по этому конвейеру в виде условий if board == ....

При этом весь проект не написан на одном языке. Ядро и его исправления — C, оболочка — Qt/QML, сборочная инфраструктура — Rust. Часть служб аппаратного bring-up написана на Python: на этом этапе возможность быстро проверить последовательность QMI-вызовов полезнее преждевременного переноса ещё меняющегося протокола.

Самая неприятная ошибка дисплея возникла до запуска оболочки

Повторяющийся чёрный экран сначала легко принять за ошибку QML, Vulkan или менеджера входа. В одном из проблемных запусков Qt вообще не успевал стартовать: Cage зависал внутри операции установки видеорежима.

В журнале ядра появились два связанных симптома:

cont_splash feature not enabledmsm_smmu_fault_handler ... iova=0xe1000000

За ними шли ошибки передачи команд DSI.

Адрес 0xe1000000 оказался существенной зацепкой: по Device Tree там находился framebuffer загрузчика. Дисплей мог продолжать читать эту память, пока Linux уже менял состояние отображений SMMU.

В исходниках нашёлся ранний выход из sdekms_get_splash_data():

memset(data, 0, sizeof(*data));return -EINVAL;

Он отключал обработку continuous splash. Этот обход ранее появился из-за другого падения при передаче управления дисплеем. В результате мы обходили один проблемный участок, одновременно пропуская нужную часть инициализации.

Разбор первоначального падения привёл к обращению через sde_enc->crtc до привязки CRTC. Исправление получилось небольшим:

priv = drm_enc->dev->dev_private;/* Splash handoff can enable resources before a CRTC is attached. * Keep them on until the first real commit can schedule idle work. */if (!sde_enc->crtc || sde_enc->crtc->index >=        ARRAY_SIZE(priv->disp_thread)) {    SDE_DEBUG_ENC(sde_enc, "skip idle work without a valid CRTC\n");    return;}disp_thread = &priv->disp_thread[sde_enc->crtc->index];

Одновременно был восстановлен штатный путь обработки splash.

Здесь важно, что ранний return теперь относится к постановке отложенной работы управления питанием. Он не выключает весь механизм передачи framebuffer от загрузчика.

После сборки и установки нового RECOVERY журнал показал:

cont_splash enabled in 1 of 1 display(s)release splash buffer: addr: e1000000, size: 2300000

Platinum загрузилась с изображением. При проверке этого запуска прежние ошибки SMMU и DSI не повторились.

Перед установкой дополнительно проверили совместимость модулей: CRC используемых символов для 83 модулей прошли проверку, набор из 13 849 экспортируемых символов ядра сохранил прежние CRC. Такая проверка не заменяет загрузку на устройстве, но помогает не получить заодно сломанный Wi-Fi после исправления дисплея.

После этого возникло отдельное зависание при открытии «Сообщений». Причина пока не установлена. Поэтому корректный результат данного этапа — исправленный и подтверждённый запуск с передачей framebuffer, а не заявление о полностью стабильной графике.

Vulkan проверяли от треугольника до оболочки

Для графики использовали последовательные проверки: доступ к GPU, простой треугольник, работа с изображениями и буферами, Qt Quick, затем полноценная оболочка.

На телефоне GPU доступен через KGSL. Доступ к /dev/kgsl-3d0 настроен через группу render, чтобы графическая сессия могла работать без запуска от root.

В журнале полноценной сессии уже виден аппаратный backend Qt:

Creating QRhi with backend VulkanInitializing QRhi Vulkan backendPhysical device 0: 'Adreno 7c+ Gen 3'

Последняя строка — имя, которое сообщает драйвер.

Отдельной задачей стал DMA-BUF и передача изображения между компонентами графического стека. Здесь легко слишком рано объявить zero-copy: аппаратный рендеринг Qt сам по себе не доказывает отсутствие копирования при выводе на экран.

В проекте были успешные аппаратные прогоны пути с DMA-BUF. При диагностике нестабильных запусков использовался и резервный вариант с Cage/pixman и передачей через shared memory, при сохранении Vulkan-рендеринга Qt. Эти конфигурации нужно различать в результатах тестирования.

Для следующего этапа важны уже не только кадры в секунду. Нужно проверять открытие сторонних окон, смену поверхностей, клавиатуру, блокировку экрана и повторный запуск сессии.

Телефония начинается задолго до кнопки вызова

Для работы с модемом недостаточно загрузить firmware и получить состояние ONLINE. В этой системе участвуют QRTR, QMI, службы доступа к данным модема, service registry и доставка файлов конфигурации.

На M52 пришлось отдельно адаптировать поведение pd-mapper и tqftpserv под Samsung-ядро.

Одна проблема была в обнаружении firmware: привычный путь через /sys/class/remoteproc не давал ожидаемого результата. Добавили запасной поиск через firmware_class.path и /lib/firmware.

Другая оказалась тоньше. Name service этого ядра отбрасывал объявления QRTR-сервисов с нулевыми node и port. Пользовательская библиотека рассчитывала на поведение другого варианта ядра, где адрес отправителя подставляется автоматически. После явного заполнения адреса службы стали появляться в QRTR.

Такие несовпадения особенно неприятны: бинарник запускается, процесс жив, очевидной ошибки нет, а другой процесс не может найти его сервис.

Для управления связью сейчас используется platinum-cellular: состояние снимается через QMI, операции звонков и USSD выполняются через libqmi-glib. Оболочка получает опубликованное состояние и отправляет ограниченный набор запросов.

Например, ветка исходящего звонка выглядит так:

inp = Qmi.MessageVoiceDialCallInput.new()inp.set_calling_number(number)self.call.update(    state='dialing',    number=number,    direction='mo',    error='')def done(out, error):    if error:        self.call.update(state='ended', error=error)    else:        ok, call_id = out.get_call_id()        self.call['id'] = int(call_id) if ok else 0self.voice_call('dial_call', inp, 30, done)

Ответ на команду набора ещё не описывает всю жизнь звонка. Дальнейшее состояние обновляется по уведомлениям all-call-status. Для завершения и ответа используются отдельные операции с идентификатором вызова.

USSD тоже имеет состояние: запрос может завершиться ответом либо продолжиться диалогом. Поэтому предусмотрены операции начала, ответа и отмены USSD-сессии.

Звонки, SMS и USSD уже прошли мою проверку на телефоне. Это важный переход: за экраном набора теперь стоит работа с настоящим модемом.

При этом мобильный интернет остаётся отдельной задачей. Ранее при попытке работы LTE наблюдались падения модема и проблемы с IPA/GSI. Работоспособность звонков и сообщений нельзя автоматически переносить на пакетный тракт данных или VoLTE.

Почему для SMS пришлось писать отдельный кодек

QMI WMS позволяет читать и отправлять SMS в виде PDU. Готовая строка текста из этого интерфейса сама не появляется.

Поэтому в проекте появился отдельный platinum_smspdu.py: чистые функции для SMS-DELIVER и SMS-SUBMIT, GSM 7-bit, UCS2, адресов отправителей и составных сообщений.

Один из коротких, но показательных фрагментов — перевод текста в GSM-септеты:

def gsm7_septets(text):    out = []    for char in text:        index = GSM7_BASIC.find(char)        if index >= 0:            out.append(index)        elif char in GSM7_EXT:            out.extend((27, GSM7_EXT[char]))        else:            return None    return out

Символ из дополнительной таблицы занимает два септета. Поэтому len(text) недостаточно для определения размера GSM-сообщения. Заголовок составного сообщения тоже занимает место и влияет на упаковку.

Эту логику удобно проверять без модема: эталонные PDU, кириллица, расширенная таблица, разбиение длинного текста и сборка частей.

Исходящая отправка в службе устроена последовательно: кодек формирует PDU, затем части уходят через raw_send. Ошибка любой части переводит операцию в failed.

Входящие сохраняются в служебном inbox, публикуются для оболочки, а после переноса в переписку подтверждаются отдельным запросом sms-ack. Такое подтверждение относится к передаче сообщения между службой и интерфейсом, его нельзя путать с подтверждением доставки в сотовой сети.

Есть ещё одно различие в текущем API: состояние sent после успешного raw_send означает успешное завершение операции отправки через модем. Подтверждение получения сообщения адресатом требует отдельной обработки отчётов доставки.

Аудио: работающий DSP ещё не означает звуковую карту

Аудиоподсистема пока остаётся одним из незавершённых участков.

Уже удалось продвинуться от отсутствующей аппаратной инициализации до запуска ADSP и появления аудиосервисов. В диагностике были подтверждены состояния:

ADSP ONLINEapr_audio_svc state[Up]audio_pd ... is up on adsp

Но одновременно:

/proc/asound/cards:--- no soundcards ---

Эти строки описывают разные уровни системы и друг другу не противоречат.

Упрощённо зависимости выглядят так:

firmware и карты сервисов          ↓         ADSP          ↓       APR / PDR          ↓ q6core и аудиокомпоненты          ↓     ASoC machine driver          ↓       карта ALSA          ↓ маршруты, PCM, микрофоны, динамики

В систему добавлены штатные карты adspr.jsn, adsps.jsn и adspua.jsn, подключены необходимые компоненты ядра. Следующий блокер находится в цепочке регистрации аудиоустройств.

Одна из проверяемых гипотез — порядок доставки события готовности service locator. Если состояние UP опубликовано до регистрации потребителя и текущая реализация не воспроизводит уже известное состояние новому подписчику, нужная ветка инициализации может не выполниться. Это пока гипотеза по исходникам, а не подтверждённая причина.

Здесь бессмысленно начинать с ползунка громкости. Сначала должна появиться карта ALSA, затем нужно проверить реальные PCM-тракты, маршруты и каждый физический вход и выход.

Проверка телефонии и завершённая поддержка всех динамиков, микрофонов и мультимедийного звука — разные пункты аппаратной приёмки.

Misa: от загруженной VRM к управляемому персонажу

Misa задумана как персонаж внутри рабочего стола. Она должна появляться поверх интерфейса, разговаривать и со временем взаимодействовать с окружением.

Первый вариант довольно быстро показал модель через Qt Quick 3D. Затем обнаружилась разница между «загрузить VRM» и «управлять аватаром»: модель могла отобразиться в T-позе, а удобного доступа к humanoid-костям и выражениям через существующий путь импорта не было.

Сейчас подготовка модели разделена на этапы:

VRM ↓подготовка исходной позы и GLB ↓Balsam → сцена Qt Quick 3D ↓сопоставление VRM humanoid bones и morph targets ↓управление головой, глазами и выражениями

Принципиальный момент — сохранение исходных преобразований скелета и inverse bind matrices. Поворот головы должен добавляться к rest pose, иначе можно получить корректно импортированную, но деформированную модель.

В генерируемую QML-сцену добавлены управляющие свойства:

property real headYaw: 0property real headPitch: 0property real eyeYaw: 0property real eyePitch: 0property real blink: 0property real mouth: 0property string emotion: "neutral"Behavior on headYaw {    NumberAnimation { duration: 180 }}Behavior on eyeYaw {    NumberAnimation { duration: 120 }}

Повороты ограничиваются, затем композиционно добавляются к исходной ориентации кости через кватернионы. Голова и глаза имеют разную скорость сглаживания. Моргание меняет вес настоящего morph target.

Подключение к «мозгам» Misa идёт через WebSocket. Клиент принимает поток текста, состояния, эмоции и команды движения. Проверен настоящий ответ сервера.

Для потокового текста пришлось учитывать семантику финального события: оно завершает ответ, а не обязательно содержит очередной фрагмент для добавления. Иначе одна и та же реплика может появиться дважды.

Сетевой ответ также не становится произвольной командой ОС. Между диалогом, анимацией и действиями над приложениями должны оставаться явные интерфейсы.

Текущий клиент Misa работает с текстовым диалогом. Голосовой ввод, воспроизведение ответа и синхронизация губ с реальным звуком зависят от завершения аудиотракта. Полные жесты тела и слежение за лицом через переднюю камеру тоже ещё впереди.

Что изменилось после первых успешных запусков

Самым полезным результатом оказался способ проверки изменений.

Для SMS можно отдельно тестировать PDU-кодек. Для Misa — преобразования костей и обработку потоковых событий. Для оболочки — переходы между экранами и работу event loop. Но ни один такой тест не заменяет аппаратный прогон.

Последнее зависание при входе в «Сообщения» хорошо показывает эту границу. Локальная проверка переходов между списком, созданием сообщения и перепиской прошла, таймеры продолжали работать. На телефоне зависание произошло. Теперь нужен журнал конкретного сбоя, чтобы разделить проблему QML, сохранённых данных, графического драйвера и ядра.

Ровно так же появление ADSP ONLINE не заканчивает работу со звуком, а успешное создание Vulkan-устройства не заканчивает работу с дисплеем.

Platinum уже дошла до настоящих SMS, звонков и USSD, аппаратной графики и собственного ассистента. Следующая инженерная задача — добиться повторяемости: чтобы устройство одинаково надёжно загружалось, открывало приложения, принимало вызовы и восстанавливалось после ошибок. Именно на этом этапе отдельные работающие компоненты начинают складываться в операционную систему смартфона.

Видео: https://youtu.be/jlgnNbNx6Oc?si=HocAexw0akI0YixX

Исходники: https://github.com/digkill/Platinum-OS
TG канал:

ссылка на оригинал статьи https://habr.com/ru/articles/1086416/