TFTP i NFS w rozwoju Embedded Linux
Wykorzystaj TFTP i NFS, by przyspieszyć uruchamianie Embedded Linux...
W tym artykule skonfigurujemy serwer TFTP (Trivial File Transfer Protocol) i NFS (Network File System), a następnie użyjemy go do uruchomienia Raspberry Pi 4 (RPI). Żeby ułatwić sobie życie, użyjemy U-Boot jako bootloadera.
Po co nam to?
W początkowych (i późniejszych) fazach rozwoju często musimy zmieniać, edytować i przekompilowywać projekty. Płytki rozwojowe zazwyczaj startują z karty SD. Żeby skrócić czas iteracji, możemy pominąć wyjmowanie, montowanie, kopiowanie itd. Jest to możliwe dzięki protokołom sieciowym - pliki pozostają na komputerze hosta.
Czego potrzebujemy?
Komputer hosta z Ubuntu 22.04 (przynajmniej tego używam), konwerter USB-UART, karta SD, czytnik kart i Raspberry Pi 4 (lub cokolwiek masz pod ręką 😃).
To właściwie wszystko. Zaczynajmy!
Trivial File Transfer Protocol
Dlaczego w ogóle używać TFTP?
Trivial File Transfer Protocol (TFTP) to prosty protokół przesyłania plików w trybie lock-step, który pozwala klientowi pobierać lub wysyłać pliki na zdalny host. Jedno z głównych zastosowań to wczesne fazy uruchamiania węzłów z sieci lokalnej. TFTP jest używany do tego celu, bo jest bardzo prosty w implementacji.
Ze względu na prostą konstrukcję, TFTP można łatwo zaimplementować w kodzie o małym śladzie pamięciowym. Jest więc protokołem z wyboru w początkowych fazach każdej strategii uruchamiania sieciowego.
Źródło: Wikipedia
Najpierw zainstalujmy serwer TFTP i stwórzmy folder na pliki.
$ mkdir -p /home/${USER}/tftp
$ sudo apt-get install tftpd-hpa
Teraz zmienimy uprawnienia. Daemon TFTP musi mieć dostęp do folderu, a nasz użytkownik musi móc w nim zapisywać pliki.
# Powinniśmy mieć użytkownika tftp
$ cat /etc/passwd
...
tftp:x:133:139:tftp daemon,,,:/srv/tftp:/usr/sbin/nologin
...
$ sudo chown tftp:tftp /home/${USER}/tftp
$ sudo chmod -R 777 /home/${USER}/tftp
Teraz zmienimy konfigurację. Powinna wyglądać tak:
$ sudo vim /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/<user>/tftp"
TFTP_ADDRESS="0.0.0.0:6900"
TFTP_OPTIONS="--create --secure"
Ważna uwaga: standardowy port dla TFTP to 69. To może prowadzić do konfliktów z innymi usługami. U-Boot pozwala użyć innego portu. Możesz sprawdzić swoje porty, np. przez:
$ sudo netstat -lnp | grep 69
udp6 0 0 :::69 :::* 1246
Jak widać wyżej, mój port 69 jest już zajęty. Dlatego przydaje się możliwość użycia innego 😃.
Teraz możemy uruchomić daemon TFTP.
$ sudo systemctl start tftpd-hpa
# Sprawdź status, powinno wyglądać tak
$ sudo systemctl status tftpd-hpa
● tftpd-hpa.service - LSB: HPA's tftp server
Loaded: loaded (/etc/init.d/tftpd-hpa; generated)
Active: active (running) since Thu 2023-05-11 20:45:29 CEST; 12s ago
Docs: man:systemd-sysv-generator(8)
Process: 3502 ExecStart=/etc/init.d/tftpd-hpa start (code=exited, status=0/SUCCESS)
Tasks: 1 (limit: 18571)
Memory: 412.0K
CPU: 47ms
CGroup: /system.slice/tftpd-hpa.service
└─3510 /usr/sbin/in.tftpd --listen --user tftp --address 0.0.0.0:6900 --create>
May 11 20:45:29 build systemd[1]: Starting LSB: HPA's tftp server...
May 11 20:45:29 build tftpd-hpa[3502]: * Starting HPA's tftpd in.tftpd
May 11 20:45:29 build tftpd-hpa[3502]: ...done.
May 11 20:45:29 build systemd[1]: Started LSB: HPA's tftp server.
Warto szybko przetestować. Stwórzmy plik w folderze TFTP i prześlijmy go.
$ cd ~
$ echo "Test transfer" > ./tftp/test.txt
$ tftp
tftp> connect localhost 6900
tftp> get test.txt
Received 15 bytes in 0.0 seconds
tftp> quit
$ cat test.txt
Test transfer
Sukces!!! Przejdźmy do serwera NFS.
Network File System
Czym jest NFS?
NFS to rozproszony protokół systemu plików, który pozwala użytkownikowi na komputerze klienckim uzyskać dostęp do plików przez sieć komputerową tak, jak do lokalnej pamięci masowej. NFS, jak wiele innych protokołów, bazuje na systemie Open Network Computing Remote Procedure Call (ONC RPC).
Źródło: Wikipedia
Linux obsługuje NFS i pozwala nam uruchomić główny system plików przez NFS.
Konfiguracja serwera NFS jest jeszcze prostsza niż TFTP. Zainstalujmy serwer NFS, stwórzmy współdzielony folder i ustaw uprawnienia.
$ sudo apt-get install nfs-kernel-server
$ sudo mkdir /home/${USER}/nfs
$ sudo chmod 777 /home/${USER}/nfs
Teraz musimy udostępnić folder serwerowi NFS i ustawić uprawnienia.
$ sudo vim /etc/exports
/home/<user>/nfs *(rw,sync,no_root_squash,no_subtree_check)
- *: Zezwól na wszystkie IP
- rw: Eksportuj folder jako odczyt i zapis
- sync: Wybierz wersję synchroniczną
- no_root_squash: Żądania od użytkownika ID 0 są przetwarzane bez zmiany na inny ID
- no_subtree_check: Wyłącza sprawdzanie poddrzewa (może poprawić niezawodność)
Na koniec zrestartujmy usługę.
$ sudo service nfs-kernel-server restart
Jeśli wszystko poszło dobrze, możemy teraz uruchomić się przez NFS.
Stwórz obraz, bootloader itd.
Żeby ułatwić sobie życie, użyjemy Buildroot do stworzenia wszystkiego, czego potrzebujemy.
$ mkdir rpi4 && cd $_
$ git clone https://github.com/buildroot/buildroot.git
$ cd buildroot
$ git checkout tags/2022.02.12
Zobaczmy, jakie defconfigi są obsługiwane.
$ make list-defconfigs | grep "raspberrypi*"
raspberrypi0_defconfig - Build for raspberrypi0
raspberrypi0w_defconfig - Build for raspberrypi0w
raspberrypi2_defconfig - Build for raspberrypi2
raspberrypi3_64_defconfig - Build for raspberrypi3_64
raspberrypi3_defconfig - Build for raspberrypi3
raspberrypi3_qt5we_defconfig - Build for raspberrypi3_qt5we
raspberrypi4_64_defconfig - Build for raspberrypi4_64
raspberrypi4_defconfig - Build for raspberrypi4
raspberrypicm4io_64_defconfig - Build for raspberrypicm4io_64
raspberrypicm4io_defconfig - Build for raspberrypicm4io
raspberrypi_defconfig - Build for raspberrypi
raspberrypizero2w_defconfig - Build for raspberrypizero2w
Budujemy dla Raspberry Pi 4, więc wybierzmy raspberrypi4_64_defconfig.
$ make O=/home/${USER}/rpi4/build raspberrypi4_64_defconfig
$ cd ../build
Teraz skonfigurujmy Buildroot. Wybierzemy nowszą wersję U-Boot, bo pozwoli nam zmienić port TFTP.
$ make menuconfig
Filesystem images -> tar the root filesystem
Bootloaders -> U-Boot
Bootloaders -> U-Boot -> U-Boot version: Custom version
Bootloaders -> U-Boot -> U-Boot version: 2023.04
Bootloaders -> U-Boot -> Build system: Kconfig
Bootloaders -> U-Boot -> Board defconfig: rpi_arm64
Sprawdźmy, czy Linux jest przygotowany na NFS.
$ make linux-menuconfig
# Jeśli napotkamy problemy, możemy wyłączyć initial RAM FS
General setup -> Initial RAM filesystem and RAM disk (initramfs/initrd) support: Disable
# NFS jest zazwyczaj już włączony, ale powinniśmy wyłączyć wersję 2
File systems -> Network File Systems -> ...

Usuńmy obsługę NFS wersji 2.
Teraz musimy włączyć w U-Boot możliwość wyboru innego portu TFTP.
$ make uboot-menuconfig
Networking support -> Set TFTP UDP source/destination ports via the environment
Czas budować…
$ make
Jeśli dostajesz błędy, sprawdź wymagania i pakiety deweloperskie do budowania Linuxa 😉. Jeśli nie, kontynuuj!
Formatowanie karty SD
Teraz przygotujmy kartę SD.
Po podłączeniu karty SD musimy znaleźć jej nazwę. Możemy użyć dmesg lub lsblk. Ważne, żeby na pewno zidentyfikować kartę, z którą chcemy pracować!!!
$ sudo dmesg | tail
[sudo] password for op:
[10416.824858] sd 6:0:0:0: Attached scsi generic sg2 type 0
[10416.825236] scsi 6:0:0:1: Attached scsi generic sg3 type 0
[10417.054587] sd 6:0:0:0: [sdb] 62357504 512-byte logical blocks: (31.9 GB/29.7 GiB)
[10417.055601] sd 6:0:0:0: [sdb] Write Protect is off
[10417.055606] sd 6:0:0:0: [sdb] Mode Sense: 21 00 00 00
[10417.056370] sd 6:0:0:0: [sdb] Write cache: disabled, read cache: enabled, doesn\'t support DPO or FUA
[10417.057339] sd 6:0:0:1: [sdc] Media removed, stopped polling
[10417.058474] sd 6:0:0:1: [sdc] Attached SCSI removable disk
[10417.066080] sdb: sdb1
[10417.068324] sd 6:0:0:0: [sdb] Attached SCSI removable disk
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
...
sdb 8:16 1 29.1G 0 disk
├─sdb1 8:17 1 32M 0 part
└─sdb2 8:18 1 512M 0 part
...
Do formatowania karty SD możemy użyć np. fdisk lub GParted. Tutaj użyję fdisk. Najpierw musimy usunąć istniejące partycje (musimy też odmontować kartę).
$ sudo umount ...
$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.37.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): p
Disk /dev/sdb: 29.12 GiB, 31267487744 bytes, 61069312 sectors
Disk model: STORAGE DEVICE
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 1 65536 65536 32M c W95 FAT32 (LBA)
/dev/sdb2 65537 1114112 1048576 512M 83 Linux
Command (m for help): d
Partition number (1,2, default 2): 1
Partition 1 has been deleted.
Command (m for help): d
Selected partition 2
Partition 2 has been deleted.
Teraz stwórzmy nowe partycje.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (1-4, default 1):
First sector (2048-61069311, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-61069311, default 61069311): +1G
Created a new partition 1 of type 'Linux' and of size 1 GiB.
Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (2-4, default 2):
First sector (2099200-61069311, default 2099200):
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2099200-61069311, default 61069311):
Created a new partition 2 of type 'Linux' and of size 28.1 GiB.
Teraz utwórzmy partycję startową.
Command (m for help): a
Partition number (1,2, default 2): 1
The bootable flag on partition 1 is enabled now.
Następnie musimy ustawić formaty partycji. Partycja startowa powinna być FAT32, a rootfs w formacie ext.
Command (m for help): t
Partition number (1,2, default 2): 1
Hex code or alias (type L to list all): L
00 Empty 24 NEC DOS 81 Minix / old Lin bf Solaris
01 FAT12 27 Hidden NTFS Win 82 Linux swap / So c1 DRDOS/sec (FAT-
02 XENIX root 39 Plan 9 83 Linux c4 DRDOS/sec (FAT-
03 XENIX usr 3c PartitionMagic 84 OS/2 hidden or c6 DRDOS/sec (FAT-
04 FAT16 <32M 40 Venix 80286 85 Linux extended c7 Syrinx
05 Extended 41 PPC PReP Boot 86 NTFS volume set da Non-FS data
06 FAT16 42 SFS 87 NTFS volume set db CP/M / CTOS / .
07 HPFS/NTFS/exFAT 4d QNX4.x 88 Linux plaintext de Dell Utility
08 AIX 4e QNX4.x 2nd part 8e Linux LVM df BootIt
09 AIX bootable 4f QNX4.x 3rd part 93 Amoeba e1 DOS access
0a OS/2 Boot Manag 50 OnTrack DM 94 Amoeba BBT e3 DOS R/O
0b W95 FAT32 51 OnTrack DM6 Aux 9f BSD/OS e4 SpeedStor
0c W95 FAT32 (LBA) 52 CP/M a0 IBM Thinkpad hi ea Linux extended
0e W95 FAT16 (LBA) 53 OnTrack DM6 Aux a5 FreeBSD eb BeOS fs
0f W95 Ext'd (LBA) 54 OnTrackDM6 a6 OpenBSD ee GPT
10 OPUS 55 EZ-Drive a7 NeXTSTEP ef EFI (FAT-12/16/
11 Hidden FAT12 56 Golden Bow a8 Darwin UFS f0 Linux/PA-RISC b
12 Compaq diagnost 5c Priam Edisk a9 NetBSD f1 SpeedStor
14 Hidden FAT16 <3 61 SpeedStor ab Darwin boot f4 SpeedStor
16 Hidden FAT16 63 GNU HURD or Sys af HFS / HFS+ f2 DOS secondary
17 Hidden HPFS/NTF 64 Novell Netware b7 BSDI fs fb VMware VMFS
18 AST SmartSleep 65 Novell Netware b8 BSDI swap fc VMware VMKCORE
1b Hidden W95 FAT3 70 DiskSecure Mult bb Boot Wizard hid fd Linux raid auto
1c Hidden W95 FAT3 75 PC/IX bc Acronis FAT32 L fe LANstep
1e Hidden W95 FAT1 80 Old Minix be Solaris boot ff BBT
Aliases:
linux - 83
swap - 82
extended - 05
uefi - EF
raid - FD
lvm - 8E
linuxex - 85
Hex code or alias (type L to list all): b
Changed type of partition 'Linux' to 'W95 FAT32'.
Command (m for help): t
Partition number (1,2, default 2):
Hex code or alias (type L to list all): 83
Changed type of partition 'Linux' to 'Linux'.
Command (m for help): p
Disk /dev/sdb: 29.12 GiB, 31267487744 bytes, 61069312 sectors
Disk model: STORAGE DEVICE
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x00000000
Device Boot Start End Sectors Size Id Type
/dev/sdb1 * 2048 2099199 2097152 1G b W95 FAT32
/dev/sdb2 2099200 61069311 58970112 28.1G 83 Linux
I musimy zapisać zmiany.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
Na koniec sformatujmy partycje.
$ sudo mkfs.vfat -n "BOOT" /dev/sdb1
mkfs.fat 4.2 (2021-01-31)
$ sudo mkfs.ext4 -L "ROOTFS" /dev/sdb2
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 7371264 4k blocks and 1843200 inodes
Filesystem UUID: 14b83a0a-5ef2-493a-bcd0-a089cbec1570
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information:
done
Jeśli napotkasz problemy z uruchamianiem, sprawdź rozmiar karty SD. Czasami platformy potrzebują mniejszych kart (16 GB/32 GB powinno definitywnie działać).
Łatwiejszym sposobem jest użycie skryptu dostarczonego przez Chris Simmonds.
Przygotowanie skryptów U-Boot
Żeby skonfigurować uruchamianie TFTP na naszej platformie docelowej, użyjemy skryptów U-Boot. Pierwszy nazwiemy boot.scr. Będzie odpowiedzialny za konfigurację klienta TFTP i pobranie następnego skryptu.
Stwórzmy nowy folder i plik o nazwie boot.source.
$ mkdir bootscripts && cd $_
$ vim boot.source
setenv fileaddr 0xc00000
fatload mmc 0:1 ${fileaddr} uEnv.txt
setenv autoload no
dhcp
env import -t ${fileaddr}
tftpb ${fileaddr} tftp.scr
source ${fileaddr}
Ten skrypt załaduje plik uEnv.txt, pobierze adres IP przez DHCP i pobierze tftp.scr z naszego serwera TFTP.
Plik uEnv.txt tylko dostarcza IP naszego serwera, port TFTP i ścieżkę NFS.
$ vim uEnv.txt
serverip=192.168.0.241
tftpdstp=6900
nfspath=/home/<user>/nfs/
Następnie skrypt tftp.scr załaduje nasz obraz i ustawi argumenty startowe dla uruchomienia przez NFS.
$ vim tftp.source
tftpb ${kernel_addr_r} Image
setenv bootargs root=/dev/nfs rw rootwait console=tty1 console=ttyAMA0,115200 nfsroot=${serverip}:${nfspath},tcp,v3 ip=${ipaddr}
booti ${kernel_addr_r} - ${fdt_addr}
Ważne do zapamiętania:
Device tree (DT) można też załadować przez TFTP, ale bootloader Raspberry Pi modyfikuje DT podczas aplikowania nakładek. To prowadzi do problemów z U-Boot. Dlatego pozwolimy bootloaderowi RPI się tym zająć 😅. Więcej można znaleźć tutaj.
DT znajduje się pod fdt_addr, więc użyjemy go do uruchomienia.
Żeby włączyć uruchamianie przez NFS, powinniśmy podać ‘,tcp,v3’. Bez tego możemy napotkać problemy (opisane tutaj). Gdy wyłączymy NFS wersji 2, działa tak czy siak.
Na koniec skompilujmy skrypty. Jeśli nie mamy jeszcze narzędzi U-Boot, zainstalujmy je szybko:
$ sudo apt install u-boot-tools
Następnie uruchom:
$ mkimage -T script -A arm64 -C none -a 0x2400000 -e 0x2400000 -d boot.source boot.scr
Image Name:
Created: Sat May 13 20:19:55 2023
Image Type: AArch64 Linux Script (uncompressed)
Data Size: 166 Bytes = 0.16 KiB = 0.00 MiB
Load Address: 02400000
Entry Point: 02400000
Contents:
Image 0: 158 Bytes = 0.15 KiB = 0.00 MiB
$ mkimage -A arm64 -T script -C none -a 0xC00000 -e 0xC00000 -d tftp.source tftp.scr
Image Name:
Created: Sat May 13 17:48:39 2023
Image Type: AArch64 Linux Script (uncompressed)
Data Size: 203 Bytes = 0.20 KiB = 0.00 MiB
Load Address: 00c00000
Entry Point: 00c00000
Contents:
Image 0: 195 Bytes = 0.19 KiB = 0.00 MiB
Łatwym sposobem na poznanie adresów RAM, których możemy użyć, jest skopiowanie sdcard.img przez ‘dd’ i przerwanie startu. Wtedy możemy użyć
U-Boot> printenv
żeby wypisać środowisko i przejrzeć adresy ładowania.
Przygotowanie karty SD
Zamontujmy kartę SD.
$ sudo mount /dev/sdb1 /mnt/boot
Teraz możemy skopiować pliki na kartę.
$ sudo cp ~/rpi4/buildroot/images/u-boot.bin /mnt/boot/
$ sudo cp ~/rpi4/buildroot/images/bcm2711-rpi-4-b.dtb /mnt/boot/
$ sudo cp -R ~/rpi4/buildroot/images/rpi-firmware/* /mnt/boot/
$ sudo rm /mnt/boot/cmdline.txt
$ sudo cp ~/rpi4/bootscripts/boot.scr ~/rpi4/bootscripts/uEnv.txt /mnt/boot/
Następnie edytujemy plik config.txt i odmontowujemy kartę SD.
$ sudo vim /mnt/boot/config.txt
kernel=u-boot.bin
enable_uart=1
# Jeśli nie jest już ustawione
arm_64bit=1
$ sudo umount /mnt/boot
Przygotowanie katalogu NFS
Rozpakujmy główny system plików do współdzielonego folderu.
sudo tar -C /home/${USER}/nfs -xf ~/rpi4/build/images/rootfs.tar
Żeby zapobiec problemom z własnością, zmienimy ją na root.
$ sudo chown -R 0:0 ~/nfs/*
Przygotowanie katalogu TFTP
Żeby zmniejszyć ilość kopiowania do folderu TFTP, możemy użyć twardych dowiązań do plików. W ten sposób możemy zmieniać pliki, a przy następnym uruchomieniu użyta będzie nowa wersja. Ważne: dowiązania symboliczne nie są obsługiwane.
$ cd ~/tftp
$ cp ../rpi4/build/images/Image .
$ cp ../rpi4/bootscripts/tftp.scr .
$ ln ../rpi4/bootscripts/uEnv.txt .
Jedna wada - twarde dowiązanie jest usuwane, gdy dowiązanie lub źródło zostanie usunięte. Dzieje się tak również, gdy podczas edycji lub regeneracji stary plik źródłowy jest usuwany. W takim przypadku mogą wystąpić nieprzyjemne niespodzianki. Szybki przykład:
$ touch source.txt
# Komenda ls -l pokazuje liczbę twardych dowiązań (tutaj 1 lub 2)
$ ls -l
-rw-rw-r-- 1 fp fp 0 May 15 14:10 source.txt
# Teraz tworzymy twarde dowiązanie
$ ln source.txt link.txt
# I licznik jest zwiększony
$ ls -l
-rw-rw-r-- 2 fp fp 0 May 15 14:10 link.txt
-rw-rw-r-- 2 fp fp 0 May 15 14:10 source.txt
# Edycja pliku zmienia też zawartość dowiązania
$ echo "Text" > source.txt
$ cat link.txt
Text
# Nawet gdy źródło jest usunięte, mamy wciąż dostęp do zawartości
$ rm source.txt
# Np. skrypt usuwający i odtwarzający plik może sprawić, że uwierzymy, iż dowiązanie wciąż istnieje
$ touch source.txt
$ cat link.txt
Text
# Ale licznik twardych dowiązań jest zmniejszony
$ ls -l
-rw-rw-r-- 1 fp fp 5 May 15 14:13 link.txt
Dlatego sprawdź swoje pliki i jeśli to nie pasuje, po prostu skopiuj.
Podłączenie RPI
Piny UART to GPIO (14/15) 8/10 i 6 dla masy. Podłączając konwerter USB-UART, musimy skrzyżować TX i RX. Musimy też podłączyć Ethernet i zasilanie.

Jedna rzecz, która nie jest uwzględniona w tym artykule, to separacja między platformą docelową a ogólną siecią. Można to osiągnąć osobnym interfejsem sieciowym lub siecią wirtualną.
Uruchamianie…
Następnie nawiąż połączenie szeregowe przez program taki jak picocom:
$ picocom -b 115200 /dev/ttyUSB0
Po włączeniu Raspberry Pi powinniśmy zobaczyć poniższe logi.
...
TFTP from server 192.168.0.241; our IP address is 192.168.0.66
Filename 'tftp.scr'.
Load address: 0xc00000
Loading: ################################################## 267 Bytes
51.8 KiB/s
done
Bytes transferred = 267 (10b hex)
## Executing script at 00c00000
Using ethernet@7d580000 device
TFTP from server 192.168.0.241; our IP address is 192.168.0.66
Filename 'Image'.
Load address: 0x80000
Loading: ################################T T ################## 20.7 MiB
1.7 MiB/s
done
Bytes transferred = 21658112 (14a7a00 hex)
Moving Image from 0x80000 to 0x200000, end=17f0000
## Flattened Device Tree blob at 2eff2e00
Booting using the fdt blob at 0x2eff2e00
Working FDT set to 2eff2e00
Using Device Tree in place at 000000002eff2e00, end 000000002f002fa1
Working FDT set to 2eff2e00
Starting kernel ...
...
[ 7.037589] device=eth0, hwaddr=e4:5f:01:7a:84:ae, ipaddr=192.168.0.66, mask=255.255.255.0, gw=255.255.255.255
[ 7.048053] host=192.168.0.66, domain=, nis-domain=(none)
[ 7.053911] bootserver=255.255.255.255, rootserver=192.168.0.241, rootpath=
[ 7.054985] uart-pl011 fe201000.serial: no DMA platform data
[ 7.077101] VFS: Mounted root (nfs filesystem) on device 0:17.
[ 7.083500] devtmpfs: mounted
[ 7.093540] Freeing unused kernel memory: 3648K
[ 7.121371] Run /sbin/init as init process
Starting syslogd: OK
Starting klogd: OK
Running sysctl: OK
Seeding 2048 bits without crediting
Saving 2048 bits of non-creditable seed for next boot
Starting network: ip: RTNETLINK answers: File exists
Skipping eth0, used for NFS from 192.168.0.241
Welcome to Buildroot
Udało się!!!
Szybko przetestujmy naszą partycję NFS. Uruchommy płytkę i stwórzmy nowy plik na hoście
$ cd ~/nfs
$ sudo touch test.txt
i zweryfikujmy go na platformie docelowej.
# ls /
bin lib media proc sbin var
dev lib64 mnt root sys tmp
etc linuxrc opt run test.txt usr
Jak widać, możemy teraz wymieniać i edytować pliki bez dotykania karty SD.
Końcowe przemyślenia
Przez użycie dwóch skryptów U-Boot możemy łatwo aplikować zmiany do drugiego, bez potrzeby zmiany zawartości karty SD. Jeśli musimy zmodyfikować uEnv.txt, możemy oczywiście skopiować go na kartę lub użyć TFTP i magii U-Boot 😀. Dodajmy ‘test 1’ do naszego uEnv.txt, załadujmy go z hosta i zapiszmy do partycji startowej.
...
U-Boot> tftpb 0xc00000 uEnv.txt
Using ethernet@7d580000 device
TFTP from server 192.168.0.241; our IP address is 192.168.0.66
Filename 'uEnv.txt'.
Load address: 0xc00000
Loading: ################################################## 65 Bytes
12.7 KiB/s
done
Bytes transferred = 65 (41 hex)
U-Boot> save mmc 0:1 ${fileaddr} uEnv.txt ${filesize}
65 bytes written in 27 ms (2 KiB/s)
U-Boot> setenv fileaddr 0xc00000
U-Boot> fatload mmc 0:1 ${fileaddr} uEnv.txt
65 bytes read in 9 ms (6.8 KiB/s)
U-Boot> env import -t ${fileaddr}
## Warning: Input data exceeds 1048576 bytes - truncated
## Info: input data size = 1048578 = 0x100002
U-Boot> printenv test
test=1
Podsumowanie
W tym artykule skonfigurowaliśmy serwer TFTP i NFS. Zbudowaliśmy obraz Linuxa, główny system plików i bootloader używając Buildroot. Następnie skonfigurowaliśmy U-Boot do obsługi uruchamiania sieciowego. Na koniec użyliśmy go do uruchomienia Raspberry Pi 4 przez sieć.
To podejście jest łatwo odtwarzalne dla innych płytek i może drastycznie skrócić czasy iteracji podczas rozwoju. Dlatego warto poświęcić czas na konfigurację 😀.
Mam nadzieję, że się podobało. Dzięki za przeczytanie!
Dalsze czytanie 🙂
- Mastering Embedded Linux Programming – Third Edition — Frank Vasquez, Chris Simmonds
- Boot Linux Kernel From NFS (NFSboot)-buildroot
Powiązane wpisy
Embedded Linux od podstaw — szybko i łatwo z QEMU
Skompiluj system Linux i jego narzędzia budowania od zera...