RBS RepkaPi 3: различия между версиями
Ivzeivze (обсуждение | вклад) (Установка минимальной системы Sisyphus с нуля) |
Pauli (обсуждение | вклад) |
||
(не показано 15 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
[[Файл:Description.webp|мини|Repka Pi3 v1.3 H5]] | |||
[https://rbs-computers.ru RBS] [https://repka-pi.ru Repka Pi 3] -- одноплатный компьютер (SBC) с SoC Allwinner H5 на архитектуре AArch64. | [https://rbs-computers.ru RBS] [https://repka-pi.ru Repka Pi 3] -- одноплатный компьютер (SBC) с SoC Allwinner H5 на архитектуре AArch64. | ||
Строка 64: | Строка 63: | ||
* 8 -- UART TX | * 8 -- UART TX | ||
* 10 -- UART RX. | * 10 -- UART RX. | ||
=== Общая распиновка === | |||
Распиновка разъёма GPIO для Repka pi3 ver 1.3 Allwinner H5. | |||
[[Изображение:RBS Repka pi3 - new-periphery-heavy.webp|mini|700px]] | |||
На изображении указаны пять вариантов конфигурации пинов ввода-вывода. Эти варианты соответствуют записям в утилите настройки в одном из вариантов сборки ОС для репки. | |||
Также прослеживается связь этих наборов с *.dtb файлами, которые можно скачать с сайта разработчиков: для режима 1.2 ГГц нашлось ровно пять вариантов конфгурации: | |||
<source lang="bash"> | |||
$ 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 | |||
</source> | |||
В именах файлов нет номеров конфигурации, но прослеживается предназначение, фокус на одну из подсистем, по чему можно угадать прошивку. | |||
Вообще говоря, конфигурация ног процессора настраивается редактированием *.dtb файла, см. [[Device Tree]]. | |||
== Установка минимальной системы Sisyphus с нуля == | == Установка минимальной системы Sisyphus с нуля == | ||
Строка 130: | Строка 149: | ||
==== Альтернативный путь установки ядра rbs-def ==== | ==== Альтернативный путь установки ядра rbs-def ==== | ||
Альтернативно можно было бы запускаться при помощи виртуальной машины по статье [[arm#%D0%97%D0%B0%D0%BF%D1%83%D1%81%D0%BA_%D0%B2_QEMU]]. Однако перед заменой ядра на rbs-def следует заменить /etc/initrd.mk на /etc/initrd.mk.oem, что меняет алгоритм обнаружения необходимых модулей ядра при сборке initramfs. Если этого не сделать ([https://bugzilla.altlinux.org/46696] Bug 46696]), набор модулей ядра будет соответствовать виртуальной машине, и на репке это не запустится. При замене на .oem добавляется большая куча различных модулей на все случаи жизни, среди которых оказываются нам нужные. После перенесения образа на флешку и запуска системы на железе репки следует вернуть оригинальный initrd.mk и пересобрать initramfs командой make-initrd уже в правильном окружении. | Альтернативно можно было бы запускаться при помощи виртуальной машины по статье [[Regular/arm#%D0%97%D0%B0%D0%BF%D1%83%D1%81%D0%BA_%D0%B2_QEMU]]. Однако перед заменой ядра на rbs-def следует заменить /etc/initrd.mk на /etc/initrd.mk.oem, что меняет алгоритм обнаружения необходимых модулей ядра при сборке initramfs. Если этого не сделать ([https://bugzilla.altlinux.org/46696] Bug 46696]), набор модулей ядра будет соответствовать виртуальной машине, и на репке это не запустится. При замене на .oem добавляется большая куча различных модулей на все случаи жизни, среди которых оказываются нам нужные. После перенесения образа на флешку и запуска системы на железе репки следует вернуть оригинальный initrd.mk и пересобрать initramfs командой make-initrd уже в правильном окружении. | ||
==== Перезагрузка ==== | ==== Перезагрузка ==== | ||
Строка 140: | Строка 159: | ||
==== Устанавливаем нужную версию .dtb ==== | ==== Устанавливаем нужную версию .dtb ==== | ||
Последний штрих - заменить | Последний штрих - заменить файлы Device Tree из поставки rbs-def на предоставляемые разработчиками репки под различные конфигурации устройства. | ||
В архиве <code>Uboot-<дата>.zip</code> найти файл <code>sun50i-h5-repka-pi3-1.2ghz.dtb</code> или любой другой с нужной частотой и желаемой конфигурацией пинов ввода-вывода. Файлы dtb можно править вручную (аккуратно и вдумчиво!), см. [[Device Tree]]. Указанные файл, в частности, содержит зависимость напряжения ядра процессора от желаемой тактовой частоты и критически влияет на устойчивость системы. Таблица, поставляемая вместе с ядром rbs-def, отличается от таблицы из .zip архива. По крайней мере, какое-то время на ней система работает стабильно. | В архиве <code>Uboot-<дата>.zip</code> найти файл <code>sun50i-h5-repka-pi3-1.2ghz.dtb</code> или любой другой с нужной частотой и желаемой конфигурацией пинов ввода-вывода. Файлы dtb можно править вручную (аккуратно и вдумчиво!), см. [[Device Tree]]. Указанные файл, в частности, содержит зависимость напряжения ядра процессора от желаемой тактовой частоты и критически влияет на устойчивость системы. Таблица, поставляемая вместе с ядром rbs-def, отличается от таблицы из .zip архива. По крайней мере, какое-то время на ней система работает стабильно. | ||
Строка 177: | Строка 196: | ||
Теперь посмотрев на монитор при загрузке, можно убедиться: загрузчик U-boot забирает новый файл dtb по указанному адресу! Также можно проверить, какое дерево устройств загружено, проанализировав значения <code>/sys/firmware/devicetree/</code>. | Теперь посмотрев на монитор при загрузке, можно убедиться: загрузчик U-boot забирает новый файл dtb по указанному адресу! Также можно проверить, какое дерево устройств загружено, проанализировав значения <code>/sys/firmware/devicetree/</code>. | ||
=== Часы реального времени === | |||
У Allwinner H5 есть часы реального времени в составе микросхемы. Однако на плате Repka Pi3 v1.3 ноги, к которым подключается литиевый элемент (например CR2032), не разведены, что типично для одноплатных компьютеров, как например Raspberry и Orange. По заверениям разработчиков, эта фича будет добавлена в следующих версиях. В результате время теряется при сбросе питания. | |||
Стандартное решение - использовать внешний I2C модуль часов, в моём случае это был модуль на микросхеме DS3231. | |||
Чтобы подключить модуль я использовал путь редактирования и пересборки файла описания дерева устройств. | |||
В соответствии статьёй [[Device Tree]], следует совершить следующие действия: | |||
* Взять исходную конфигурацию $OLDNAME.dtb, распаковать в $NEWNAME.dts: | |||
<source lang="bash"> | |||
cd /boot | |||
dtc $OLDNAME.dtb -o $NEWNAME.dts | |||
</source> | |||
* Отредактировать $NEWNAME.dts, вставить секцию <code>rtc@68</code> в нужный I2C порт: | |||
<source lang="devicetree"> | |||
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>; | |||
}; | |||
}; | |||
</source> | |||
* Скомпилировать новое дерево устройств: | |||
<source lang="bash"> | |||
dtc $NEWNAME.dts $NEWNAME.dtb | |||
</source> | |||
* Исправить в <code>/boot/extlinux/extlinux.conf</code> строчку (см. пункт выше): | |||
<source> | |||
fdt ../$NEWNAME.dtb | |||
</source> | |||
В результате обновлённая модель набора устройств даст информацию ядру о том, что к порту I2C подключены часы. Нужный драйвер ядра будет подгружен автоматически! | |||
Также для работы часов не нужно присутствие драйвера, поддерживающего файлы устройств /dev/i2c{i}. Драйвер часов общается по I2C напрямую. Т.е. утилиты вроде <code>i2cdetect -y 1</code> могут и не работать, а часы всё равно подключатся. | |||
Для управления часами следует использовать утилиту <code>hwclock</code>: | |||
<source lang="bash"> | |||
# чтение времени и вывод на экран | |||
hwclock -rf /dev/rtc1 | |||
# запись системных часов во внешний RTC модуль | |||
hwclock -wf /dev/rtc1 | |||
# обновление системного времени из RTC модуля | |||
hwclock -sf /dev/rtc1 | |||
</source> | |||
Системные часы <code>/dev/rtc0</code> всё ещё остаются основными, переключить ядро на <code>/dev/rtc1</code> у меня не получилось. | |||
Для исправления времени при загрузке можно использовать скрипт rc.local: | |||
<source lang="bash"> | |||
$ cat /etc/rc.d/rc.local | |||
#!/bin/sh | |||
hwclock -sf /dev/rtc1 | |||
</source> | |||
Если, кроме того, сеть окажется доступной, сработает ntp подсистема, и время будет исправлено на более точное. | |||
"Подводить" часы при таком подходе следует вручную. | |||
=== Watchdog === | |||
Процессор Allwinner имеет аппаратную watchdog-подсистему, что достаточно типично. Watchdog, "сторожевая собака" - аппаратная часть процессора, которая устраивает сброс системы, если считает, что система зависла. | |||
При включении питания watchdog отключён. После того, как какой-то процесс открывает файл /dev/watchdog, драйвер ядра активирует watchdog-подсистему, и теперь, если не сбрасывать её, репка перезагрузится через какое-то фиксированное время около 10 секунд (теоретически настраивается). Сброс осуществляется записью хотя бы одного байта в файл /dev/watchdog. | |||
Ниже пердоставлена минимальная программа, которая в бесконечном цикле: | |||
* вызывает stat() для нескольких файлов системы | |||
* сбрасывает /dev/watchdog и мигает светодиодом репки (1 раз на 8 проверок) | |||
* если проверка неуспешна, диод не мигает, и через некоторое время система уходит в сброс | |||
{|class="mw-collapsible mw-collapsed" | |||
!Исходный код программы adhoc_watchdog.c | |||
|- | |||
| | |||
<source lang="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"; | |||
// Не мусорить в stdout/stderr ping сообщениями | |||
#define SUPPRESS_PING_OUTPUT 1 | |||
////////////////////// | |||
// Реализация PING // | |||
////////////////////// | |||
// Если не получилось exec()-нуться | |||
#define RETURN_CODE_NOEXEC 150 | |||
#define RETURN_CODE_CANT_PREPARE_CLOSE 151 | |||
#define RETURN_CODE_CANT_PREPARE_OPEN 152 | |||
#define RETURN_CODE_CANT_PREPARE_STRANGE 153 | |||
static int reopen_0_1_2_as_dev_null(){ | |||
// Закрываем дескрипторы, открываем /dev/null | |||
for(int fd = 0; fd < 3; fd += 1){ | |||
while(1){ | |||
int rv = close(fd); | |||
if(rv != 0){ | |||
if(errno == EINTR){ | |||
continue; // левое прерывание | |||
}else if(errno == EBADF){ | |||
break; // уже закрыт почему-то | |||
}else{ | |||
// странная ошибка | |||
perror("Error while closing file descriptors 0,1,2 for ping"); | |||
return RETURN_CODE_CANT_PREPARE_CLOSE; | |||
} | |||
} | |||
break; | |||
} | |||
} | |||
for(int fd = 0; fd < 3; fd += 1){ | |||
while(1){ | |||
int flags; | |||
const char * path = "/dev/null"; | |||
if(fd == 0){ | |||
flags = O_RDONLY; | |||
}else{ | |||
flags = O_RDWR; | |||
} | |||
int rv = open(path, flags); | |||
if(rv == -1) { | |||
if(errno == EINTR){ | |||
continue; | |||
}else{ | |||
perror("Error while opening /dev/null"); | |||
return RETURN_CODE_CANT_PREPARE_OPEN; | |||
} | |||
} | |||
if(rv != fd){ // такого быть не должно | |||
return RETURN_CODE_CANT_PREPARE_STRANGE; | |||
} | |||
break; | |||
} | |||
} | |||
return 0; | |||
} | |||
/*noreturn*/ | |||
static void exec_ping(const char * host){ | |||
if(SUPPRESS_PING_OUTPUT){ | |||
int rv = reopen_0_1_2_as_dev_null(); | |||
if(rv){ | |||
exit(rv); | |||
} | |||
} | |||
char * host_ = (char*) host; // снимаем запрет на чтение - всё равно сейчас всё перезапустится (причуды API) | |||
char * const argv[] = { | |||
"ping", | |||
host_, | |||
"-c", | |||
"3", // число пакетов | |||
"-i", | |||
"0.01", // время между отправкой пакетов [сек] | |||
"-W", | |||
"0.05", // время ожидания ответа, если ни один пакет не пришел | |||
// (оценка времени, за которое пакет должен точно прийти) | |||
"-n", // численный вывод адресов | |||
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 | |||
static 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; | |||
} | |||
</source> | |||
|} | |||
Данную программу следует собрать командой <source lang=bash>gcc adhoc_watchdog.c -o /opt/adhoc_watchdog</source> прямо в системе. | |||
После этого следует добавить строку (или создать новый) /etc/rc.d/rc.local: | |||
<source lang=bash> | |||
#!/bin/sh | |||
nice -n -15 setsid -f /opt/adhoc_watchdog | |||
</source> | |||
=== Установка ПО === | === Установка ПО === | ||
Строка 239: | Строка 623: | ||
=== Итог === | === Итог === | ||
Получилась машина с доступом по SSH и подключаемой текстовой консолью. Можно поставить графическую оболочку, но это отдельная история. Можно поставить разнообразное | Получилась машина с доступом по SSH и подключаемой текстовой консолью. Можно поставить графическую оболочку, но это отдельная история. Можно поставить разнообразное серверное ПО. | ||
Образ системы рекомендуется забекапить! | Образ системы рекомендуется забекапить! | ||
== Ссылки == | |||
[[Файл:Allwinner H5 Manual v1.0.pdf]] | |||
[[RBS_RepkaPi_3/1C|Клиент 1С:Предприятия на RepkaPi 3]] | |||
{{Category navigation|title=ARM|category=ARM|sortkey=*}} | {{Category navigation|title=ARM|category=ARM|sortkey=*}} |
Текущая версия от 11:42, 12 мая 2024
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";
// Не мусорить в stdout/stderr ping сообщениями
#define SUPPRESS_PING_OUTPUT 1
//////////////////////
// Реализация PING //
//////////////////////
// Если не получилось exec()-нуться
#define RETURN_CODE_NOEXEC 150
#define RETURN_CODE_CANT_PREPARE_CLOSE 151
#define RETURN_CODE_CANT_PREPARE_OPEN 152
#define RETURN_CODE_CANT_PREPARE_STRANGE 153
static int reopen_0_1_2_as_dev_null(){
// Закрываем дескрипторы, открываем /dev/null
for(int fd = 0; fd < 3; fd += 1){
while(1){
int rv = close(fd);
if(rv != 0){
if(errno == EINTR){
continue; // левое прерывание
}else if(errno == EBADF){
break; // уже закрыт почему-то
}else{
// странная ошибка
perror("Error while closing file descriptors 0,1,2 for ping");
return RETURN_CODE_CANT_PREPARE_CLOSE;
}
}
break;
}
}
for(int fd = 0; fd < 3; fd += 1){
while(1){
int flags;
const char * path = "/dev/null";
if(fd == 0){
flags = O_RDONLY;
}else{
flags = O_RDWR;
}
int rv = open(path, flags);
if(rv == -1) {
if(errno == EINTR){
continue;
}else{
perror("Error while opening /dev/null");
return RETURN_CODE_CANT_PREPARE_OPEN;
}
}
if(rv != fd){ // такого быть не должно
return RETURN_CODE_CANT_PREPARE_STRANGE;
}
break;
}
}
return 0;
}
/*noreturn*/
static void exec_ping(const char * host){
if(SUPPRESS_PING_OUTPUT){
int rv = reopen_0_1_2_as_dev_null();
if(rv){
exit(rv);
}
}
char * host_ = (char*) host; // снимаем запрет на чтение - всё равно сейчас всё перезапустится (причуды API)
char * const argv[] = {
"ping",
host_,
"-c",
"3", // число пакетов
"-i",
"0.01", // время между отправкой пакетов [сек]
"-W",
"0.05", // время ожидания ответа, если ни один пакет не пришел
// (оценка времени, за которое пакет должен точно прийти)
"-n", // численный вывод адресов
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
static 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 и подключаемой текстовой консолью. Можно поставить графическую оболочку, но это отдельная история. Можно поставить разнообразное серверное ПО. Образ системы рекомендуется забекапить!