RBS RepkaPi 3: различия между версиями
Ivzeivze (обсуждение | вклад) м (испр. забытый кусочек копипасты из примера) |
Ivzeivze (обсуждение | вклад) (→Watchdog: добавил ping) |
||
Строка 279: | Строка 279: | ||
| | | | ||
<source lang="C"> | <source lang="C"> | ||
#include <sys/stat.h> | |||
#include <sys/types.h> | #include <sys/types.h> | ||
#include <sys/ | #include <sys/wait.h> | ||
#include < | #include <errno.h> | ||
#include <fcntl.h> | #include <fcntl.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
#include < | #include <stdlib.h> | ||
#include <time.h> | #include <time.h> | ||
#include <unistd.h> | |||
Строка 297: | Строка 300: | ||
"/proc/cpuinfo" | "/proc/cpuinfo" | ||
}; | }; | ||
// NULL отключает ping-проверку | |||
const char * ping_host = NULL; | |||
// const char * ping_host = "192.168.0.1"; | |||
////////////////////// | |||
// Реализация PING // | |||
////////////////////// | |||
// Если не получилось exec()-нуться | |||
#define RETURN_CODE_NOEXEC 150 | |||
/*noreturn*/ | |||
void exec_ping(const char * host){ | |||
char * host_ = (char*) host; // снимаем запрет на чтение - всё равно сейчас всё перезапустится (причуды API) | |||
char * const argv[] = { | |||
"ping", | |||
host_, | |||
"-c", | |||
"3", // число пакетов | |||
"-i", | |||
"0.01", // время между отправкой пакетов [сек] | |||
"-W", | |||
"0.05", // время ожидания ответа, если ни один пакет не пришел | |||
// (оценка времени, за которое пакет должен точно прийти) | |||
NULL | |||
}; | |||
execvp( | |||
argv[0], | |||
argv | |||
); | |||
// если мы здесь, нас сильно заглючило - мы должны были заместиться | |||
// вызываемой программой | |||
perror("Child process couldn't launch ping, exiting"); | |||
exit(RETURN_CODE_NOEXEC); | |||
} | |||
// Запускает подпроцесс ping, ждёт его завершения и выдаёт информацию | |||
// о его успешности | |||
// @param host - кого пинговать | |||
// | |||
// @return 0 - успех! | |||
// -2 - неуспех fork() | |||
// -3 - непонятная ошибка при вызове wait() | |||
// -4 - дочерняя программа завершилась аварийно | |||
// прочие коды >0 - коды неудачности завершения вызова ping | |||
int launch_ping(const char * host){ | |||
pid_t p = fork(); | |||
if(p < 0) { | |||
// ошибка, мы в процессе-родителе, мы не разделились | |||
perror("Can't fork to launch ping"); | |||
return -2; | |||
} | |||
if(p == 0){ | |||
// код потомка | |||
exec_ping(host); // или запустит ping, или заверщит программу-потомка с кодом ошибки | |||
// в это место код не дойдёт в потомке никогда | |||
exit(RETURN_CODE_NOEXEC); | |||
} | |||
// код родителя | |||
int wstatus; | |||
while(1){ | |||
pid_t wp = wait(&wstatus); | |||
if(wp == -1) { | |||
// что-то не так - посмотрим! | |||
if(errno == EINTR){ | |||
// какой-то левый сигнал прервал ожидание | |||
continue; | |||
}else{ | |||
// непонятная ошибка | |||
perror("Unexpected error in wait()"); | |||
return -3; | |||
} | |||
} | |||
if(wp != p) { | |||
fprintf(stderr, "Wait pid return code %i doesn't match our forked process pid %i. It's bizzare.\n", (int)wp, (int)p); | |||
return -3; | |||
} | |||
break; | |||
} | |||
if(!WIFEXITED(wstatus)){ | |||
return -4; | |||
} | |||
return WEXITSTATUS(wstatus); | |||
} | |||
/////////////////////// | |||
// конец PING секции // | |||
/////////////////////// | |||
Строка 333: | Строка 426: | ||
static int run_tests_report_success(){ | static int run_tests_report_success(){ | ||
int | int rv_success = 1; | ||
if(ping_host != NULL){ | |||
return | int lpcode = launch_ping(ping_host); | ||
if(lpcode != 0){ | |||
rv_success = 0; | |||
fprintf(stderr, "launch_ping() returned %i\n", lpcode); | |||
} | |||
} | |||
{ | |||
int sts_fail = stat_test_files(); | |||
if(sts_fail){ | |||
rv_success = 0; | |||
fprintf(stderr, "stat() failed\n"); | |||
} | |||
} | |||
return rv_success; | |||
} | } | ||
Строка 382: | Строка 488: | ||
} | } | ||
} | } | ||
} | } | ||
return -1; | return -1; | ||
} | } | ||
</source> | </source> | ||
|} | |} |
Версия от 15:39, 23 октября 2023
RBS Repka Pi 3 -- одноплатный компьютер (SBC) с SoC Allwinner H5 на архитектуре AArch64.
Поддерживется портом Sisyphus на AArch64.
Ядра и firmware в Сизифе
Плата поддерживается ядром rbs-def и U-Boot из пакета u-boot-sunxi, начиная с 2023.04-alt2.
Образы Альт
Simply Linux alpha1
- Скачать IMG (img.xz, 1.6G)
- linux rbs-def-5.15.106, rpi-def-5.15.73, rpi-un-6.1.0.
Это даёт возможность загружать систему и на Repka Pi 3 и на Raspberry Pi 3/4/400.
- xorg-server 21.1.8
- chromium 113.0.5672.63
- LibreOffice 7.4.6.2
Возьмите образ в формате img, запишите на SD-карту при помощи alt-rootfs-installer, укажите цель в виде repka_pi3.
Обновите alt-rootfs-installer из Sisyphus, если он не поддерживает цель repka_pi3.
Поддержка аппаратного обеспечения
Протестированы и работают
- Ethernet
- GPIO (использовался UART)
- Звук через mini-jack
- HDMI (звук прерывается паузами)
- GPU Mali-450
- термодатчики CPU и GPU
- WiFi
Для поддержки WiFi в образе Simply Linux alpha1 нужно выполнить от root команды:
cd /lib/firmware/brcm
ln -s ../cypress/cyfmac43430-sdio.bin brcmfmac43430-sdio.xunlong,orangepi-pc2.bin
ln -s brcmfmac43430-sdio.AP6212.txt brcmfmac43430-sdio.txt
ln -s brcmfmac43430-sdio.AP6212.txt brcmfmac43430-sdio.xunlong,orangepi-pc2.txt
Не тестировалось/не поддерживается
- bluetooth
Подключение UART
Вам понадобится usb-to-uart адаптер с напряжением 3.3V. Для UART используются пины из 40-пиновой гребёнки:
- 6 -- ground
- 8 -- UART TX
- 10 -- UART RX.
Общая распиновка
Распиновка разъёма GPIO для Repka pi3 ver 1.3 Allwinner H5.
На изображении указаны пять вариантов конфигурации пинов ввода-вывода. Эти варианты соответствуют записям в утилите настройки в одном из вариантов сборки ОС для репки.
Также прослеживается связь этих наборов с *.dtb файлами, которые можно скачать с сайта разработчиков: для режима 1.2 ГГц нашлось ровно пять вариантов конфгурации:
$ ls sun50i-h5-repka-pi3*1.2ghz.dtb
sun50i-h5-repka-pi3-1.2ghz.dtb
sun50i-h5-repka-pi3-alt-1.2ghz.dtb
sun50i-h5-repka-pi3-alt-i2c2-1.2ghz.dtb
sun50i-h5-repka-pi3-alt-pwm-1.2ghz.dtb
sun50i-h5-repka-pi3-alt-uart-1.2ghz.dtb
В именах фалов нет номеров конфигуарации, но прослеживается предназначение, фокус на одну из подсистем, по чему можно угадать прошивку.
Вообще говоря, конфигурация ног процессора настраивается редактированием *.dtb файла, см. Device Tree.
Установка минимальной системы Sisyphus с нуля
Установим минимальную (jeos) сборку из Сизифа на плату Repka Pi3 (ver 1.3).
Нам потребуются...
- Компьютер (рабочая станция) на ОС Alt Linux p10 x86_64, с которого будут совершаться основные операции.
- Плата Repka Pi3 VER 1.3, частота 1.2 ГГц (у меня пришла с бумажкой об использовании такой частоты процессора)
- USB-клавиатура и HDMI-монитор
- Блок питания
- Флешка SD от 8 до 16 Гб под систему, не из самых дешёвых. Дешёвые флешки быстро выходят из строя от перезаписи.
- Адаптер USB-SD card.
- Образ Sisyphus jeos (Just Enough OS, голая операционка с командной строкой):
wget http://nightly.altlinux.org/sisyphus-aarch64/current/regular-jeos-systemd-latest-aarch64.img.xz
- Аппаратно-специфичные файлы по ссылке https://rbs-computers.ru/repkapi3 "Скачать DTS и DTSI". У меня, когда я писал статью (июнь 2023), ссылка указывала на https://download.robointellect.ru/Uboot-19.06.23.zip
Установка системы
Загрузка образа на SD-карту
SD-карта вставляется в карточиталку и втыкается в рабочую станцию.
Если не установлена, необходимо установить утилиту alt-rootfs-installer:
apt-get install alt-rootfs-installer
Версия, доступная в репозитории p10 в конце июня 2023 года, уже поддерживает цель repka_pi3!
Выясняем, как называется флешка, например, командой fdisk -l.
Загружаем образ ОС на флешку /dev/sdXXX:
alt-rootfs-installer --resize --target=repka_pi3 --image-in=regular-jeos-systemd-20230621-aarch64.img.xz --media=/dev/sdXXX
Что делает эта команда? Загружает бинарный образ img, собранный системой сборки дистрибутивов, на флешку. Этот образ содержит в себе таблицу разделов MBR и корневой раздел ext4 целевой системы, ужатый до нескольких гигабайт. После записи запускается алгоритм расширения раздела на всю флешку. После того программа берёт бинарный загрузчик u-boot-sunxi из файловой системы флешки (/usr/share/u-boot/repka_pi3/u-boot-sunxi-with-spl.bin
) и записывает по отступу 8 килобайт в адресном пространстве флешки. Специально так сделано, что там несколько мегабайт неразмеченного пространства. Сам загрузчик занимает меньше мегабайта. При запуске процессор Allwinner H5 как раз ищет исполнимый код по отступу 8K.
Загрузка в режиме текстовой консоли
Операционная система загружена на флешку. Теперь следует вставить SD-карту в нашу репку, подключить к ней USB-клавиатуру и HDMI-монитор и включить питание. Пользовательский интерфейс - текстовый терминал. Команды вводятся с клавиатуры, данные отображаются на мониторе в виде текста.
На экране должен появиться интерфейс загрузчика U-boot. Желтая подводная лодка справа сверху экрана символизирует! Загрузчик U-boot богат на функционал: он способен парсить файловые системы и загружаться по сети. В нашем случае загрузчик находит системный раздел на флешке, читает ФС Ext4, находит в ней файл /boot/extlinux/extlinux.conf
и загружает указанное в нём ядро.
Ванильные ядра, поставляемые в образе, достаточны, чтобы запустить систему! Репка должна загрузиться и пригласить пользователя к входу. Следует войти в систему, используя:
login: root
password: altlinux
Замена ядра на rbs-def
Давайте однако же установим ядро rbs-def, предназначенное для нашей репки.
apt-get update
apt-get dist-upgrade
update-kernel -t rbs-def
Через несколько минут ядро поставится, и будет собран правильный initramfs в условиях целевой системы.
Альтернативный путь установки ядра rbs-def
Альтернативно можно было бы запускаться при помощи виртуальной машины по статье Regular/arm#Запуск_в_QEMU. Однако перед заменой ядра на rbs-def следует заменить /etc/initrd.mk на /etc/initrd.mk.oem, что меняет алгоритм обнаружения необходимых модулей ядра при сборке initramfs. Если этого не сделать ([1] Bug 46696]), набор модулей ядра будет соответствовать виртуальной машине, и на репке это не запустится. При замене на .oem добавляется большая куча различных модулей на все случаи жизни, среди которых оказываются нам нужные. После перенесения образа на флешку и запуска системы на железе репки следует вернуть оригинальный initrd.mk и пересобрать initramfs командой make-initrd уже в правильном окружении.
Перезагрузка
- Или перезагружаемся с ядром rbs-def.
- Или при следовании альтернативному пути, устанавливаем образ, побывавший виртуалке, на флешку утилитой alt-rootfs-installer. Запускаем репку.
Всё почти завершено!
Устанавливаем нужную версию .dtb
Последний штрих - заменить фалйы Device Tree из поставки rbs-def на предоставляемые разработчиками репки под различные конфигурации устройства.
В архиве Uboot-<дата>.zip
найти файл sun50i-h5-repka-pi3-1.2ghz.dtb
или любой другой с нужной частотой и желаемой конфигурацией пинов ввода-вывода. Файлы dtb можно править вручную (аккуратно и вдумчиво!), см. Device Tree. Указанные файл, в частности, содержит зависимость напряжения ядра процессора от желаемой тактовой частоты и критически влияет на устойчивость системы. Таблица, поставляемая вместе с ядром rbs-def, отличается от таблицы из .zip архива. По крайней мере, какое-то время на ней система работает стабильно.
Теперь следует исправить вручную файл extlinux.conf. Поскольку файл перегенерируется автоматически при обновлении ядра, это нужно будет делать каждый раз, если ядро обновится. Всё же, речь идёт о встраиваемой системе. Изящное решение а Альт Линукс по изменению make-initrd только предстоит сделать.
Выключить репку, подключить флешку к рабочей станции - или загрузить в работающую систему по сети при помощи утилиты netcat, "петли и палки".
Загрузить sun50i-h5-repka-pi3-1.2ghz.dtb
в папку /boot/
репки
cd /boot/extlinux
cp extlinux.conf extlinux.conf.bak
vim extlinux.conf
...
label linux
Удалить запись fdtdir
Добавить запись:
fdt ../sun50i-h5-repka-pi3-1.2ghz.dtb
Эта запись означает "загрузить указанный бинарник Device Tree напрямую"
Формат файла описан в исходниках загрузчика: Uboot-19.06.23.zip -> /Uboot/Uboot-repka/u-boot-v2022.04.zip -> /u-boot-v2022.04/doc/README.pxe
. Вообще говоря, PXE - это протокол загрузки по сети, но, видимо, просто используется уже готовый синтаксис этого стандарта. По крайней мере, при анализе исходного кода u-boot ключевые лексемы из файла extlinux.conf были обнаружены только в pxe-подсистеме.
Теоретически сюда же можно добавлять патчи для Device Tree, называемые оверлеями, через команду:
fdtoverlays <path> [...]
Это не проверялось.
Теперь посмотрев на монитор при загрузке, можно убедиться: загрузчик U-boot забирает новый файл dtb по указанному адресу! Также можно проверить, какое дерево устройств загружено, проанализировав значения /sys/firmware/devicetree/
.
Часы реального времени
У Allwinner H5 есть часы реального времени в составе микросхемы. Однако на плате Repka Pi3 v1.3 ноги, к которым подключается литиевый элемент (например CR2032), не разведены, что типично для одноплатных компьютеров, как например Raspberry и Orange. По заверениям разработчиков, эта фича будет добавлена в следующих версиях. В результате время теряется при сбросе питания.
Стандартное решение - использовать внешний I2C модуль часов, в моём случае это был модуль на микросхеме DS3231. Чтобы подключить модуль я использовал путь редактирования и пересборки файла описания дерева устройств.
В соответствии статьёй Device Tree, следует совершить следующие действия:
- Взять исходную конфигурацию $OLDNAME.dtb, распаковать в $NEWNAME.dts:
cd /boot
dtc $OLDNAME.dtb -o $NEWNAME.dts
- Отредактировать $NEWNAME.dts, вставить секцию
rtc@68
в нужный I2C порт:
i2c@1c2ac00 {
compatible = "allwinner,sun6i-a31-i2c";
reg = <0x1c2ac00 0x400>;
interrupts = <0x00 0x06 0x04>;
clocks = <0x03 0x3b>;
resets = <0x03 0x2e>;
pinctrl-names = "default";
pinctrl-0 = <0x22>;
status = "okay";
#address-cells = <0x01>;
#size-cells = <0x00>;
phandle = <0x6c>;
rtc@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
};
};
- Скомпилировать новое дерево устройств:
dtc $NEWNAME.dts $NEWNAME.dtb
- Исправить в
/boot/extlinux/extlinux.conf
строчку (см. пункт выше):
fdt ../$NEWNAME.dtb
В результате обновлённая модель набора устройств даст информацию ядру о том, что к порту I2C подключены часы. Нужный драйвер ядра будет подгружен автоматически!
Также для работы часов не нужно присутствие драйвера, поддерживающего файлы устройств /dev/i2c{i}. Драйвер часов общается по I2C напрямую. Т.е. утилиты вроде i2cdetect -y 1
могут и не работать, а часы всё равно подключатся.
Для управления часами следует использовать утилиту hwclock
:
# чтение времени и вывод на экран
hwclock -rf /dev/rtc1
# запись системных часов во внешний RTC модуль
hwclock -wf /dev/rtc1
# обновление системного времени из RTC модуля
hwclock -sf /dev/rtc1
Системные часы /dev/rtc0
всё ещё остаются основными, переключить ядро на /dev/rtc1
у меня не получилось.
Для исправления времени при загрузке можно использовать скрипт rc.local:
$ cat /etc/rc.d/rc.local
#!/bin/sh
hwclock -sf /dev/rtc1
Если, кроме того, сеть окажется доступной, сработает ntp подсистема, и время будет исправлено на более точное.
"Подводить" часы при таком подходе следует вручную.
Watchdog
Процессор Allwinner имеет аппаратную watchdog-подсистему, что достаточно типично. Watchdog, "сторожевая собака" - аппаратная часть процессора, которая устраивает сброс системы, если считает, что система зависла.
При включении питания watchdog отключён. После того, как какой-то процесс открывает файл /dev/watchdog, драйвер ядра активирует watchdog-подсистему, и теперь, если не сбрасывать её, репка перезагрузится через какое-то фиксированное время около 10 секунд (теоретически настраивается). Сброс осуществляется записью хотя бы одного байта в файл /dev/watchdog.
Ниже пердоставлена минимальная программа, которая в бесконечном цикле:
- вызывает stat() для нескольких файлов системы
- сбрасывает /dev/watchdog и мигает светодиодом репки (1 раз на 8 проверок)
- если проверка неуспешна, диод не мигает, и через некоторое время система уходит в сброс
Исходный код программы adhoc_watchdog.c |
---|
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define LED_CTL_FILE "/sys/class/leds/rbs:blue:status/brightness"
const char * stat_targets[] = {
"/etc/hostname",
"/tmp",
"/proc/uptime",
"/proc/cpuinfo"
};
// NULL отключает ping-проверку
const char * ping_host = NULL;
// const char * ping_host = "192.168.0.1";
//////////////////////
// Реализация PING //
//////////////////////
// Если не получилось exec()-нуться
#define RETURN_CODE_NOEXEC 150
/*noreturn*/
void exec_ping(const char * host){
char * host_ = (char*) host; // снимаем запрет на чтение - всё равно сейчас всё перезапустится (причуды API)
char * const argv[] = {
"ping",
host_,
"-c",
"3", // число пакетов
"-i",
"0.01", // время между отправкой пакетов [сек]
"-W",
"0.05", // время ожидания ответа, если ни один пакет не пришел
// (оценка времени, за которое пакет должен точно прийти)
NULL
};
execvp(
argv[0],
argv
);
// если мы здесь, нас сильно заглючило - мы должны были заместиться
// вызываемой программой
perror("Child process couldn't launch ping, exiting");
exit(RETURN_CODE_NOEXEC);
}
// Запускает подпроцесс ping, ждёт его завершения и выдаёт информацию
// о его успешности
// @param host - кого пинговать
//
// @return 0 - успех!
// -2 - неуспех fork()
// -3 - непонятная ошибка при вызове wait()
// -4 - дочерняя программа завершилась аварийно
// прочие коды >0 - коды неудачности завершения вызова ping
int launch_ping(const char * host){
pid_t p = fork();
if(p < 0) {
// ошибка, мы в процессе-родителе, мы не разделились
perror("Can't fork to launch ping");
return -2;
}
if(p == 0){
// код потомка
exec_ping(host); // или запустит ping, или заверщит программу-потомка с кодом ошибки
// в это место код не дойдёт в потомке никогда
exit(RETURN_CODE_NOEXEC);
}
// код родителя
int wstatus;
while(1){
pid_t wp = wait(&wstatus);
if(wp == -1) {
// что-то не так - посмотрим!
if(errno == EINTR){
// какой-то левый сигнал прервал ожидание
continue;
}else{
// непонятная ошибка
perror("Unexpected error in wait()");
return -3;
}
}
if(wp != p) {
fprintf(stderr, "Wait pid return code %i doesn't match our forked process pid %i. It's bizzare.\n", (int)wp, (int)p);
return -3;
}
break;
}
if(!WIFEXITED(wstatus)){
return -4;
}
return WEXITSTATUS(wstatus);
}
///////////////////////
// конец PING секции //
///////////////////////
static int stat_test_files(){
struct stat st;
int fail = 0;
for(int i = 0; i < sizeof(stat_targets)/sizeof(stat_targets[0]); i += 1){
int strv = stat(stat_targets[i], &st);
if(strv < 0){
fail = 1;
}
}
return fail;
}
static int open_led_spec_file(){
int fd = open(LED_CTL_FILE, O_WRONLY);
if(fd < 0){
perror("Can't open LED file");
}
return fd;
}
static int open_watchdog_spec_file(){
int fd = open("/dev/watchdog", O_WRONLY);
if(fd < 0){
perror("Can't open watchdog file");
}
return fd;
}
static int run_tests_report_success(){
int rv_success = 1;
if(ping_host != NULL){
int lpcode = launch_ping(ping_host);
if(lpcode != 0){
rv_success = 0;
fprintf(stderr, "launch_ping() returned %i\n", lpcode);
}
}
{
int sts_fail = stat_test_files();
if(sts_fail){
rv_success = 0;
fprintf(stderr, "stat() failed\n");
}
}
return rv_success;
}
static void sleep_delay_or_less(){
struct timespec dt = {
.tv_sec = 0,
.tv_nsec = 500000000
};
struct timespec rem;
nanosleep(&dt, &rem);
}
static void reset_watchdog(int fd){
write(fd, "A", 1);
}
static void set_led(int fd, int on){
char buf[1];
if(on){
buf[0] = '1';
}else{
buf[0] = '0';
}
write(fd, buf, 1);
}
int main(){
int led = open_led_spec_file();
if(led < 0){
return 42;
}
int wfd = open_watchdog_spec_file();
if(wfd < 0){
return 43;
}
int ccnt = 0;
{
while(1){
ccnt += 1;
sleep_delay_or_less();
int success = run_tests_report_success();
if(success){
reset_watchdog(wfd);
set_led(led,ccnt & 4);
}
}
}
return -1;
}
|
Данную программу следует собрать командой
gcc adhoc_watchdog.c -o /opt/adhoc_watchdog
прямо в системе.
После этого следует добавить строку (или создать новый) /etc/rc.d/rc.local:
#!/bin/sh
nice -n -15 setsid -f /opt/adhoc_watchdog
Установка ПО
Настройка ssh-сервера
vim /etc/openssh/sshd_config
Раскомментировать и исправить:
PasswordAuthentication no
AuthorizedKeysFile - настроить список
apt-get install netcat
на рабочем месте
netcat -l 12345 < .ssh/id_rsa<что-то-там>.pub
в системе репки
cd /etc/openssh/authorized_keys/
netcat <рабочее место>.local 12345 > root
chmod 640 root
systemctl restart sshd
Ура теперь можно подключаться по SSH с хост-машины. Мониор и клавиатуру можно отключать!
Пакетный менеджер
apt-get install aptitude
Имя системы
hostnamectl hostname repka-pi3
Служба автообнаружения
apt-get install avahi libnss-mdns
systemctl enable --now avahi-dnsconfd.service
На хост-машине p10:
resolve repka-pi3.local
Выдаёт IP-шник репки!
В репке:
resolve <хост-машина>.local
Выдаёт IP-шник рабочей станции.
Итог
Получилась машина с доступом по SSH и подключаемой текстовой консолью. Можно поставить графическую оболочку, но это отдельная история. Можно поставить разнообразное серевеное ПО. Образ системы рекомендуется забекапить!