RBS RepkaPi 3

Материал из ALT Linux Wiki
Repka Pi3 v1.3 H5

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


  • 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.

mini

На изображении указаны пять вариантов конфигурации пинов ввода-вывода. Эти варианты соответствуют записям в утилите настройки в одном из вариантов сборки ОС для репки.

Также прослеживается связь этих наборов с *.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

Установка системы

Загрузка образа на 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 и подключаемой текстовой консолью. Можно поставить графическую оболочку, но это отдельная история. Можно поставить разнообразное серверное ПО. Образ системы рекомендуется забекапить!

Ссылки

Файл:Allwinner H5 Manual v1.0.pdf

Клиент 1С:Предприятия на RepkaPi 3